1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

The problem…

In Yii you can have all your JS and CSS files together with your PHP. For example, you have some view in /views/user/create.php and you can have your css & js in /views/user/assets/css/ and /views/user/assets/js/. That’s quite convenient when you reuse your views/controllers/components from application to application. But these directories are not accessible from the web, so there is publishing mechanism there. You should do:

  1. $files = array(‘Box’, ‘CommentObject’, ‘FavouriteStore’, ‘FavouriteObject’, ‘FriendObject’, ‘RateObject’, ‘init’, ‘rateComment’, ‘TagObject’);
  2. foreach ($files as $file)
  3. {
  4.         $path = CHtml::asset(dirname(__FILE__).‘/product/assets/’.$file.‘.js’);
  5.         $cs->registerScriptFile($path);
  6. }

Yes, it’s simple, but why should I do these routines all the time!

…and solution

Luckily, in Yii you can change everything. When we write

  1. Yii::app()->clientScript

we access the clientScript application component. This is default component, it is created from the class CClientScript. So we can extend this CClientScript and replace it in the application configuration file with our class. OK, here is /protected/components/AdvancedClientScript.php file:

  1. <?php
  2. class AdvancedClientScript extends CClientScript
  3. {
  4.         /**
  5.          * Registeres all files within the directory as JS
  6.          *
  7.          * @param string $path Path to the directory with files. With trailing ‘/’
  8.          * @param bool $makeAssets Wether or not publish file as asset. If the directory is under /protected/, you should set true (default)
  9.          * @param int $position Position where to put the file. @see CClientScript::registerScriptFile
  10.          */
  11.         public function registerScriptDirectory($path, $makeAssets = true, $position = self::POS_HEAD)
  12.         {
  13.                 $id = md5(serialize($path.$makeAssets.$position));
  14.                 $data = Yii::app()->cache->get($id);
  15.                 if($data === false)
  16.                 {
  17.                         $di = new DirectoryIterator($path);
  18.                         foreach ($di as $file)
  19.                         {
  20.                                 if (!$file->isDot() && !$file->isDir())
  21.                                 {
  22.                                         $data[] = $makeAssets ? CHtml::asset($path.$file->getFileName()) : $path.$file->getFileName();
  23.                                 }
  24.                         }
  25.                         Yii::app()->cache->set($id, $data, 1800);
  26.                 }
  27.                 foreach ($data as $file)
  28.                 {
  29.                         $this->registerScriptFile($file, $position);
  30.                 }
  31.         }
  32.  
  33.         /**
  34.          * Registeres all files within the directory as CSS
  35.          *
  36.          * @param string $path Path to the directory with files. With trailing ‘/’
  37.          * @param bool $makeAssets Wether or not publish file as asset. If the directory is under /protected/, you should set true (default)
  38.          * @param int $position Position where to put the file. @see CClientScript::registerCssFile
  39.          */
  40.         public function registerCSSDirectory($path, $makeAssets = true, $position = self::POS_HEAD)
  41.         {
  42.                 $id = md5(serialize($path.$makeAssets.$position));
  43.                 $data = Yii::app()->cache->get($id);
  44.                 if($data === false)
  45.                 {
  46.                         $di = new DirectoryIterator($path);
  47.                         foreach ($di as $file)
  48.                         {
  49.                                 if (!$file->isDot() && !$file->isDir() && (stristr($file->getFileName(), ‘.css’) !== false))
  50.                                 {
  51.                                         $data[] = $makeAssets ? CHtml::asset($path.$file->getFileName()) : $path.$file->getFileName();
  52.                                 }
  53.                         }
  54.                         Yii::app()->cache->set($id, $data, 1800);
  55.                 }
  56.                 foreach ($data as $file)
  57.                 {
  58.                         $this->registerCssFile($file, $position);
  59.                 }
  60.         }
  61.  
  62.         /**
  63.          * Registeres all files inthe passed array as JS
  64.          *
  65.          * @param array $files Array of file names
  66.          * @param string $basePath Path to the container directory
  67.          * @param bool $makeAssets Wether or not publish file as asset. If the directory is under /protected/, you should set true (default)
  68.          * @param int $position Position where to put the file. @see CClientScript::registerScriptFile
  69.          */
  70.         public function registerScriptFiles($files = array(), $basePath = -1, $makeAssets = true, $position = self::POS_HEAD)
  71.         {
  72.                 if (sizeof($files) <= 0) return;
  73.                 if ($basePath === -1) $basePath = dirname(__FILE__);
  74.                 $id = md5(serialize(implode($files).$basePath.$makeAssets.$position));
  75.                 $data = Yii::app()->cache->get($id);
  76.                 if($data === false)
  77.                 {
  78.                         for ($i = 0, $s = sizeof($files); $i < $s; $i++)
  79.                         {
  80.                                 $data[] = $makeAssets ? CHtml::asset($path.$files[$i]) : $path.$files[$i];
  81.                         }
  82.                         Yii::app()->cache->set($id, $data, 1800);
  83.                 }
  84.                 foreach ($data as $file)
  85.                 {
  86.                         $this->registerScriptFile($file, $position);
  87.                 }
  88.         }
  89.  
  90.         /**
  91.          * Registeres all files inthe passed array as CSS
  92.          *
  93.          * @param array $files Array of file names
  94.          * @param string $basePath Path to the container directory
  95.          * @param bool $makeAssets Wether or not publish file as asset. If the directory is under /protected/, you should set true (default)
  96.          * @param int $position Position where to put the file. @see CClientScript::registerCssFile
  97.          */
  98.         public function registerCssFiles($files = array(), $basePath = -1, $makeAssets = true, $position = self::POS_HEAD)
  99.         {
  100.                 if (sizeof($files) <= 0) return;
  101.                 if ($basePath === -1) $basePath = dirname(__FILE__);
  102.                 $id = md5(serialize(implode($files).$basePath.$makeAssets.$position));
  103.                 $data = Yii::app()->cache->get($id);
  104.                 if($data === false)
  105.                 {
  106.                         for ($i = 0, $s = sizeof($files); $i < $s; $i++)
  107.                         {
  108.                                 $data[] = $makeAssets ? CHtml::asset($path.$files[$i]) : $path.$files[$i];
  109.                         }
  110.                         Yii::app()->cache->set($id, $data, 1800);
  111.                 }
  112.                 foreach ($data as $file)
  113.                 {
  114.                         $this->registerScriptFile($file, $position);
  115.                 }
  116.         }
  117. }
  118. ?>

