ClientScript Packages - CSS media types - can they be specified?

Hi,

I’m investigating how useful ClientScript Packages may be for an upcoming project. Example config code is as follows:




,'packages' => array(

                      'mypackage'    => array(

                                              'baseUrl' => '/themes/default/'

                                             ,'css'     => array(

                                                                  'css/screen-only.css'

                                                                 ,'css/print-only.css'

                                                                 )

                                             ,'js'      => array(

                                                                  'js/script1.js'

                                                                 ,'js/script2.js'

                                                                 )

                                              )

                     )



When Yii builds the web page, it creates stylesheet links for all the CSS files, but there are no "media" attributes. Some of the CSS files are for "screen", and some for "print".




<link rel="stylesheet" type="text/css" href="/themes/default/css/screen-only.css" />

<link rel="stylesheet" type="text/css" href="/themes/default/css/print-only.css" />



How can we supply the media type in the Package definition so that the stylesheet links are correctly formed?




<link rel="stylesheet" type="text/css" href="/themes/default/css/screen-only.css" media="screen"/>

<link rel="stylesheet" type="text/css" href="/themes/default/css/print-only.css" media="print"/>



I know I can use "@media" within a CSS file for different types of media, but I would like to know if it is possible to define the media type within the Package definition.

Also, using "@media" within a single CSS file is not as efficient as specifying "media" attributes. I believe a web browser downloads only the CSS files required for a given environment by looking at the "media" attribute, but when the "@media" tag is used within a CSS file, the whole file needs to be downloaded before the web browser parses out what it needs.

The "media" attribute can also consist of quite complex statements (or could in the future) such as the following:




media="screen, 3d-glasses, print and resolution > 90dpi"



Not being able to define the "media" attribute within the Package is actually an issue as far as I can see.

Thanks in advance for your assistance.

Mike




public CClientScript registerCssFile(string $url, string $media='')



Certainly would appear so from the method signature for registering CSS. The really annoying thing about ClientScript is that it does not support IE comment conditionals, meaning you cant write <!-- if lt[ie7] --> (can’t remember exact syntax). Meaning if you need to shim or polyfill Internet Exploder you need to have browscap installed and make inclusion conditional serverside.

One of the reasons why I tend to not use ClientScript much. ;)

Hi Luke,

I’m aware of registerCssFile as I use it lots, but I was looking more at Packages which eliminate the need for multiple calls to registerCssFile; however, I cannot find an option with Packages that provides the same functionality as the “media” parameter on registerCssFile.

But thanks anyway :)

The only way I have found to allow conditional comments is to wrap them around the registerCssFile call, but that does not feel very "Yii".

Your secret’s safe with me, jacmoe ;)

Just out of curiosity … how do you include your CSS (files and/or inline) if you don’t use ClientScript?

Quite simply:


	<link rel="stylesheet" href="<?php echo Yii::app()->request->baseUrl; ?>/css/main.css" type="text/css" media="screen" />



Or, if it’s in a theme:


		<link rel="stylesheet" type="text/css" href="<?php  echo Yii::app() -> theme -> baseUrl;?>/css/main.css" />



Same for some scripts.

Probably not the best way, but it seems easier.

Doesn’t work for widgets, obviously.

I wish the clientscript/css classes in Yii were more like what’s in RoR.

Okay, after some checking, I’ve come up with a proposal and I would like your opinion.

I’ve extended CClientScript in the following way:




        public function renderCoreScripts()

        {

                if($this->coreScripts===null)

                        return;

                $cssFiles=array();

                $jsFiles=array();

		

                foreach($this->coreScripts as $name=>$package)

                {

                        $baseUrl=$this->getPackageBaseUrl($name);

                        if(!empty($package['js']))

                        {

                                foreach($package['js'] as $js)

                                        $jsFiles[$baseUrl.'/'.$js]=$baseUrl.'/'.$js;

                        }

                        if(!empty($package['css']))

                        {

                                foreach($package['css'] as $css)

// ######  Commented out the following original line of code ...

//                                    $cssFiles[$baseUrl.'/'.$css]='';

// ######  Added from here ...

				{

					if (!is_array($css))

					{

						$cssFiles[$baseUrl.'/'.$css] = '';

					}

					else

					{

						list($cssUrl, $cssParams) = $css;

						

						if (is_array($cssParams))

						{

							if (isset($cssParams['media']))

							{

								$cssFiles[$baseUrl.'/'.trim($cssUrl)] = trim($cssParams['media']);

							}

						}

					}

				}

// ######  ... to here :-)

                        }

                }

                // merge in place

                if($cssFiles!==array())

                {

                        foreach($this->cssFiles as $cssFile=>$media)

                                $cssFiles[$cssFile]=$media;

                        $this->cssFiles=$cssFiles;

                }

                if($jsFiles!==array())

                {

                        if(isset($this->scriptFiles[$this->coreScriptPosition]))

                        {

                                foreach($this->scriptFiles[$this->coreScriptPosition] as $url)

                                        $jsFiles[$url]=$url;

                        }

                        $this->scriptFiles[$this->coreScriptPosition]=$jsFiles;

                }

        }		




