Zend Framework: стремимся к MVC для Javascript, CSS

    При постепенном изучении Zend Framework и построении с его помощью базовых приложений я заметил, что клиентский js-код и инлайновые стили попадают в скрипты вида, при этом они начинают занимать почти половину всего скрипта. В принципе, ничего плохого в этом нет, но мне такая мусорка начала давить на глаза. Кроме того, инлайновый js постепенно превращается в жестко привязанную к скрипту вида конструкцию, полностью зависимую как от самого скрипта, так и от переданных контроллером данных. При всем этом не оставляет желание писать на javascript код максимально красивый при помощи библиотеки jQuery.

    Итак, что в данном случае можно сделать. Первая же мысль — вынести инлайновые скрипты и стили во внешние файлы и подключать их через помощники вида headLink и headStyle. Если со стилями все примерно понятно — достаточно специфический стиль для конкретного вида вынести в css файл и подключать его по мере необходимости, то js по-прежнему зависим от данных.

    В качестве первого решения напишем плагин к frontController для подключения по мере необходимости нужных файлов:

    1. class My_Controller_Plugin_Webinit extends Zend_Controller_Plugin_Abstract
    2. {
    3.     public function preDispatch()
    4.     {
    5.         $controllerName    = $this->_request->getControllerName();
    6.         $actionName        = $this->_request->getActionName();
    7.         
    8.         $view = Zend_Layout::getMvcInstance()->getView();
    9.         
    10.         if ( file_exists(APPLICATION_CSS_FOLDER.'/'.
    11.             $controllerName.'/'.
    12.             $actionName.'.css') )
    13.         {
    14.             $view->assign('cssControllerAction',$controllerName.'/'.$actionName.'.css');    
    15.         }
    16.         
    17.         if ( file_exists(APPLICATION_JS_FOLDER.'/'.
    18.             $controllerName.'/'.
    19.             $actionName.'.js') )
    20.         {
    21.             $view->assign('jsControllerAction', $controllerName.'/'.$actionName.'.js');    
    22.         }
    23.     }
    24. }
    * This source code was highlighted with Source Code Highlighter.


    Не забываем в Bootstrap классе подключить созданный плагин:

    1. protected function _initFrontControllerPlugins()
    2. {
    3.     $frontController = Zend_Controller_Front::getInstance();
    4.     $frontController->registerPlugin( new My_Controller_Plugin_Webinit() );
    5. }
    * This source code was highlighted with Source Code Highlighter.


    Как уже понятно, используемые css и js файлы размещаются в соответствии с полной аналогией размещения скриптов вида. Только вместо /application/views/script/controllerName/actionName/ корневыми директориями для них будет соответственно public/css/ и public/js/. Для этого нужно объявить три константы в index.php:

    1. defined('APPLICATION_PUBLIC_FOLDER')
    2.     || define( 'APPLICATION_PUBLIC_FOLDER',
    3.              dirname(__FILE__) );
    4.  
    5. defined('APPLICATION_CSS_FOLDER')
    6.     || define( 'APPLICATION_CSS_FOLDER',
    7.              realpath(APPLICATION_PUBLIC_FOLDER . '/css') );             
    8.  
    9. defined('APPLICATION_JS_FOLDER')
    10.     || define( 'APPLICATION_JS_FOLDER',
    11.              realpath(APPLICATION_PUBLIC_FOLDER . '/js') );
    * This source code was highlighted with Source Code Highlighter.


    Плагин позволяет подключить в нашем Layout нужные файлы следующим кодом:

    1. <? if ($this->cssControllerAction) $this->headLink()->appendStylesheet('/css/'.$this->cssControllerAction)?>
    2.  
    3. <? if ($this->jsControllerAction) $this->headScript()->appendFile('/js/'.$this->jsControllerAction)?>
    * This source code was highlighted with Source Code Highlighter.

    Открываем браузер — нужные файлы подключились.

    Остается вопрос с жаваскриптом. Мы вынесли код во внешний файл в пределах public директории. Но есть необходимость, например, вывести приветственный текст на основе словаря функцией alert(). Естественно, для перевода используется Zend_Translate, но опять же, как быть, если все наши данные доступны только в скрипте вида?

    Для собственных задач я посчитал идеальным решением организацию неймспейсов. К примеру, для переводчика внутри одного скрипта вида нам необходим ограниченный набор фраз. Значит мы можем организовать хранилище этих фраз и отдать их необходимому обработчику на уровне js.

    Вот как это происходит.

    В основном js-файле моего проекта application.js происходит настройка глобальных методов, объектов и обработчиков. Сюда можно добавить:

    1. $(document).ready(function(){
    2.     $.TRANSLATION = {};
    3. });
    * This source code was highlighted with Source Code Highlighter.


    Хранилище огранизовано, теперь нужно заполнить его.

    Для этого в скрипте вида нам понадобится следующий код:

    1. <script type="text/javascript">
    2. $(document).ready(function(){
    3.     $.data($.TRANSLATION, "Nice to see you!", '<?=$this->translate("Nice to see you!")?>');
    4. </script>
    * This source code was highlighted with Source Code Highlighter.


    Для использования данных из хранилища мы можем обратиться из любой функции, обрабатывающей этот участок проекта, достучаться до хранилища и получить нужные данные:

    1. alert( $.data($.TRANSLATION, "Nice to see you!") );
    * This source code was highlighted with Source Code Highlighter.


    Итак, мы получили следующую картину: для контроллера index, экшена index существует файл в паблик-директории /js/index/index.js, являющийся набором функций для работы. При этом все необходимые данные попадают в хранилище данных непосредственно в скрипте вида, а само хранилище объявляется глобально для всех скриптов вида в основном js-файле приложения.

    По модели MVC. Немного притянуто за уши, но все же.

    В качестве контроллера (реакцией на пользователя) выступает плагин к frontController, организующий подключение, скрипт layout-а и основа — application.js.

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

    В качестве вида выступает подключаемый код javascript, полностью отвечающий за клиентское представление.

    В хранилище можно накапливать любые данные для дальнейшего использования.
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 25
    • +1
      И все же не могу не спросить, зачем?
      • 0
        Меня такой подход заставляет писать код на js чище, собирать отдельные объекты и функции. И, по-моему, скрипты вида — не более чем макет, который для удобства работы следует отделить от жаваскрипта и стилей.
        • 0
          эм… у меня это реализуется так: приложения (в 95% случаев) работает и без яваскрипта. А яваскрипт сам по себе нужен только как часть пользовательского интерфейса, так что при компиляции шаблонов, если js файл для приложения существует, оно дописывает вызов в хеадере… короче больно хитрая система)) Но мне нравится)) А главное никакой мороки с подключением) кинул в папку js файлик mawebapp.js и готово) при загрузке одноименного приложения оно будет подключаться.
          • 0
            … если не будет очепятки в имени.
    • –1
      >class My_Controller_Plugin_Webinit extends Zend_Controller_Plugin_Abstract

      Ужас…
      • +1
        Что испугало? Что Webinit лежит в library/My/Controller/Plugin/, аналогично Zend?
        • 0
          Вот это и «пугает» в ZF.
          • 0
            Никак в толк не возьму что вы имеете ввиду? Что тут ужасного?
            • 0
              У человека просто стереотипы. Прочитал в какой-нибудь умной книжке, что длинные имена это плохо и заучил это как непоколебимую истину, вместо того, чтобы своей головой подумать.
            • +1
              Достаточно человеку, знакомому с фреймворком, посмотреть на это объявление класса, и он поймет, что имел в виду разработчик кода, а также отчасти, чего ожидать от этого класса. Одной строкой. Страшно?
        • +1
          Тоже сталкивался с описанной проблемой. Подключал файлы вручную — пока такой подход устраивает, так как таких страниц штук 5-6.

          Настоящая проблема возникла, когда кода на javascript стало так много, что там тоже нужно было организовывать все как-то в соответствии с MVC. И универсального решения пока не нашел.
          А заголовок был многообещающий ("… MVC для Javascript ..."). (((
          • +1
            >javascript код максимально красивый при помощи библиотеки jQuery.
            Когда идея овладевает массами, она становится материальной силой (с)
            Но имхо если смотрите в сторону jQuery, м.б. имеет смысл уйти от программирования клиентской части на стороне сервера и заняться динамической загрузкой js-скриптов?
            Да и вообще конструкция
            $.data($.TRANSLATION, «Nice to see you!», '');
            уродская (извините уж). Имхо лучше воткнуть эти данные в како-нибудь другое место. Например в метатеги, имена которых вообще м.б. произвольными. А уже на клиенте разбирать эти данные тем же $(document).ready…
            • 0
              По поводу первой части — немного недопонял. Если можно, растолкуйте поподробнее, что от этого изменится.

              Конструкция и правда не блещет. Думаю, как организовать работу со словарем удобнее и так, чтобы не тормозить клиентскую часть.
              • 0
                >По поводу первой части — немного недопонял.
                Первая и вторая — общие. В идеале (так как я этот идеал понимаю) серверное и клиентсткое программирование должны быть максимально развязаны. В HTML выводится не чистый js а инструкции для js (метатеги, специфические id, class)/ Все остальное (включая подгрузку необходимых js-библиотек) должно выполняться на стороне клиента.
                С CSS конечно все сложнее.
                • 0
                  Первый шаг я сделал. В отличие от Fesor несколько лишних казалось бы телодвижений ради разграничения кода меня не пугают. По поводу метатегов с инструкциями для js подумаю, благо есть где применить, может быть получится решение поизящнее. Спасибо за идею.

                  В идеале (так как я этот идеал понимаю) серверное и клиентсткое программирование должны быть максимально развязаны.

                  Всеми своими ложноножками за.

                  С CSS конечно все сложнее.

                  При динамической подгрузке придется либо весь стиль максимально описывать, либо организовывать js-код так, чтобы при создании новых элементов по необходимости подключались нужные стили уже в клиентсткой части. Если, к примеру, модуль calendar.js использует calendar.css с известным путем, то сам модуль должен попытаться подключить таблицу стилей при инициализации. Я вижу это как-то так.
                  • 0
                    >Если, к примеру, модуль calendar.js использует calendar.css
                    Я про это и сказал, что здесь готовый рецепт нельзя придумать. Если календарь покажется после нажатия пользоватлем кнопки/ссылки тут все нормально А если он должен отображаться исходно, то наверное не очень здорово тормозить отображение страницы за счет динамически подгружаемых CSS.
            • 0
              Советую привязать файлы скриптов не по имени экшена, а по имень файла вида, ведь именно для него они и существуют. Помните, что разные экшены могут использвать один и тот же вид (например, форма редактирования и добавления записей).

              Жёстко подключая только один файл вы слегка облегчаете себе жизнь. Если страница имеет сложный клиентский интерфейс, тогда файлов по-хорошему будет много (склеивание в один, сжатие — это другой вопрос для production варианта). Идея высказанная выше про папку в таком случае намного лучше.
              • 0
                Спасибо, сразу об этом не подумал, следует немного видоизменить плагин. Потихоньку набираю мыслей на реализацию, попробую собрать все воедино и показать следующий вариант.
              • 0
                > defined('APPLICATION_PUBLIC_PATH') || define( 'APPLICATION_PUBLIC_FOLDER', dirname(__FILE__) );

                Вы тут проверяете наличие одной константы, а дефайн делаете другой
              • 0
                А вот интересно, путь, который предлагает ZendX_JQuery на практике не подходит для реальных проектов? Насколько я понимаю писать некоторый код можно непосредственно в контроллере на PHP, а не во view, что по-моему лучше для определенной логики (подготовки дынных для вывода), однако, в при таком подходе мы сильно ограничены запрограммированным в ZendX_JQuery функционалом. Кто-нибудь пользует?
                • 0
                  упс, не ожидал, что это все-таки опубликуется. Удалить как-то можно? Активность на хабре стал проявлять недавно, не все еще мне известно…
                  • 0
                    Удалить нельзя, пользуйтесь кнопочкой «Предпросмотр» перед отправкой.
                • +1
                  А вот интересно, путь, который предлагает ZendX_JQuery на практике не подходит для реальных проектов? Насколько я понимаю писать некоторый код можно непосредственно в контроллере на PHP, а не во view, что по-моему лучше для определенной логики. Плюс как раз решается проблема, которую отчасти пытался решить автор — подготовка данных к отображению из модели в определенном экшне. Однако, в при таком подходе мы сильно ограничены запрограммированным в ZendX_JQuery функционалом. Кто-нибудь пользует? Если да, то приходится ли дописывать javascript код руками?

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.