Zend Layout и ajax приложение просмотра погоды

Мы пишем программы, чтобы облегчить жизнь рядовым пользователям. Однако какой бы уникальной ни была ваша программа, не последнюю роль играет ее внешний вид, т.е. графический интерфейс. Дизайнеры и специалисты по построению UI (user interface) тратят много сил, чтобы создать понятную и удобную «обертку» для программы. Однако за программистами остается
работа по созданию инструмента, который бы позволил легко настраивать внешний вид, и не последнюю роль в этом процессе играют шаблоны. Поэтому очень важно использовать простые и
понятные методики использования шаблонов, чтобы другие люди с легкостью могли настраивать внешний вид как они сами того пожелают. Zend Framework предлагает два компонента
для управления представлением программы — Zend_View и Zend_Layout. Сегодня мы рассмотрим лишь один из них, а именно  Zend_Layout.

Zend_Layout является своего рода макетом страницы, он представляет собой каркас, фундамент вашего представления. Именно в своем layout вы определяете доктайп, стили и внешние подключаемые файлы в секции head, разбиваете страницу на различные блоки, чтобы потом можно было отрендерить их, используя вспомогательные шаблоны или снипеты. Таким образом ваша HTML страница изначально будет строиться с использованием макета layout. Zend_Layout реализует паттерн двухшаговая шаблонизация. Т.е. сначала вы представляете данные в виде некой логической структуры, а затем используете необходимое форматирование данных, чтобы представить их пользователю. По умолчанию Zend Framework предлагает хранить ваши макеты в папке layouts/scripts, которая находится в папке application вашего приложения.

Основы использования Zend_Layout

Возможно, вы заметили, что в самом начале, когда мы воспользовались утилитой zf для создания нашего первого приложения на Zend Framework, исходный код не являлся валидной HTML страницей, т.е. отсутствовали тэги html и body в частности. Мы ведь не указали свой собственный макет для страницы, а использовали лишь снипет index.phtml. Так как же Zend Framework может узнать, где искать макеты для страниц?

Это можно сделать несколькими способами:

a) Создать массив с настройками, содержащими путь и названия макета, а затем передать этот массив объекту Zend_Layout

[php]
$options = array(
‘layout’ => ‘foo’,
‘layoutPath’ => ‘/path/to/layouts’,
‘contentKey’ => ‘CONTENT’,
);

$layout = new Zend_Layout($options);
// или так:
$layout = Zend_Layout::startMvc($options);

[/php]

б) Использовать конфигурационный файл

[php]
// layout.ini

[layout]
layout = "foo"
layoutPath = "/path/to/layouts"
contentKey = "CONTENT"

$config = new Zend_Config_Ini(‘/path/to/layout.ini’, ‘layout’);
// передаем конструктору объект Zend_Config:
$layout = new Zend_Layout($config);
// можно сделать и так:
$layout = Zend_Layout::startMvc($config);

[/php]

в) Использовать аксессоры

[php]
$layout->setLayout(‘foo’)
->setLayoutPath(‘/path/to/layouts’)
->setContentKey(‘CONTENT’);
[/php]

Zend_Layout и application.ini

Я предпочитаю указывать макеты в конфигурационных файлах. Конфигурационные файлы по умолчанию находятся в папке application/configs.
В индексном файле, если помните, мы определили application.ini и передали его в качестве параметра конструктору Zend_Application. Теперь нам необходимо добавить в конфигурационный файл следующие строчки:

[php]
resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"
resources.layout.layout = "layout"
[/php]

В итоге application.ini примет следующий вид:

[php]
[production]
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
includePaths.library = APPLICATION_PATH "/../library"
bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
bootstrap.class = "Bootstrap"
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"

resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"
resources.layout.layout = "layout"

[staging : production]