I’ve added 4 methods there:

  • registerScriptDirectory – registers all files in the specified directory as JS files (no check is performed)
  • registerCssDirectory – registers all files in the specified directory as CSS files
  • registerScriptFiles – same as first, but deals with an array of files
  • registerCssFiles – same as second, but deals with an array of files

And no we’ll replace default component with ours. My config is in the /protected/config/main.php:

  1. // application components
  2. ‘components’=>array(
  3.         ‘clientScript’ => array(
  4.                 ‘class’ => ‘application.components.AdvancedClientScript’ //here we specified the path to our component
  5.         ),
  6.         //other components follow
  7.         ……
  8. ),

In order to make it work, you should enable caching. This is quite simple. I’ll show, how to use caching in DB:

  1. You should have your DB connection configured. See details here.
  2. Add new component to the application config:
    1. ‘cache’ => array(
    2.         ‘class’ => ‘system.caching.CDbCache’,
    3.         ‘connectionID’ => ‘db’//this is id of the DB module. If you didn’t change anything, it should have name "id"
    4. ),

We’re done! Now we can publish the files simply calling:

  1. Yii::app()->clientScript->registerScriptDirectory(dirname(__FILE__).‘/user/assets/ui/’);

Note, that the code I wrote uses caching and when I call

  1. Yii::app()->cache->set($id, $data, 1800);

I cache this for 30 mins (1800 seconds). However, it is much more flexible to have either constant or file with parameters.

UPDATE: Post updated after discussion in the Yii forum according to the Maxximus advice.

No related posts.

Related posts brought to you by Yet Another Related Posts Plugin.

Share this post with a friend Share this post with a friend

