Издавно у меня выработалась привычка писать приложения так, чтобы они могли с лёгкостью работать и через 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 забил на него и лишь занимался доработкой проектов, а сейчас впервые начал делать с нуля и делаю всё по старинке, подходом который использую в своём фреймворке. Так что учите меня.
Собсно как то мудрено сделано…
можно все контроллеры наследовать от абстрактного, в котором в методе init будет проверятся AJAX-овый ли запрос, и при истине отключать layout, то есть выводить только вьюшки конкретного action:
$this->_helper->layout()->disableLayout();
То есть «тупо часть нагенерированную экшином «
Шикарно. Теоретически правильнее было бы сделать через AjaxContent, но Ваш способ удобнее и требует меньше кода и в контексте данной задачи подходит идеально.
AjaxContent — это наверно ContextSwitch?
Я, к сожалению, не смог связать его с маршрутизацией (route) в зенде.
Получилось как то неудобно.
Если мне нужно возвращать XML или HTML через AJAX запрос, при этом добавляя метку в адресную строку (format/xml), то этот же вариант пришлось прописывать и в маршрутизации и в самом контроллере.
Я конечно не офигеть какой спец по зенду, но помучавшись и так и этак, учитывая что конкретной задачи ни когда нет и всегда приходится всё допиливать и переделывать, я нашел такой выход из ситуации, как абстрактный класс и отключение лайоута.
Для AJAX запросов, где возвращаются данные в виде JSON или XML — я делаю отдельный контроллер, скажем AjaxController.
А ХТМЛ можно и так гонять
Здравствуйте! Почему Ajax называют HMVC? Спасибо!
Вы неверно поняли, HMVC — это рекурсивный mvc. Допустим у нас есть страница, всё по MVC, а во вьюхе мы подключаем другой компонент как в примере echo $this->navigation()->menu();
Соотвественно и этот компонент navigation работает имеет свои контроллер, вьюху и т.д.
За счёт такой структуры мы можем вызывать компонент как внутри той же вьюхи, так и рендерить отдельно, например, подтягивая блок через ajax или собирая страницу с помощью SSI или ESI, что удобно при агрессивном кешировании.