AJAX в Zend Framework

Издавно у меня выработалась привычка писать приложения так, чтобы они могли с лёгкостью работать и через AJAX и как его сейчас называют HMVC. При этом самое важное условие — изменения в коде контроллеров не должно быть вовсе или они должны быть минимальны. На самом деле будет написано об AJAH, потому что общение идёт через html, а не xml, но AJAX более привычное название, поэтому ниже буду использовать его.

Для начала нам нужен двухшаговая шаблониция, когда в начале генерируется основной контент, а затем он вставляется в мастер шаблон. В ZF для этого есть layouts. Подключаем их в конфиге
resources.layout.layoutPath = APPLICATION_PATH «/layouts/scripts/»
При этом будет по дефолту рентериться layout.phtml. У себя я практикую подмену мастер шаблона на ajax.tpl.php, в ZF это можно сделать через плагины или бутстрапы, но они меня пугают своей кривой документацией и примеры из мануала зачастую не работают. Можно использовать смену контекста AjaxContext, но опять же у меня возникли проблемы, да и это не очень мне нравится.  Поэтому, следуя принципу KISS, я решил наговнокодить прямо в основном лэйауте.

<?php /* @var $this Zend_View */ ?>
<?php $request = Zend_Controller_Front::getInstance()->getRequest();
if ($request->isXmlHttpRequest()) { ?>
    <?php echo $this->layout()->content; ?>
<?php } else { ?>
    <html>
        <head>
            <meta http-equiv="content-type" content="text/html; charset=utf-8" />
            <?php echo $this->jQuery(); ?>
            <script type="text/javascript">
                $(function() {
                    $("a.ajax").live('click', function() {
                        var conteiner = $(this).attr('ajax-container').toString();
        		if (!conteiner) {
        		    conteiner = '#container';
        		}
                        var $container = $( conteiner );
                        $container.load(this.href);
                        return false;
                    });
                    $dialog = $( "#dialog" );
                    $("a.ajax-modal").live('click', function() {
                        $dialog.hide().load(this.href).dialog({
                            width: 800,
                            position: 'top',
                            modal: true
                        });
                        return false;
                    });
                    $('#form-conteiner form').live('submit', function() {
                        $dialog.html('Загрузка...');
                        $.post(this.action, $(this).serialize(), function(data) {
                            $dialog.html(data);
                        });
                        return false;
                    });
                })
            </script>
        </head>
        <body>
            <div id="dialog" style="display: none;"></div>
            <section id="container">
                <?php
                $partial = array('menu.phtml', 'default');
                $this->navigation()->menu()->setPartial($partial);
                echo $this->navigation()->menu();
                ?>
                <?php echo $this->layout()->content; ?>
            </section>
        </body>
    </html>
<?php } ?>

Пройдёмся по коду. В начале шаблонов я пытаюсь организовать хоть какой автокомплит подсказав IDE что $this — это Zend_View и благодаря этому получил подсказки на публичные методы, хотя можно использовать protected и private, но на безрыбье и рак — рыба.

Затем спрашиваем у реквеста на AJAX ли запрос $request->isXmlHttpRequest(), благо это zf умеет, а фича определяется на основании заголовков.
Собственно для AJAX нам нужно выплюнуть тупо часть нагенерированную экшином <?php echo $this->layout()->content; ?>, а для обычных запросов отдаём как положено c head, меню и т.д.
На этом можно было бы заканчивать повествование, т.к. теперь всё можно запрашивать через AJAX, но раскажу о парочке строк js.

<?php echo $this->jQuery(); ?> — это подключение jQuery, для этого используется библиотека ZendX, которая является не обязательной, поэтому можно вручную прописать пути к jQuery и jQuery UI, это будет даже более KISS.

Теперь вешаем на ссылки с классом  «ajax» запросы AJAH. По умолчанию всё будет грузиться в div#conteiner, но для удобства можно прописывать другой контейнер в атрибуте ссылки  ajax-container, указав в нём jquery селектор, например, ajax-container=»#conteiner» или container=»#conteiner2″. Так же можно выводить в модальном окне, указав класс ajax-modal.

Формы я заварачиваю в div <div id=»form-conteiner»> и прописываю хук, чтобы он перехватывал обработчик отправки и отсылал форму AJAX-ом.

$('#form-conteiner form').live('submit', function() {
	$dialog.html('Загрузка...');
	$.post(this.action, $(this).serialize(), function(data) {
		$dialog.html(data);
	});
	return false;
});

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

<script>$('#dialog').dialog('close')</script>
<script>location.href='/new/url/'</script>

Я обычно не делаю формы на ajax. При том, что всё может работать на AJAX буквально одной строкой кода, но предпочитаю олдскульные перезагрузки, без геморроя с утечками, файлами, сохранением URL и history. Будь осторожны, не злоупотребляйте.

P.S. Очень хотелось бы услышать критику и советы как нужно делать. Потому как  я после выхода ZF 1.0 забил на него и лишь занимался доработкой проектов, а сейчас впервые начал делать с нуля и делаю всё по старинке, подходом который использую в своём фреймворке. Так что учите меня.

AJAX в Zend Framework: 5 комментариев

  1. iPrior

    Собсно как то мудрено сделано…
    можно все контроллеры наследовать от абстрактного, в котором в методе init будет проверятся AJAX-овый ли запрос, и при истине отключать layout, то есть выводить только вьюшки конкретного action:
    $this->_helper->layout()->disableLayout();

    То есть «тупо часть нагенерированную экшином «

    1. AmdY Автор записи

      Шикарно. Теоретически правильнее было бы сделать через AjaxContent, но Ваш способ удобнее и требует меньше кода и в контексте данной задачи подходит идеально.

      1. iPrior

        AjaxContent — это наверно ContextSwitch?
        Я, к сожалению, не смог связать его с маршрутизацией (route) в зенде.
        Получилось как то неудобно.
        Если мне нужно возвращать XML или HTML через AJAX запрос, при этом добавляя метку в адресную строку (format/xml), то этот же вариант пришлось прописывать и в маршрутизации и в самом контроллере.

        Я конечно не офигеть какой спец по зенду, но помучавшись и так и этак, учитывая что конкретной задачи ни когда нет и всегда приходится всё допиливать и переделывать, я нашел такой выход из ситуации, как абстрактный класс и отключение лайоута.
        Для AJAX запросов, где возвращаются данные в виде JSON или XML — я делаю отдельный контроллер, скажем AjaxController.
        А ХТМЛ можно и так гонять

    1. AmdY Автор записи

      Вы неверно поняли, HMVC — это рекурсивный mvc. Допустим у нас есть страница, всё по MVC, а во вьюхе мы подключаем другой компонент как в примере echo $this->navigation()->menu();
      Соотвественно и этот компонент navigation работает имеет свои контроллер, вьюху и т.д.

      За счёт такой структуры мы можем вызывать компонент как внутри той же вьюхи, так и рендерить отдельно, например, подтягивая блок через ajax или собирая страницу с помощью SSI или ESI, что удобно при агрессивном кешировании.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *