Доброго всем времени суток.
Сейчас я поведаю вам о том, как лучше и правильнее интегрировать AJAX в Zend Framework, а точнее в его MVC-компоненты. Что такое AJAX я вам рассказывать не буду, об этом в рунете очень много достойных материалов. Также не буду рассказывать основы Zend Framework – об этом лучше почитать в оффициальном руководстве.
Для реализации клиентской части скрипта мы будем использовать javascript-фреймворк jQuery. На момент написания статьи последняя версия – jQuery 1.2.3, именно её мы будем применять.
Теперь немного о манере повествования. Рассказывать я буду на примере формы авторизации, проверяющей введённые логин и пароль без перезагрузки страницы. Форма будет работоспособна и в браузере с отключенным JavaScript’ом.
Часть 0. Введение.
Для начала ответим на один вопрос: чем отличается AJAX-запрос от обычной загрузки страницы? Теоретически ничем, те же заголовки запроса и ответа, посылаемые POST или GET данные и выводимый результат. На практике же отличия есть, пусть и не слишком велики. В частности, при использовании MVC-компонентов приходится или подключать альтернативный модуль вывода данных (к примеру, чтобы возвращать данные в XML или JSON формате), или же использовать альтернативные шаблоны для вывода не полной страницы, а её части (например, для обновления блока на странице).
Как мы видим, различия есть. Но теперь перед нами стоит проблема – как определить, что это именно AJAX-запрос, чтобы отдать ему нужный шаблон? Вот что сказано по этому поводу в документации:
Zend_Controller_Request_Http имеет зачаточный метод для определения запросов AJAX: isXmlHttpRequest(). Этот метод проверяет наличие заголовка HTTP-запроса X-Requested-With со значением ‘XMLHttpRequest’. Если он найден, то возвращается true.
На данный момент известно, что этот заголовок по умолчанию отправляется следующими JS-библиотеками:
- Prototype/Scriptaculous (и библиотеки, производные от Prototype)
- Yahoo! UI Library
- jQuery
- MochiKit
Большинство AJAX-библиотек позволяет отправлять произвольные заголовки HTTP-запросов. Если ваша библиотека не отправляет этот заголовок, то просто добавьте его в качестве заголовка ответа, чтобы быть уверенным в том, что метод isXmlHttpRequest() работает для вас.
Приведу пример использования этого метода для отключения автоматического рендеринга шаблонов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class IndexController extends Zend_Controller_Action { public function init() { //проверяем, используется ли AJAX if ($this->getRequest()->isXmlHttpRequest()) { //если AJAX - отключаем авторендеринг шаблонов Zend_Controller_Action_HelperBroker::removeHelper('viewRenderer'); } } public function indexAction() { $this->view->string = 'Hello, World!'; } } |
На этом мы закончим с введением и перейдём к рассмотрению конкретных приёмов работы.
Часть 1. Традиционный подход.
Суть этого подхода заключается в отключении автоматического рендеринга и ручном выводе необходимых данных в нужном формате. “Традиционным” я назвал его потому, что этот подход был освещён ещё в далёком 2006 году в статье Adding interactivity with Ajax and JSON, опубликованной в DevZone IBM. На данный момент статья устарела, но подход, предложенный в ней, до сих многие видят наиболее логичным способом интеграции AJAX.
Приведу пример использования:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class IndexController extends Zend_Controller_Action { public function init() { //проверяем, используется ли AJAX if ($this->getRequest()->isXmlHttpRequest()) { //если AJAX - отключаем авторендеринг шаблонов Zend_Controller_Action_HelperBroker::removeHelper('viewRenderer'); } } public function indexAction() { //самый обыкновенный action $this->view->string = 'Hello, World!'; } public function getAnythingAjaxAction() { //а это уже ajax-действие //$result - массив некоторых данных, которые нам необходимо вернуть в браузер $output = Zend_Json::encode($result); $response = $this->getResponse(); $response()->setBody($output) ->setHeader('content-type', 'application/json', true); } } |
Как видите, в случае со специально преднозначенным для ajax методом мы передаём сериализованные json-ом данные в объект ответа для вывода. В дальнейшем они передаются в браузер и javascript производит с ними все необходимые действия. Также в объект ответа мы передаём заголовок “content-type: application/json”, чтобы браузер распознал тип возвращаемых данных (не является обязательным, но это хороший тон).
Надеюсь суть подхода вам ясна, перейдём к построению нашей формы авторизации.
Для начала, создадим шаблон:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript"> function submitform(login, password) { //посылаем наш аякс-запрос $.getJSON( '/login', { login: login, password: password }, function(results) { if (results.result == 'success') { //если авторизация прошла успешно - редирект window.location = '/'; } else { //иначе - выводим ошибки $('#errors').html($.makeArray(results.errors).join('<br />')); } } ); } </script> <div id="errors" class="errors"> <?php if ($this->errors) { foreach ($this->errors as $error) { echo $error . '<br />'; } } ?> </div> <form method="post" action="" onsubmit="submitform(getElementById('login').value, getElementById('password').value); return false;"> <dl> <dt>Логин</dt> <dd><input type="text" id="login" name="login" value="<?php echo @$login; ?>" /></dd> <dt>Пароль</dt> <dd><input type="password" id="password" name="password" value="<?php echo @$password; ?>" /></dd> <dd><input type="submit" value="submit" /></dd> </dl> </form> |
Теперь – код нашего действия:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public function loginAction() { //проверяем, были ли посланы логин и пароль if (($login = $this->_getParam('login')) && ($password = $this->_getParam('password'))) { //наш воображаемый класс авторизации $loginClass = new loginClass($login, $password); //проверяем валидность $result = $loginClass->isValid() ? 'success' : 'error'; //если авторизация прошла успешно - в errors записываем пустой массив //иначе - получаем сообщения ошибкок $errors = 'success' == $result ? array() : $loginClass->getErrors(); if ($this->getRequest()->isXmlHttpRequest()) { //если ajax - выводим данные на экран в json-формате echo Zend_Json::encode(array('result' => $result, 'errors' => $errors)); } else { //если не аякс if ('success' == $result) { //авторизация успешна - делаем редирект $this->_redirect('/'); } else { //ошибка - передаём результаты в шаблон $this->view->errors = $errors; } } } } |
В методе init() нашего контроллера мы отключаем автоматический рендеринг шаблонов в случае, если запрос был послан через ajax (об этом мы говорили выше). Остальное, думаю, будет ясно из кода – ничего сложного здесь нет. В результате мы получаем форму авторизации без лишних перезагрузок страницы – очень удобно и юзабельно.
Продолжение статьи
Если что-нибудь неясно – спрашивайте в комментариях.
Спасибо за внимание, до скорых встреч.
Evgenii.S.Semenchuk says:
Оч познавательный пост. Большое спасибо…
22 мая 2008, 10:56Пойду займусь немного рефакторингом
admin says:
2 Evgenii.S.Semenchuk
22 мая 2008, 11:16Лучше пока не надо
В ближайшие дни планирую написать ещё 2 статьи на эту тему – интеграция c Zend_Form и использование хэлпера AjaxContext. Без них рассмотрена лишь верхушка айсберга.
Igor says:
Такие классные посты, что прям нетерпится почитать продолжение.
11 июня 2008, 17:48Rauan says:
В данном подходе есть один маленький минус. В плане удобства лучше не найти, но от этого страдает производительность.
Вот если бы можно было как-то отделить ajax от основной части…
16 июня 2008, 15:54admin says:
2 Rauan:
16 июня 2008, 15:58Не совсем понял. Вы предлагаете вынести ajax-функционал из контроллера?
Rauan says:
Что-то вроде того.
Раньше я “делал” Ajax в контроллере. Но это очень долго, например для autocomplete-ов. Потом пришлось переводить в plain php, совсем неудобно, но хорошо для производительности.
18 июня 2008, 12:14Stanislav says:
Здравствуйте, admin
Пытался сделать этот урок. Но не получилось что-то с $.getJSON. То есть при нажатии кнопки ничего не происходит, хотя никаких ошибок FF не выдает… Наверное что-то делаю не правильно.
А вот попробовал сделать с JsHttpRequest – всё ОК. С ним я работал и до начала освоения ZF.
Не ли у Вас готового архива с рабочим кодом ( чтобы осталось только ZF добавить ) от этой статьи? Если есть, вышлите на емаил. Спасибо.
ps/ Начал осваивать ZF, и начал с Вашего сайта. Спасибо за сайт.
23 октября 2008, 17:46Сергей Митрошин says:
Станислав, благодаря таким комментариям, как ваш, и появляются силы для продолжения наполнения сайта. Времени это отнимает довольно много.
23 октября 2008, 18:18Архива, к сожалению, нет, статья писалась довольно давно.
Всё же цель статьи не в том, чтобы в итоге получить работающий код, а чтобы объяснить принципы работы.
Stanislav says:
Ясно, спасибо за ответ! Попробую ещё раз разобраться!
23 октября 2008, 19:06Stanislav says:
Всё, вроде разобрался. Ещё раз спасибо за статью!
23 октября 2008, 19:58Буду приступать ко второй части.
Hinikato says:
Небольшое дополнение: лучше выводить данные не Zend_Json::encode(), а с помощью Action-хелпера Zend_Controller_Action_Helper_Json. Пример того как это делается:
10 ноября 2008, 16:44$this->_helper->json($data);
тогда вообще отключать не надо ничего, т. к. скрипт выведет JSON-данные и завершит работу по exit() (см. код этого хелпера).
Т. е. работа с выводом JSON вообще сводится к одной строке.
Сергей Митрошин says:
2 Hinikato:
10 ноября 2008, 16:57Если хэлпер вызывает exit(), то я его использовать не рекомендую – это противоречит теории MVC. В данном случае больше подошёл бы код:
$this->getResponse()->setBody(Zend_Json::encode(…));
Как считаешь?
Hinikato says:
Ну не знаю, я его использую и пока доволен. В чем противоречие? Что нет вида? Ну его же делали тоже люди знакомые с MVC, поэтому я думаю перед тем как пихать в ZF, они подумали об этом. Я не вижу противоречия…
11 ноября 2008, 2:38Сергей Митрошин says:
Нет вида – это стандартная ситуация. Меня смущает то, что выполнение скрипта прекращается.
11 ноября 2008, 10:18Например, мы отдаём контент, сжатый через gzip. Сжатие поисходит после цикла диспетчеризации. Если мы прервём скрипт во время диспетчеризации, то сжатие не будет выполнено.
Hinikato says:
Ну такая ситуация возможна, но как правило через JSON пересылают не тонны текста. При использовании хелпера скорее всего придется пожертвовать сжатием ради удобства или написать свой хелпер, который будет дополнительно сжимать через gzip закодированные JSON данные.
И еще у стандартного хелпера есть две опции, которые настраивают то, будет ли производиться выход по exit() и будут ли использоваться Layout-ы.
Если не надо выходить по exit(), а возвращать контент, то тогда вторым параметром надо передать false:
$data = $this->_helper->json($data, false).
Если надо использовать Layout-ы при отдаче контента, то тогда третьим параметром надо передать true:
$data = $this->_helper->json($data, false, true);
или
$this->_helper->json($data, true, true);
Обычно я не использую только первый параметр и отправляю данные сразу.
Но лучше скорее всего вообще будет не привязываться к тому откуда поступают данные.
11 ноября 2008, 16:53Сергей Митрошин says:
>Ну такая ситуация возможна, но как правило через JSON пересылают не тонны текста.
11 ноября 2008, 16:59Сжатие я привёл в качестве примера, ситуаций может быть множество.
Спасибо за разъяснения, надо будет подробнее ознакомиться с этим хэлпером.
Сергей Митрошин says:
Я посмотрел Zend_Controller_Action_Helper_Json. Достаточно установить public-свойство $suppressExit в true, и exit вызываться не будет. Остальное как я и хотел: отключение layout’а и viewRenderer’а, JSON передаётся в объект ответа, а не напрямую выводится. В общем, замечательно.
11 ноября 2008, 18:42Фома says:
Добрый день. Вопрос про AJAX. Что вы используете для того что бы с помощью технологии AJAX вернуть в JS – куски HTML ? Куда вы их зашиваете ?
Например я использую разделители.
Например $ответ = HTML1 . ” . HTML2 . ” . HTML3 и т.д.
Затем в JS разбиваю строку по разделителю и получаю массив HTML’ок затем вставляю каждую куда мне нужно.
Наверно это не хорошо. Но другого способа не вижу. Отзовитесь пожалуйста!
6 марта 2009, 11:58Использовать XML или JSON – мне кажется там будут проблемы.
Например неправильно составлен XML либо проблемы с eval в JSON.
Может я чего-то не знаю …
Сергей Митрошин says:
Куски HTML получать аяксом – не наш путь. Не советую так делать.
6 марта 2009, 17:23Фома says:
Спасибо! Т.е. вы вообще не работаете с AJAH и AHAH ?
7 марта 2009, 10:03Или 1 кусочек это максимум …
Сергей Митрошин says:
Да, предпочитаю одним запросом возвращать 1 кусок HTML.
7 марта 2009, 13:09Как вариант, можно возвращать куски HTML JSON’ом. Если правильно экранировать – проблем не будет.
Фома says:
Да. Хорошо. Спасибо. Вчера попробовал. Но мне кажется ZendFramework за меня все экранировал. Я использовал $json = Zend_Json::encode($response); В CodeIgniter такое не входит.
7 марта 2009, 18:18Snowcore says:
При составлении URL вручную (‘login=’ + login + ‘&password=’ + password)
я бы советовал для каждого параметра применить javascript функцию encodeURIComponent:
‘login=’ + encodeURIComponent(login) + ‘&password=’ + encodeURIComponent(password)
7 мая 2009, 15:55Сергей Митрошин says:
2 Snowcore:
7 мая 2009, 20:58Спасибо за поправку, внёс изменения. Теперь в примере передаётся объект, jQuery сам экранирует.
Антон says:
Извините Сергей,а не могли бы вы для Нуба объяснить, как сделать запрос при помощи Ajax в дб
4 октября 2009, 16:14Сергей Митрошин says:
Антон, запрос делается не в БД, а на определённый PHP-файл, который уже производит необходимые операции с БД.
4 октября 2009, 16:45Антон says:
ага …спасибо! ….я как раз начал читать статью по технологии Ajax и до меня стало доходить, что я сказал глупость
… спасибо!
4 октября 2009, 17:00Сергей Митрошин says:
Не за что
4 октября 2009, 17:29Анон says:
Хе опять Я
…меня наверное скоро прирежут за глупые вопросы….но я уже туго соображаю к вечеру, вот вопросик, постараюсь внятно объяснить:
) …
Вот в IndexController описываем запрос $.ajax({…})
и возникает 2 вопроса,где писать сам файл, на котором будет обрабатываться запрос (в какой папке),
и как при помощи jquery проверять текс на валидность
спасибо!
5 октября 2009, 22:47Антон says:
Я уже даже имя своё правильно написать не могу …ааааа все пора делать отдых
..
5 октября 2009, 23:01Антон says:
Ну что то у меня на свежую голову все равно не думается
…
…
6 октября 2009, 9:42Вот Сергей подскажите,
Если допустим брать ваш пример когда добавляли альбомы, допустим,
в IndexController создать экземпляр класса формы в низу под выводом дисков,
далее я должен вместо строк где мы проверяем на то, пришла ли форма из пост создать $.ajax запрос ну или Json, в котором указать путь до (вот тут не пойму) AlbumForm ? например, а потом в ответе обработать на валидность, фактически так же как это было в методе addAction?
Если я абсолютно не прав подскажите пожалуйста
Сергей Митрошин says:
Антон, в самом простом варианте аякс-запроса вы можете просто выводить HTML-код формы с уже выведенными ошибками валидатора, и при помощи javascript вставлять её в блок, заменяя текущую форму.
6 октября 2009, 17:59Marat says:
А почему бы не отрубать только лэйаут?
11 марта 2010, 11:28if ($this->getRequest()->isXmlHttpRequest()) {
$this->_helper->layout->disableLayout();
}
Oldman says:
Вопросы автору:
1) статья писалась давно, актуальна на сегодняшний день (для текущей версии ZF)?
2) На какой версии тестировали примеры?
3) в офф. документации не нашел ничего про $.getJSON и isXmlHttpRequest это актуально сейчас ? Просто начал смотреть на зенд.ком доки и там ничего похожего.
http://framework.zend.com/manual/en/zendx.jquery.html
Ваш пример не заработал, почему понять не могу.
4) если jquery-1.5.min.js вместо jquery.js должно работать?
5) Есть ли смысл читать вторую и третьи части сейчас?
13 февраля 2011, 1:29Oldman says:
На вопросы из предыдущего поста можно не отвечать. Разобрался.
16 февраля 2011, 4:49Изначально не хватало знаний по JS. Очень помогла статья в освоении, автору спасибо.
Shamann says:
Здравствуйте!
У меня пример не работает, пока не могу понять почему – какой-то косяк с $.getJSON – ничего не происходит, серверной части управление не передается.
Oldman, можешь подсказать, что ты сделал, чтоб у тебя заработало? Или, еще лучше, если можно, скинь архив на почту (shamann-gms@rambler.ru).
Спасибо большое.
23 февраля 2011, 3:09Oldman says:
Советую сначала разобраться с JavaScript.
А потом уже смотреть применительно к Zend’у
Я смотрел серию статей (8 штук) http://anton.shevchuk.name/javascript/jquery-for-beginners/
Советую, очень доходчиво изложено. В частности в третей части там описано как работает $.getJSON
Пример в этой статье и не должен работать, смотрите внимательно код, он не до конца дописан.
24 февраля 2011, 7:26public function getAnythingAjaxAction() {
//а это уже ajax-действие
//$result – массив некоторых данных, которые нам необходимо вернуть в браузер
$output = Zend_Json::encode($result);
$response = $this->getResponse();
$response()->setBody($output)
->setHeader(‘content-type’, ‘application/json’, true);
}
Shamann says:
Спасибо, Oldman. На эти статьи уже натыкался, но тем не менее спасибо. Все получилось, но я использовал не $.GetJSON, а $.ajax.
24 февраля 2011, 10:28Charlie says:
> Да, предпочитаю одним запросом возвращать 1 кусок HTML.
> Как вариант, можно возвращать куски HTML JSON’ом. Если правильно экранировать – проблем не будет.
Не нужно так делать. В принципе нежелательно гонять асинхронно куски HTML. Если уж приходится – гоняйте без JSON, JSON предназначен для кодирования данных, а не представления.
14 апреля 2011, 10:52Swapf says:
Никак не могу разобраться с интеграцией AJAX в ZF, вроде все делаю правильно. Если кто может – скиньте пример работающего проекта. Буду очень-очень благодарен.
Swapf@mail.ru
25 апреля 2011, 21:18