← Назад к статьям

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

M
miholeus
19 апреля 2010 г. · 9 мин чтения

Мы пишем программы, чтобы облегчить жизнь рядовым пользователям. Однако какой бы уникальной ни была ваша программа, не последнюю роль играет ее внешний вид, т.е. графический интерфейс. Дизайнеры и специалисты по построению 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

$options = array(
 'layout' => 'foo',
 'layoutPath' => '/path/to/layouts',
 'contentKey' => 'CONTENT',
);

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

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

// 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);

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

$layout->setLayout('foo')
 ->setLayoutPath('/path/to/layouts')
 ->setContentKey('CONTENT');

Zend_Layout и application.ini

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

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

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

[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

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

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

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

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

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

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

<?=$this->layout()->content;?>

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

$this->layout()

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

$layout = $this->_helper->layout();

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

$layout = Zend_Layout::getMvcInstance();

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

$layout = $bootstrap->getResource('Layout');

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

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

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

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

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

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

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

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

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

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

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

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

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

<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>

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

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

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

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

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

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

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

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

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

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

$q = $this->_getParam("q");

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

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

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

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;
 }

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

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

<span id="wait-box"></span>

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

var ajaxImg = new Image();
 ajaxImg.src = '/images/ajax-loader.gif';

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

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');
 }

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

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

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

Комментарии 3

Комментарии проходят модерацию
Загрузка комментариев...