3 Comments

  1. jz says:

    Спасибо за простое, но полезное расширение.

    Я попробовал Yii Framework на небольшом рабочем проекте. Очень понравилось. Раньше был на CodeIgniter, но как-то последнее время его разработка мало движется, а при написании кода приходится делать много лишних действий. Поддержка в Yii концепций примешивания кода, behaviors, events — тоже обрадовало. Жалко, что потенциал этих паттернов пока ещё толком не раскрыт в расширениях.

    Кстати, для этого сайта я писал подобный скрипт. Только у меня он был behavior. Больше для интереса, чем для пользы, но с CClientScriptMinify удалось использовать :) И ещё там есть поддержка mixed-групп, в которых перечислены CSS+JS+Core файлы. Была идея добавить туда ещё и управление зависимостями, но вовремя остановился: слишком маленькая задача, чтобы столько времени на неё тратить.

    Что-то я расписался… Прошу прощения)
    —————————————
    Translation by Konstantin:
    Thanks for the simple, but helpful extension.

    I tried Yii Framework on the little project. I liked it very much. I was using CodeIgniter before, but its development isn’t very active recent time and it requires lots of extra things when writing the code. Mixing of the code in Yii (behaviours, events) – seems to be great, but unfortunately the potential of these patterns isn’t revealed in full in the extensions.

    By the way, for my project I wrote the similar script. But I implemented it as behaviour. More for interest, than for usefulness, I used CClientScriptMinify also :) There is also mixed-group support in it, where there are CSS+JS+Core files. I also had an idea to add dependencies management there, but stopped: too little project to spend so much time on it.

    Seems I wrote too much… Sorry )

  2. Konstantin Mirin says:

    Nice comment :) A few questions:
    1) I guess you were talking about the same functionality I presented here? How did you implement this as behaviour?
    2) What are mixed groups? Any more info? Sounds quite interesting :)
    3) Dependencies management – like it’s done for the core libraries? Cool. Could you describe this in more details?

  3. jz says:

    1) Да, мне нужна была примерно та же функциональность. С той разницей, что в своём проекте я не использовал assets и группировку файлов по директориям. Моя цель была упростить кодирование при подключении группы файлов и собрать все конфиги в одном месте (DRY), чтобы легче было переходить с development версии на production (для этого достаточно в конфиге выдавать разные группы файлов в зависимости от константы YII_DEBUG, например).

    2) Mixed groups — придуманный мной термин, не более того. Это удобно, если подключаемый скрипт тянет за собой css или зависит от какой-нибудь библиотеки. Ничего экстраординарного, просто в конфигах задаются правила под каждую группу файлов. Словами это дольше объяснять, чем кодом, так что вот пример конфигурации из protected/config/main.php:

    [code lang="php"]

    'clientScript' => array(
      // … come ClientScript settings …
      'behaviors' => array(
      'scriptGroupBehavior' => array(
         'class' => 'application.extensions.CScriptGroupBehavior',
             'scriptGroups' => array(
               'login' => array('/js/forms.js', '/js/login.js'),
              ),
              'mixedGroups' => array(
      'base' => array(
        'core' => 'jquery',
    'css' => array('/css/reset.css', '/css/style.css'),
    'script' => '/js/all.js'
    ),
    'admin' => array(
      'core' => 'jquery',
       'css' => array('/css/reset.css', '/css/admin.css', '/css/rte.css'),
    'script' => array('/js/rte.js', '/js/admin.js'),
    ),
    ),),),),),),

    [/code]

    Соответственно, CScriptGroupBehavior расширяет класс CBehavior и содержит методы registerCoreGroup, registerScriptGroup, registerCssGroup, registerCoreGroup. После этого можно пользоваться как вызовами вида Yii::app()->clientScript->registerCssFile(…), так и Yii::app()->clientScript->registerCssGroup(…).

    3) До написания разрешения зависимостей между группами дело не дошло: не понадобилось. Но это сделать довольно просто, надо только немного подумать над упрощением конфигурации и производительностью.

    А может быть, стоит спросить у Qiang, что он думает о вынесении настройки core libraries в конфиги CClientScript. Тогда все эти ухищрения с подключением групп файлов станут не нужны: можно будет просто регистрировать свои core-группы.

    Если вам интересен код CScriptGroupBehavior (хоть он и тривиален в моём исполнении), то я могу выложить его в расширениях Yii или выслать по e-mail. Посылать его в комментах не буду просто потому, что и так длинные пишу :)

    P.S. Кстати, было бы неплохо добавить в этот программерский блог поддержку комментариев с тегом “code” и прикрутить плагин (например, highlight) для их подсветки.

Leave a Reply