Then, in my config file, modify ‘clientScript’ in this way:




,'clientScript'		=>	array(

	'class'		=>	'application.components.MyClientScript'

	,'packages'	=>	array(

		'mypackage'		=>	array(

				'baseUrl'	=>	'/themes/default/'

//  We would usually say ...

//                              ,'css'          =>      array(

//                                              'css/screen-only.css'

//                                              ,'css/print-only.css'

//                                                            )

//  But having extended CClientScript, we can now use 'media' as an element ...

				,'css'		=>	array(

						array('css/screen-only.css', array('media' => 'screen,projection'))

						array('css/print-only.css', array('media' => 'print'))

								)

				,'js'		=>	array(

						'js/script1.js'

						,'js/script2.js'

								)

							)

						)

					)



This solution satisfies my needs of being able to specify the "media" attribute within a Package.

Can anyone see any issues with this approach?

Personally, I think the solution is quite simple, and does not impact existing code whilst providing more control for those individuals like me.

Maybe the proposed change could be incorporated into the core CClientScript code by the Dev team? ::)

… or am I getting ahead of myself.

I’ve corrected a bug that occurs when no parameters are passed, even though the css file is passed within an array.




        public function renderCoreScripts()

        {

                if($this->coreScripts===null)

                        return;

                $cssFiles=array();

                $jsFiles=array();

                

                foreach($this->coreScripts as $name=>$package)

                {

                        $baseUrl=$this->getPackageBaseUrl($name);

                        if(!empty($package['js']))

                        {

                                foreach($package['js'] as $js)

                                        $jsFiles[$baseUrl.'/'.$js]=$baseUrl.'/'.$js;

                        }

                        if(!empty($package['css']))

                        {

                                foreach($package['css'] as $css)

// ######  Commented out the following original line of code ...

//                                    $cssFiles[$baseUrl.'/'.$css]='';

// ######  Added from here ...

                                {

                                        if (!is_array($css))

                                        {

                                                $cssFiles[$baseUrl.'/'.$css] = '';

                                        }

                                        else

                                        {

                                                list($cssUrl, $cssParams) = $css;

// ** Added

                                                $cssFiles[$baseUrl.'/'.trim($cssUrl)] = '';

// **

                                                

                                                if (is_array($cssParams))

                                                {

                                                        if (isset($cssParams['media']))

                                                        {

                                                                $cssFiles[$baseUrl.'/'.trim($cssUrl)] = trim($cssParams['media']);

                                                        }

                                                }

                                        }

                                }

// ######  ... to here :-)

                        }

                }

                // merge in place

                if($cssFiles!==array())

                {

                        foreach($this->cssFiles as $cssFile=>$media)

                                $cssFiles[$cssFile]=$media;

                        $this->cssFiles=$cssFiles;

                }

                if($jsFiles!==array())

                {

                        if(isset($this->scriptFiles[$this->coreScriptPosition]))

                        {

                                foreach($this->scriptFiles[$this->coreScriptPosition] as $url)

                                        $jsFiles[$url]=$url;

                        }

                        $this->scriptFiles[$this->coreScriptPosition]=$jsFiles;

                }

        }  



+1 for the solution above.

@Techie42

Why dont you make a pull request with this? Would be nice to merge it to next version of Yii.

Hi Fragoulas,

Thanks for the support. Unfortunately, I don’t know how to do that, but feel free if you know how.

Hi all,

I made pull request with changes related to this issue:

git hub.com/yiisoft/yii/pull/2477

But with this solution you can use this format of package config:


'components' => array(

    'clientScript'=>array(

        ...

        'package_all_css' => array(

	    'css'	=>	array(

                'screen' => array(

                    'global.css', 'layout.css',

                ),

                'print' => array(

                    'homepage.css', 'notifier.css'

                ),

                'simple.css', 'another.css'

            ),

        ),

    ),

),

I’ve done simplier. I have several css of bootstrap. And added them to one single css file with media setting. I looks like this one


@import url("bootstrap.min.css") screen, projection;

@import url("bootstrap.fix.css") screen, projection;

@import url("bootstrap.theme.css") screen, projection;

@import url("bootstrap.theme.fix.css") screen, projection;

After that i add it to package and it works well.


'bootstrap' => array(

        'baseUrl' => '/public/',

        'js' => array('js/bootstrap.js'),

        'css' => array('css/bootstrap.css',),

        'depends' => array('jquery')

    )

No need to rewrite clientScript, we can setup it in ccs file. I know it’s not best solution but it’s work…