Автоматическое подключение css и js файлов в Yii

    Доброго времени суток.

    На этот раз хочу, рассказать о способе автоматического подключения css и js файлов в Yii. Идея заключается в том, что бы к каждому файлу вида привязывать соответствующую папку с ресурсами (css, js, images). Это удобно, т.к. в большенстве случаев конкретные «assets» используются для конкретных файлов вида.

    И так, ближе к коду. Расширим класс Controller (который наверняка лежит у вас в папке «components» :)), добавив в него следующую функциональность:

    /**
     * Array of paths of assets 
     * @var array
     */
    private $_assetsPaths = array();
    
    /**
     * Array of asset Url
     * @var array
     */
    private $_assetsUrl = array();
    
    /**
     * Name of directory for css files
     * @var strign
     */
    protected $_cssDirName = 'css';
    
    /**
     * Default file name of css
     * @var string
     */
    protected $_defaultCssFile = 'index.css';
    
    /**
     * Name of directory for script files
     * @var strign
     */
    protected $_scriptDirName = 'js';
    
    /**
     * Default file name of script
     * @var string
     */
    protected $_defaultScriptFile = 'index.js';
    
    /**
     * Name of directory for images
     * @var strign
     */
    protected $_imageDirName = 'images';
    
    
    /**
     * This method is invoked at the beginning of {@link render()}.
     * 
     * @param string $view the view to be rendered
     * @return boolean whether the view should be rendered.
     */
    protected function beforeRender($view) 
    {
        $this->_setupScript($view);
        $this->_setupCss($view);
    
        $viewCamelCase = preg_replace_callback(
            '/_([a-z0-9])/',
            function ($char) {
                return strtoupper($char[1]);
            },
            ucfirst($view)
        );
    
        $methodScript = '_setupScript' . $viewCamelCase;
        if (method_exists($this, $methodScript)) {
            $this->$methodScript($view);
        }
    
        $methodCss = '_setupCss' . $viewCamelCase;
        if (method_exists($this, $methodCss)) {
            $this->$methodCss($view);
        }
    
        return true;
    }
    
    /**
     * Setup script files
     * 
     * @param string $view
     * @return void
     */
    protected function _setupScript($view) 
    {
        $scriptRealPath = $this->getScriptPath($view, $this->_defaultScriptFile);
        if (is_file($scriptRealPath)) {
            $scriptPublishedUrl = $this->getScriptUrl($view, $this->_defaultScriptFile);
            Yii::app()->clientScript->registerScriptFile($scriptPublishedUrl);
        }
    }
    
    /**
     * Setup css files
     * 
     * @param string $view
     * @return void
     */
    protected function _setupCss($view) 
    {
        $cssRealPath = $this->getCssPath($view, $this->_defaultCssFile);
        if (is_file($cssRealPath)) {
            $cssPublishedUrl = $this->getCssUrl($view, $this->_defaultCssFile);
            Yii::app()->clientScript->registerCssFile($cssPublishedUrl);
        }
    }
    
    /**
     * Returns the published script URL
     * 
     * @param string $view
     * @param string $fileName
     * @return string|false
     */
    public function getScriptUrl($view, $fileName) 
    {
        if (($publishedUrl = $this->getPublishedAssetsUrl($view))) {
            return $publishedUrl . '/' . $this->_scriptDirName . '/' . $fileName;
        }
    
        return false;
    }
    
    /**
     * Returns the real script Path
     * 
     * @param string $fileName
     * @param string $view
     * @return string|false
     */
    public function getScriptPath($view, $fileName) 
    {
        if (($path = $this->getAssetsPath($view))) {
            return $path . DIRECTORY_SEPARATOR . $this->_scriptDirName . DIRECTORY_SEPARATOR .  $fileName;
        }
    
        return false;
    }
    
    /**
     * Returns the published css URL
     * 
     * @param string $view
     * @param string $fileName
     * @return string|false
     */
    public function getCssUrl($view, $fileName) 
    {
        if (($publishedUrl = $this->getPublishedAssetsUrl($view))) {
            return $publishedUrl . '/' . $this->_cssDirName . '/' . $fileName;
        }
    
        return false;
    }
    
    /**
     * Returns the real css path
     * 
     * @param string $view
     * @param string $fileName
     * @return string|false
     */
    public function getCssPath($view, $fileName) 
    {
        if (($path = $this->getAssetsPath($view))) {
            return $path . DIRECTORY_SEPARATOR . $this->_cssDirName . DIRECTORY_SEPARATOR .  $fileName;
        }
    
        return false;        
    }
    
    /**
     * Returns the published image URL
     * 
     * @param string $view
     * @param string $fileName
     * @return string|false
     */
    public function getImageUrl($view, $fileName) 
    {
        if (($publishedUrl = $this->getPublishedAssetsUrl($view))) {
            return $publishedUrl . '/' . $this->_imageDirName . '/' . $fileName;
        }
    
        return false;
    }
    
    /**
     * Returns the real image path
     * 
     * @param string $view
     * @param string $fileName
     * @return string|false
     */
    public function getImagePath($view, $fileName) 
    {
        if (($path = $this->getAssetsPath($view))) {
            return $path . DIRECTORY_SEPARATOR . $this->_imageDirName . DIRECTORY_SEPARATOR .  $fileName;
        }
    
        return false;  
    }
    
    /**
     * Returns alias of assets
     * 
     * @param string $view
     * @return string|false
     */
    protected function getAssetsPath($view) 
    {
        if (!array_key_exists($view, $this->_assetsPaths)) {
            $assetPath = false;
    
            $viewPath = $this->getViewFile($view);
            if ($viewPath) {
                if (($pos = strrpos($viewPath, DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR)) !== false) {
                    $extension = ($renderer=Yii::app()->getViewRenderer()) !== null ? 
                        $renderer->fileExtension : 
                        '.php';
    
                    $assetPath = substr($viewPath, 0, $pos) . DIRECTORY_SEPARATOR 
                               . 'assets'
                               . substr($viewPath, $pos + 1 + strlen('views'));
                    $assetPath = dirname($assetPath) . DIRECTORY_SEPARATOR .  basename($assetPath, $extension);
                }
            }
    
            $this->_assetsPaths[$view] = $assetPath;
        }
    
        return $this->_assetsPaths[$view];
    }        
    
    /**
     * Returns the published asset URL
     * 
     * @param string $view
     * @return string|false
     */
    public function getPublishedAssetsUrl($view) 
    {
        if (!array_key_exists($view, $this->_assetsUrl)) {            
            $assetsUrl  = false;
            $assetsPath = $this->getAssetsPath($view);
            if ($assetsPath) {
                $assetsUrl = Yii::app()->assetManager->publish($assetsPath);
            }
    
            $this->_assetsUrl[$view] = $assetsUrl;
        }
    
        return $this->_assetsUrl[$view];
    }    
    

    Разберем как это работает. В «beforeRender($view)» перед рендерингом вызываются методы:

    // Подключение скриптов (по умолчанию подключает файл по пути assets/{controllerName}/{viewName}/js/index.js)
    _setupScript($view) 
    
    // Подключение стилей (по умолчанию подключает файл по пути assets/{controllerName}/{viewName}/css/index.css)
    _setupCss($view)
    

    В них происходит автоматическое подключение соответствующих css и js файлов для заданного файла вида.

    Далее, здесь же в «beforeRender()» запускаются дополнительные методы для подключения стилей и скриптов (если они определены). Отличие этих методов от предыдущих в том, что они привязаны к конкретному файлу вида. Т.е. в формировании названия соответствующего метода участвует имя файла вида, например, имя метода для скриптов складывается из правила: "_setupScript" + «Имя файла вида в верблюжьей нотации», для стилей: "_setupCss" + «Имя файла вида в верблюжьей нотации».

    Так же теперь нам доступны следующие методы:

    Методы возвращающие URL адреса до опубликованных ресурсов:
    getScriptUrl($view, $fileName) // возвращает URL адрес до js файла
    getCssUrl($view, $fileName) // возвращает URL адрес до css файла
    getImageUrl($view, $fileName) // возвращает URL адрес до файла изображения 
    

    Методы возвращающие реальные пути ресурсов (в папке assets в protected):
    getScriptPath($view, $fileName) // возвращает путь до js файл
    getCssPath($view, $fileName) // возвращает путь до css файл
    getImagePath($view, $fileName) // возвращает путь до файл изображения
    

    И общие методы:
    getAssetsAlias($view) // возвращает алиас до папки "assets", которая лежит в protected
    getPublishedAssetsUrl($view) // возвращает URL адрес до опубликованной директории "assets"
    

    Имена директорий (для js, css файлов и изображений) и файлов по умолчанию определены в свойствах класса контроллера.
    protected $_cssDirName = 'css';
    protected $_defaultCssFile = 'index.css';
    protected $_scriptDirName = 'js';
    protected $_defaultScriptFile = 'index.js';
    protected $_imageDirName = 'images';
    

    Соответственно вы можете изменить их на нужные вам. По желанию можно вынести их в конфиг приложения, проделав несложные модификации в коде.

    Использование.

    Вы можете либо переопределить метод "_setupCss($view)", если вам нужно подключать css файлы для всех возможных файлов вида контроллера:
    /**
     * Setup css files
     * 
     * @param string $view
     * @return void
     */
    protected function _setupCss($view) 
    {
         parent::_setupCss($view);
    
         // будет искать my_css_file.css для каждого файла вида
         Yii::app()->clientScript->registerCssFile($this->getCssUrl($view, 'my_css_file.css'));
    }
    

    Либо определить метод "_setupCss" + «Имя файла вида в верблюжьей нотации», если вам нужно подключать css файлы для конкретного файла вида:
    /**
     * Setup css files
     * 
     * @param string $view
     * @return void
     */
    protected function _setupCssRegistration($view) 
    {
        Yii::app()->clientScript->registerCssFile($this->getCssUrl($view, 'my_css_file.css'));
    } 
    

    Данный метод будет вызван только при рендеринге «registration»

    Картинки можно получать так:
    CHtml::image($this->getImageUrl($view));
    


    На это всё. Всем спасибо за внимание.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 16

      0
      В бихэйвор бы все это ;)
        0
        или просто так
        class SiteController extends Controller
        {
           public $package = array(
             'basePath' => 'application.views.site.assets';
             'css' => array('index.css'),
             'js' => array('index.js'),
             'depends' => array('jquery'),
           );
        
           public function init()
           {
               $this->package['baseUrl'] = Yii::app()->assetManager->publish($this->package['basePath']);
               Yii::app()->clientScript->addPackage('site')->registerPackage('site');
           }
           public function getAssetUrl()
           {
               return $this->package['baseUrl'];
           }
        
        }


        как-то так
          0
          Это разные вещи. Вы предлагаете публиковать пакпейдж. Мой вариант предполагает зависимость ресурсов от файла вида.
            0
            А что бывают такие задачи, когда для каждого представления нужно публиковать отдельные ресурсы? С группировать их в ресурсы для контроллера нельзя?
              0
              А что бывают такие задачи, когда для каждого представления нужно публиковать отдельные ресурсы?

              Да, css и js обычно используются для конкретного HTML.
                0
                Это понятно. Я просил, есть ли смысл публиковать их по отдельности?
                  0
                  Есть смысл их автоматически загружать через clientScript для конкретного представления. Исходя из этого уже и идёт логика публикации.
                    0
                    Что мешает опубликовать всю пачку и имея $assetUrl, регистрировать через clientScirpt?
                      0
                      Можно хоть весь проект публиковать. Только зачем? в чем преимущество?
                        +1
                        Преимущество в том что твой код сводится к 10 строчкам в одном месте.
                        ИМХО, поддерживать код будет легче, особенно со стороны и через некоторое время.

                        Следить за каждым файлом, для каждого вида это же застрелится легче. Разве не удобнее держать ресурсы для каждого контроллера в одной папке и публиковать ее сразу.

                        Дело личных предпочтений конечно, но я думаю ты усложняешь себе и другим кто будет поддерживать твой код жизнь.
                          0
                          Смысл моего метода сводится к тому, что зная только псеводо путь представления можно получить ссылку на любой опубликованный ресурс. Т.е. вполне возможно, что может понадобиться подключить представление стороннего модуля или получить какой нибудь отдельный ресурс.
                          Например, таким образом
                          $this->getCssUrl('application.modules.default.views.default.index', 'index.css') 
                          

                          мы можем получить ссылку на css файл из модуля «default», контроллера «default», вида «index».

                          А нужно ли это вам, это у же вам решать. Я ни к чему не обязываю, просто описал способ который мы используем.
                            0
                            Да возможно у вас острая необходимость именно в такой реализации, я тоже описываю альтернативные решения и выявляю тонкие места в вашем.

                            Учитывая что у контроллера и особенно у модуля есть возможность менять viewPath, то работать с ресурсами по конкретным псевдонимам может привести к неожиданному результату.
                              0
                              В любом случае, спасибо за объективную критику.
          +2
          А в чем профит?
            0
            А мне нравится идея, подключать нужные css/js в layout'ах и в представлениях. В layout'ах общие, а в представлениях, те которые нужны только этому представлению.
              0
              Так это самый стандартный вариант. В этом случае, изменив представление, напр новый дизайн, не нужно лазить в контроллер. Но когда начинаешь искать как бы чуток оптимизировать работу, получаются вот такие идеи.

            Only users with full accounts can post comments. Log in, please.