[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1

[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
[/php]

Теперь загрузчик Zend Framework знает, что мы хотим использовать папку layouts/scripts для своих макетов, и что макет layout.phtml будет нашим основным макетом.
Создадим макет layout.phtml в папке application/layouts/scripts нашего приложения:

[html]
<html>
<head>
<title>
My Zend Framework Application
</title>
</head>
<body>
<?=$this->layout()->content;?>
</body>
</html>
[/html]

Конструкция $this->layout() возвращает объект слоя, а content является просто переменной по умолчанию, которая включает в себя содержимое выводимого блока. В дальнейшем вы будете указывать свои собственные переменные, которые отвечают за навигацию, меню, либо блок какой-либо информации, но пока у нас есть лишь одна переменная content.

Сохраним файл и откроем индексную страницу нашего проекта в браузере. Вроде бы ничего не изменилось, однако если посмотреть на исходный код страницы, то мы увидим то, что мы только что прописали в файле layout.phtml.

Доступ к объекту layout.

Теперь, создавая свои шаблоны в папке views, вместо

[php]
<?=$this->layout()->content;?>
[/php]

будет подставляться содержимое ваших шаблонов. Попробуйте создать какой-нибудь контроллер со своим шаблоном для действия и убедиться в этом.
Доступ к объекту слоя посредством

[php]
$this->layout()
[/php]

является не единственным способом. Доступ можно получить через контроллер

[php]
$layout = $this->_helper->layout();
[/php]

используя статический метод   getMvcInstance()

[php]
$layout = Zend_Layout::getMvcInstance();
[/php]

через загрузчик

[php]
$layout = $bootstrap->getResource(‘Layout’);
[/php]

AJAX приложение просмотра погоды

Теперь когда мы знаем как включать макеты в конечное представление страницы, мы научимся их отключать ;). Забегая вперед, скажу, что это реализуется методом disableLayout().

В большинстве случаев, наверно, нам придется создавать различные страницы (новости, фотогалереи, каталог и пр.), поэтому так или иначе нам придется работать с макетами. Однако макет может оказаться ненужным, когда мы имеем дело с ajax приложениями. Ведь часто посылают лишь запрос контроллеру, а в ответе нужно получить данные без всякого форматирования страницы.

Вот здесь и может понадобиться решение отказаться от макетов. Но как это сделать, если все запросы транслируются индексному файлу, тот подключает загрузчик, а последний в свою очередь запускает все приложение, инициализируя Zend_Layout?

Рассмотрим на примере, как этого можно добиться.
Итак создадим приложение, которое будет выводить погоду нескольких городов у нас на странице.
Откроем наш макет layout.phtml и подключим jquery, который будем использовать для облегчения написания ajax приложения.
Добавьте в секцию head следующий код:

[php]
<?php
$this->headScript()->appendFile(‘http://www.google.com/jsapi’);
echo $this->headScript();
?>
<script type="text/javascript">
google.load("jquery", "1.3.1");
</script>
[/php]

Здесь мы воспользовались сервисом от гугла jsapi, загрузили jquery версии 1.3.1. Можно просто скачать jquery, положить в какую-нибудь папку и подключить его аналогичным образом.
Далее напишем функцию, которая будет посылать запрос другому скрипту, обрабатывать ответ и выводить результаты.
В IndexController добавим действие search, где запрос будет перенаправляться gismeteo, чтобы узнать погоду выбранного города, а затем будет выводиться результат.

На странице http://informer.gismeteo.ru/getcode/xml.php?id=27612 дано описание формата для выдачи. Выберем три города: Москва, Берлин, Вашингтон. В строке запросе меняться
будут только идентификаторы выбранных городов, поэтому наш селект бокс будет выглядеть следующим образом:

[html]
<select name="q" id="q">
<option value="27612">Москва</option>
<option value="10381">Берлин</option>
<option value="72403">Вашингтон</option>
</select>
[/html]

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

Итак, у нас есть три города, погоду для которых мы хотим узнать, формат выдачи данных о погоде. Теперь осталось написать запрос к сайту gismeteo, чтобы тот вернул нам
запрашиваемые данные.

Запрос к сервису с погодой.

В файл views/scripts/index/index.phtml добавим такой код:

[html]
<select name="q" id="q">
<option value="27612">Москва</option>
<option value="10381">Берлин</option>
<option value="72403">Вашингтон</option>
</select>
<input type="button" id="search" value="search" onclick="search()" />

<dl id="search-results">

</dl>

<script type="text/javascript">
function search()
{
$.post("/search", {q:$("#q").val()}, function(response) {
// process data
var forecast = response.MMWEATHER.REPORT.TOWN.FORECAST;
for(var i=0;i<forecast.length;i++) {
$("#search-results").append("<dt>" + forecast[i]["@attributes"].day +
"." + forecast[i]["@attributes"].month +
"." + forecast[i]["@attributes"].year +
", Hour: " + forecast[i]["@attributes"].hour + "</dt>");
$("#search-results").append("<dd>давление: max " + forecast[i].PRESSURE["@attributes"].max +
", min: " + forecast[i].PRESSURE["@attributes"].min +
" : температура — max: " + forecast[i].TEMPERATURE["@attributes"].max +
" min: " + forecast[i].TEMPERATURE["@attributes"].min + "</dd>");
}
}, ‘json’);
}
</script>
[/html]

У нас имеется селект бокс, кнопка поиска и поле вывода результатов. Сама функция поиска посылает запрос на адрес /search, получает данные в json формате и выводит их в указанном месте.
Если внимательно посмотреть на возвращаемые результаты в формате XML, то бует видно, что сервис возвращает данные, которое содержатся в атрибутах специфичных тэгов (TEMPERATURE, PRESSURE и т.д.), поэтому для доступа к этим атрибутам мы используем конструкцию вида

[html]
<pre>forecast[i]["@attributes"]</pre>
[/html]

Это становится возможным, так как мы воспользуемся компонентом Zend_Json, но об этом чуть ниже.

Однако если сейчас попытаться нажать на кнопку, то можно увидеть (если вы используете firebug или аналогичный отладчик), что запрос вернет 404 ошибку, ведь страницы /search у нас еще не существует.
Мы не будем создавать дополнительный контроллер search, вместо этого в индексном контроллере добавим еще одно действие searchAction(). Для того чтобы запрос был обработан этим действием нам необходимо указать маршрут посредством Zend_Controller_Router_Route. Вы можете посмотреть примеры стандартного маршрутизатора для более ясной картины, как это
работает. Сейчас же мы просто добавим маршрут в метод _initRoutes() нашего загрузчика. Добавляем _initRoutes() в application/Bootstrap.php:

[php]
protected function _initRoutes()
{
$router = Zend_Controller_Front::getInstance()->getRouter();
$router->addRoute(
‘search’,
new Zend_Controller_Router_Route("/search", array("controller" => "index", "action" => "search"))
);
}
[/php]

Теперь если перейти на страницу /search будет вызвано действие search контроллера index. Последнее что нам осталось, это реализовать действие searchAction в индексном контроллере.

Вот здесь нам как раз и понадобится отключить layout для страницы, вы ведь еще не забыли про disableLayout()? 😉
Ведь если этого не сделать нам просто придет ответ в виде HTML страницы, а нам надо получить лишь данные в формате JSON. Нам также не нужно рендерить шаблоны.

Все это делается двумя строчками кода:

[php]
public function searchAction()
{
$this->_helper->viewRenderer->setNoRender();
$this->_helper->getHelper("layout")->disableLayout();
}
[/php]

Осталось лишь отправить запрос сервису с погодой. В Zend Framework есть очень удобный компонент для работы с HTTP запросами Zend_Http_Client. Его-то мы и будем использовать для получения данных с удаленного сайта. Сейчас я не буду рассматривать работу этого компонента, все что нам нужно — это три метода setUri(), request(), getBody():

[php]
$q = $this->_getParam("q");

$client = new Zend_Http_Client();
$client->setUri("http://informer.gismeteo.ru/xml/" . $q . "_1.xml");
echo $client->request("GET")->getBody();
[/php]

Параметр q приходит к нам в запросе, который посылает функция search(). Далее лишь создается объект Zend_Http_Client и посылается запрос на указанный URL; больше нам от этого объекта ничего не понадобится 😉

Если все оставить как есть, то мы получим данные в XML формате, можно, конечно, и так поступить, но мне лично удобнее работать с JSON, поэтому мы сконвертируем XML в JSON при помощи Zend_JSON. В итоге наш метод searchAction() примет окончательный вид:

[php]
public function searchAction()
{
$this->_helper->viewRenderer->setNoRender();
$this->_helper->getHelper("layout")->disableLayout();
$q = $this->_getParam("q");

$client = new Zend_Http_Client();
$client->setUri("http://informer.gismeteo.ru/xml/" . $q . "_1.xml");
$jsContents = Zend_Json::fromXml($client->request("GET")->getBody(), false);
echo $jsContents;
}
[/php]

Теперь все готово, и если нажать на кнопку поиска, то, подождав, вы получите погоду для выбранного города. Добавим небольшое украшение, крутящийся кружочек во времся аякс-запроса, чтобы придать большую интерактивность нашему приложению.

Для генерации различных изображений ajax загрузки я использую сервис ajaxload.info.
В шаблон index.phtml добавим span бокс, где будет выводиться картинка ожидания,

[html]
<span id="wait-box"></span>
[/html]

и саму картинку в секцию javascript

[html]
var ajaxImg = new Image();
ajaxImg.src = ‘/images/ajax-loader.gif’;
[/html]

При нажатии на кнопку будет показываться картинка ожидания, а когда будет получен ответ от удаленного сервера, эту картинку скроем. Ну, и добавим название города для которого будет выводиться погода в результат вывода:

[html]
function search()
{
$("#wait-box").append(ajaxImg);
var city = $("#q option:selected").text();
$.post("/search", {q:$("#q").val()}, function(response) {
// process data
var forecast = response.MMWEATHER.REPORT.TOWN.FORECAST;
for(var i=0;i<forecast.length;i++) {
$("#search-results").append("<dt><b>Погода для г. " + city + "</b> на " + forecast[i]["@attributes"].day +
"." + forecast[i]["@attributes"].month +
"." + forecast[i]["@attributes"].year +
", Hour: " + forecast[i]["@attributes"].hour + "</dt>");
$("#search-results").append("<dd>давление: max " + forecast[i].PRESSURE["@attributes"].max +
", min: " + forecast[i].PRESSURE["@attributes"].min +
" : температура — max: " + forecast[i].TEMPERATURE["@attributes"].max +
" min: " + forecast[i].TEMPERATURE["@attributes"].min + "</dd>");
}
$("#wait-box").empty();
}, ‘json’);
}
[/html]

Вот и все, наше приложения оказалось не таким уж и сложным, а результат можно увидеть на картинке 😉

Скачать весь проект можно по следующей ссылке zf.tar.

P.S. Закончен перевод 5-й главы документации Zend Framework Плагины в Zend Framework.

3 мысли о “Zend Layout и ajax приложение просмотра погоды

  • 17 июня 2011 в 15:38
    Permalink

    Если внимательно посмотреть на возвращаемые результаты в формате XML, то [b]бует[/b] видно, что сервис возвращает данные, [b]которое[/b] содержатся в атрибутах специфичных тэгов (TEMPERATURE, PRESSURE и т.д.), поэтому для доступа к этим атрибутам мы используем конструкцию вида

  • 13 октября 2012 в 18:33
    Permalink

    Как «указывать свои собственные переменные, которые отвечают за навигацию, меню, либо блок какой-либо информации»?

  • 18 октября 2012 в 11:22
    Permalink

    Не совсем понял вопрос. Поясните, пожалуйста, что хотите сделать.

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

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