Ajax – путеводитель для колеблющихся: Часть 2. Создание программы для чтения блогов на основе Dojo

Практический обзор вопросов разработки приложений Ajax, использования инфраструктуры Dojo и работы с блогами

Предыдущая статья этой серии была посвящена введению в вопросы разработки при помощи Ajax, в частности, в ней приводились базовые сведения о подготовке среды создания Ajax-приложений. В данной статье мы начнем применять эти знания на практике, разрабатывая простую программу для чтения блогов на основе библиотеки Dojo и стандарта Atom.

Гал Шахор, старший технический специалист, IBM

Гал Шахор (Gal Shachor) занимает должность старшего технического специалиста и исследователя в исследовательском центре IBM в Хайфе. Он работает над различными задачами в сфере промежуточного программного обеспечения и многофункциональных Интернет-приложений.



Ксения Квелер, научный сотрудник, IBM

Ксения Квелер (Ksenya Kveler) имеет степень бакалавра компьютерных наук Техиона – израильского технологического института. Последние пять лет она работает в качестве разработчика программного обеспечения в исследовательском центре IBM в Хайфе. Ее деятельность в основном сосредоточена в области распределенных систем, средств для упрощения процесса разработки ПО, а также, в настоящий момент, медицинской информатики. Ее основными профессиональными интересами являются технологии Java и Web.



Майя Барня, научный сотрудник, IBM

Майя Барня (Barnea) имеет степень бакалавра в математики и компьютерных наук университета Хайфы. Последние пять лет она работает в качестве разработчика программного обеспечения в исследовательском центре IBM в Хайфе. Ее деятельность в основном сосредоточена в области визуальных средств разработки приложений и сложных сценариев обработки событий. Ее основными профессиональными интересами являются технологии Java, Web, обработка событий, упрощенное программирование и вопросы удобства использования программного обеспечения.



10.03.2010

Стандарт Atom предоставляет средства, при помощи которых владельцы online-материалов, например новостей, Web-страниц и блогов, распространяют информацию через Web. Типичный сценарий использования Atom заключается в следующем: поставщики информации (или контент-провайдеры) создают специальный файл – Web-ленту – и делают ее доступной через Web. Содержимое данной ленты, для представления которого используется формат синдикации Atom (Atom Syndication Format), представляет собой краткое описание недавно появившихся материалов. Опубликованные ленты затем используются клиентскими приложениями Atom. В подобных приложениях, как правило, применяется протокол публикаций Atom (Atom Publishing Protocol) для обнаружения и показа свежих материалов.

В процессе чтения данной статьи вы пополните свой багаж знаний Ajax, начав разрабатывать программу для чтения блогов на основе Ajax и Atom (в частности, вы создадите слои представления и контроллера). При этом будет использоваться инструментарий Dojo, а с источниками данных (лентами Atom) приложение будет взаимодействовать через протокол публикаций Atom (APP). Кроме того, пакет для хранения данных, входящий в состав Dojo, будет применяться для хранения информации о подписках.

Ajax-инструментарий Dojo – наш выбор

Возможно, вам будет интересно, почему мы остановили свой выбор на инструментарии Dojo в качестве основы для нашего приложения, а не на каком либо еще. В книге “Книга Dojo” (см. Ресурсы) перечислены некоторые преимущества Dojo, из которых мы приведем два, которые сыграли ключевую роль в данном случае:

  • Вертикальная интеграция и полнота: по сравнению с другими открытыми инструментариями Ajax, Dojo предоставляет наиболее полную и интегрированную библиотеку компонентов.
  • Возможность повторного использования по принципу “черного ящика”: механизм виджетов в Dojo позволяет разработчикам создавать новые приложения, повторно используя компоненты, не разбираясь при этом в их содержимом. Благодаря этому создавать сложные приложения Ajax оказывается достаточно просто.

О чем рассказывается в этой статье?
Эта статья посвящена вопросам использования инструментария Dojo для создания программы для чтения блогов на основе Ajax и Atom. В данной части серии мы расскажем об основах архитектуры приложений для работы с блогами, а также продемонстрируем реализацию программы для чтения блогов. В следующей статье мы подробно опишем процесс реализации общей модели подобных приложений.

Вертикальная интеграция и полнота

Dojo обладает многослойной архитектурой, в которой каждый следующий слой обладает более сложной функциональностью, чем предыдущий:

  • Ядро Dojo предоставляет наиболее фундаментальные функции Ajax, которыми обладают многие инструментарии (в частности, IO и DnD): кросс-браузерная совместимость, базовые операции с DOM и так далее.
  • Слой DIJIT, располагающийся поверх ядра Dojo, предоставляет каркас для компонентов, а также множество самих компонентов (виджетов).
  • Последний слой – DOJOX – включает в себя различные расширения, такие как возможность хранения данных в offline или кросс-браузерную поддержку векторной графики.

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

Повторное использование виджетов по принципу “черного ящика”

В Dojo виджеты представляют собой компоненты пользовательского интерфейса на основе Ajax, которые могут многократно использоваться на HTML-страницах. При этом для использования виджета достаточно всего одной строки в HTML. В листинге 1 показан пример создания виджета на HMTL-странице для представления раскрывающейся заголовочной секции.

Листинг 1. Создание виджета для представления заголовочной секции на HTML-странице
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
    <script type="text/javascript" src="../../dojo/dojo.js" 
            djConfig="parseOnLoad: true, isDebug: true"></script>

    <script language="JavaScript" type="text/javascript">
            dojo.require("dijit.TitlePane");
            dojo.require("dojo.parser");    
    </script>

    <style type="text/css">
        @import "../../dojo/resources/dojo.css";
        @import "../themes/tundra/tundra.css";
    </style>
</head>
<body class="tundra">
    <div dojoType="dijit.TitlePane" 
        title="This is the title" style="width: 300px;">
        And this is the content, clicking the title 
        will expand/collapse me.
    </div>
</body>
</html>

Как видите, в начале страницы подключается библиотека ядра Dojo. Как только ядро загружено, можно вызывать метод dojo.require() для загрузки виджета TitlePane, к которому затем происходит обращение внутри тега body.

В результате страница вместе с виджетом TitlePane выглядит, как показано на рисунке 1.

Рисунок 1. Внешний вид страницы с виджетом TitlePane
TitlePane widget in action

Создание простого виджета Dojo - InformationBox

Виджеты реализуются при помощи JavaScript-классов Dojo, которые предоставляют всю необходимую функциональность. При создании виджетов, обладающих сложным интерфейсом, можно использовать фрагменты HTML в качестве интерфейсных шаблонов. Далее, чтобы приподнять завесу тайны над виджетами, давайте создадим очень простой компонент для отображения информационных сообщений. С его помощью пользователи смогут задавать заголовки сообщения, а его интерфейс будет подобен показанному на рисунке 2.

Рисунок 2. Внешний вид виджета InformationBox
Information Box widget in action

Код нашего информационного виджета под именем dwsample.Info приведен в листинге 2.

Листинг 2. Реализация InformationBox
if(!dojo._hasResource["dwsample.Info"]) {
    
    dojo._hasResource["dwsample.Info"] = true;
    dojo.provide("dwsample.Info");
    
    dojo.require("dijit._Widget");
    dojo.require("dijit._Templated");
    
    dojo.declare(
        "dwsample.Info",
        [dijit._Widget, dijit._Templated],
    {
        title: "",
    
        // Шаблон HTML-содержимого виджета
        templateString: [
            "<div id='${id}' class='dijitTitlePaneTitle'>",
            "    <div>",
            "        <span dojoAttachPoint='titleNode' style='font-size:1.1em; margin: 
                       1em;'></span>",
          "    </div>",
          "    <div dojoAttachPoint='containerNode' class='dijitTitlePaneContent'></div>",
            "</div>"
        ].join('\n'),
    
        postCreate: function(){
            this.setTitle(this.title);
        },
    
        setTitle: function(/*String*/ title){
            this.titleNode.innerHTML = title;
        }
    });
}

Как следует из листинга 2, отправной точкой реализации компонента является вызов функции dojo.declare(). Данная функция принимает на вход описание виджета в формате JSON и инициализирует новый компонент.

Важную роль в новом виджете играет содержимое переменной templateString. Она представляет собой фрагмент HTML, который используется в качестве шаблона для HTML-интерфейса, генерируемого компонентом. Внутри шаблона многократно встречаются атрибуты dojoAttachPoint, указывающие Dojo на то, что данные элементы DOM должны выступать в роли стыковочных точек, т.е. являться членами генерируемого шаблона. Благодаря этому виджеты могут получать прямой доступ к этим элементам минуя операции DOM (такие как, например, в методе setTitle). При этом существует одна особенная стыковочная точка – переменная containerNode, определяющая специальную вершину DOM, в которую должно помещаться тело виджета.

Использование данного виджета не представляет никаких трудностей. Пример приведен в листинге 3.

Листинг 3. Пример использования виджета InformationBox
<div dojoType="dwsample.Info" 
     title="Do not panic, here is what you need to do" 
     style="width: 500px;">
    At first, developing a Dojo widget looks like a chore, but it is 
    not! Here is what you should do:
    <ol>
        <li>Create a JavaScript file with the name of the widget and in 
            a directory structure known by Dojo</li>
        <li>Extend the Dojo widget base classes</li>
        <li>...</li>
    </ol>
</div>

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

Теперь, после того, как вы получили базовое представлени о Dojo, можно переходить к рассмотрению Atom и его использованию в приложении для чтения блогов.


Atom и работа с блогами

Термин Atom используется применительно к двум стандартам, которые определяют формат представления лент и протокол редактирования Web-ресурсов, таких как Web-блоги, online-журналы, страницы Wiki и так далее.

  • Формат синдикации Atom (Atom Syndication Format - ASF) был стандартизован в RFC 4287. Данный стандарт описывает формат представления ресурсов Atom.
  • Протокол публикации Atom (Atom Publishing Protocol - APP) по-прежнему находится на этапе разработки. Он определяет сценарии работы с ресурсами Atom в соответствии с принципами REST.

Таким образом, несмотря на то, что первоначально Atom задумывался как средство для работы с блогами, он может использоваться значительно шире, а именно как средство выполнения операций CRUD над Web-ресурсами (в CRUD входят такие операции, как создание/чтение/редактирование/удаление - create/read/update/delete).

Формат синдицирования Atom (ASF)

Стандарт ASF описывает XML-представление ресурсов, несущих информацию в виде коллекций синдицируемых элементов. При этом коллекции принято называть лентами (feeds), а содержащиеся в них элементы – записями (entries).

  • Лента – это уникально идентифицируемая коллекция синдицируемых записей, обладающая заголовком и временной меткой. В частности, блоги, как правило, представлены более чем одной лентой.
  • Запись – это уникально идентифицируемый ресурс, обладающий временной меткой, а также метаданными, такими как заголовок, краткое содержание и список категорий. В качестве содержимого записи может выступать что угодно, от обычного текста до бинарного объекта blob в кодировке base 64 или даже внешнего ресурса, адресуемого при помощи URI. Записи в блогах, как правило, представляются отдельными записями в лентах.

В качестве примера ASF рассмотрим пример небольшой ленты Atom, состоящей из единственной записи (листинг 4).

Листинг 4. Пример ленты Atom
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

   <title>My Blog</title>
   <link href="http://example.org/myblog/"/>
   <updated>2005-11-13T16:25:05Z</updated>
   <author>
       <name>John Smith</name>
   </author>
   <id>urn:uuid:50a70c74-d399-11d9-b93C-0004545e0af6</id>

   <entry>
      <title>What is Atom?</title>
      <link rel="alternate" href="http://example.org/myblog/1"/>
      <id>urn:uuid:1234c670-cfb8-4ebb-aaaa-80da352efa6a</id>
      <updated>2005-11-13T16:25:05Z</updated>
      <summary> The term Atom refers to a pair of standards</summary>
      <content type="text/html"> 
            The term Atom refers to a pair of
            standards which are primarily used in the context of Web 
            content syndication. The emergence of Atom was motivated ...
      </content>
   </entry>
</feed>

Как видите, документ содержит служебную информацию о ленте, за которой следует произвольное число записей.

Информация о ленте Atom может включать несколько рекомендуемых и необязательных элементов. Кроме них существует несколько элементов, которые должны присутствовать непременно, в частности:

  • Title: Представляет собой заголовок ленты в произвольной форме. Как правило, он совпадает с заголовком Web-страницы, ассоциированной с лентой.
  • Updated: Содержит временную метку, указывающую на время последнего изменения ленты. Формат метки выбирается издателем ленты.
  • ID: Представляет собой постоянный, глобально-уникальный идентификатор ленты.

Одним из рекомендуемых элементов является Link, который может использоваться для связывания ленты с различными ресурсами, например, с ресурсом, которому посвящена данная лента.

Элемент entry в Atom схож с элементом feed в той степени, что он также содержит дочерние элементы title, updated и ID. Однако в реальности элементы entry не имеют смысла без дополнительных элементов, таких как:

  • Link: Представляет собой указатель на ресурс, имеющий отношение к данной записи. Тип отношения определяется атрибутом rel, например, значение alternate означает, что по этой ссылке находится альтернативное представление ресурса, представленного в данной записи.
  • Content: Содержит либо само представление ресурса, либо ссылку на него.
  • Summary: Содержит краткое резюме записи.

В случае если нет ни одного элемента content, то обязан присутствовать хотя бы один элемент link, указывающий на альтернативное представление ресурса.

Протокол публикаций Atom (APP)

APP – это протокол для публикации и редактирования Web-ресурсов, базирующийся на принципах REST. Это означает, что APP может применяться для работы с Web-ресурсами вообще, а не только лентами Atom. В частности, можно редактировать и публиковать изображения, содержимое электронных писем и тому подобное. Как и все REST-протоколы, APP использует средства HTTP, в частности, методы, заголовки, статусные коды и т.д., для выполнения операций CRUD над ресурсами. Именно APP будет использоваться в нашей программе для чтения блогов при взаимодействии с лентами и записями Atom.

В APP используется понятие коллекции, которая представляет собой набор ресурсов. Коллекции могут передаваться по сети целиком или по частям. При работе с Atom эти коллекции и ресурсы соотносятся с лентами и записями Atom, представленными в формате ASF. В дополнение к коллекциям и ресурсам, в APP также определяется понятие рабочих пространств (workspaces) и сервисов, используемых для обнаружения коллекций (правда обсуждение подобных вопросов выходит за рамки этой статьи).

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


Реализация программы для чтения блогов при помощи Atom

Наше приложение, интерфейс которого показан на рисунке 3, управляет списком лент (а также их URI), содержащих записи в блогах, которые представляют интерес для пользователя.

Рисунок 3. Интерфейс приложения для чтения блогов
Blog reader application

Приложение позволяет как добавлять новые ленты, так и удалять их из списка. Пользователи могут подписываться на ленты, вводя их URI и заголововки в диалоговом окне, как показано на рисунке 4.

Рисунок 4. Добавление ленты
Adding a feed

Если пользователь захочет получить содержимое той или иной ленты, все что ему надо сделать – это выбрать нужный элемент из выпадающего списка. Приложение обрабатывает подобные действия в соответствии с APP, посылая HTTP-запрос методом GET по адресу, указанному в URI ленты. Затем программа проверяет код состояния HTTP и в случае отсутствия ошибок (код 200) извлекает коллекцию записей Atom из HTTP-ответа сервера. Далее приложение просматривает полученные записи и выводит их на экран. Первоначально записи отображаются в списке в левой части интерфейса, причем каждый элемент списка соответствует заголовку записи (см. рисунок 5).

Рисунок 5. Вывод списка записей ленты
Listing the entries

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

  1. Если запись содержит дочерний элемент content, содержащий представление ресурса, то оно просто выводится на экран.
  2. Если этого элемента нет, то программа ищет элемент summary и выводит его (если он присутствует).
  3. Если нет ни content, ни summary, то используется ссылка на содержимое (элемент link). При этом в случае отсутствия ссылки на исходный ресурс используется альтернативное представление (в этом случае атрибут rel элемента link должен иметь значение alternate). При нажатии на данную ссылку программа открывает страницу в новом окне браузера.

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


Слой представления

Слой представления приложения для чтения блогов реализован в виде единственного HTML-файла, использующего Dojo. Файл состоит из трех основных секций, схематично показанных в листинге 5.

Листинг 5. Реализация слоя представления
<html>
    <head>
     <!-- Секция 1, импорт скриптов Dojo и нашего приложения -->
    </head>
    
    <body class="tundra">
        <!-- Секция 2, интерфейс приложения -->
        <fieldset class="feed-definition-area">
            Select Feed:
             ...
        </fieldset>
        <hr>
        <div class="feed-area">
            <div dojoType="dijit.layout.SplitContainer"
             ...
            </div>
        </div>    

        <!-- Секция 3, описание диалоговых окон -->
        
        <div dojoType="dijit.Dialog" 
            id="add-feed-dlg" 
            ...
        </div>

        <!-- Progress dialog -->
        <div dojoType="dijit.Dialog" 
            id="progress-dlg"
            title="">
            ...
        </div>
        
        <div dojoType="dijit.Dialog" 
            id="error-msg-dlg"
            ...
        </div>
    </body>
</html>
  1. В первой секции импортируются необходимые скрипты на JavaScript, стили CSS и виджеты, широко использующиеся в приложении.
  2. Во второй секции создается основная часть интерфейса приложения.
  3. В третьей секции определяются различные диалоговые окна, использующиеся в разных частях приложения.

Далее рассмотрим все части представления более подробно.

Секция 1: импортирование скриптов

Обратите внимание на листинг 6, в котором полностью приведена секция импортов приложения.

Листинг 6. Секция импортов
<head>
    <title>Blog Reader</title>

    <script type="text/javascript" 
            src="./reset-styles.js"></script>    

    <style type="text/css">
        @import "../dojoAjax/dojo/resources/dojo.css";
        @import "../dojoAjax/dijit/themes/tundra/tundra.css";
        @import "css/blogreader.css";
    </style>
        
    <script type="text/javascript" 
            src="../dojoAjax/dojo/dojo.js"
            djConfig="parseOnLoad: true"></script>

    <script type="text/javascript">
        dojo.require("dijit.Dialog");
        dojo.require("dijit.util.manager");
        dojo.require("dijit.form.Button");
        dojo.require("dijit.form.Textbox");
        dojo.require("dijit.layout.SplitContainer");
        dojo.require("dijit.layout.ContentPane");
        dojo.require("dojo.parser");
    </script>

    <script type="text/javascript" src="blog-reader.js"></script> 
  
</head>

В начале этой секции импортируется JavaScript-файл reset-styles.js, который сбрасывает стили различных тегов, устанавливая для них значения по умолчанию. Вслед за этим подключаются разные файлы CSS, которые задают внешний вид интерфейса приложения. Сначала импортируются файлы, относящиеся к стилям Dojo, в особенности tundra.css, который управляет внешним видом компонентов Dojo, а затем к ним добавляются стили, специфичные для показа записей в блогах.

Далее происходит подключение самой библиотеки Dojo – файла dojo.js. В данном случае Dojo размещается в каталоге dojoAjax. Все необходимые виджеты импортируются при помощи метода dojo.require(), являющегося частью ядра Dojo.

Наконец, импортируется файл blog-reader.js, в котором размещаются функции, реализующие логику приложения для чтения блогов. Этот файл мы рассмотрим чуть позже.

Секция 2: создание пользовательского интерфейса

Теперь, после того, как загружены все необходимые файлы JavaScript и CSS, можно приступать к описанию интерфейса приложения при помощи тегов HTML и виджетов Dojo (листинг 7).

Листинг 7. Реализация интерфейса приложения для чтения блогов
<body class="tundra">
      <fieldset class="feed-definition-area">
          Select Feed:
        <select dojoType="blogreader.FeedsCombo" id="feed-combo" comboClass="feedsCombo">
        </select>
        <button dojoType="dijit.form.Button" id="add-feed-btn" iconClass="addIcon">
            Add Feed
        </button>                                         
        <button dojoType="dijit.form.Button" id="clear-feed-btn" iconClass="cancelIcon">
            Clean Feeds
        </button>                                         
    </fieldset>
    <hr>
    <div class="feed-area">
        <div dojoType="dijit.layout.SplitContainer"
                orientation="horizontal"
                sizerWidth="7"
                activeSizing="false"
                class="article-splitter">
                <div dojoType="dijit.layout.ContentPane" 
                    sizeShare="30" 
                    sizeMin="20" 
                    style="overflow:auto">
                        <div id="articles-table-wrapper" 
                            style="visibility: hidden; width:96%">
                                <div dojoType="blogreader.ArticleList" 
                                id="article-list" 
                                listClass="articles-list" 
                                titleClass="articles-list-title"></div>
                        </div>
                </div>
        </div>
        <div dojoType="dijit.layout.ContentPane" 
             sizeShare="70"
             style="overflow:auto">
                <div id="article-content-pane" class="article-content-pane">
                    <div id="article-title-id" class="article-title">
                    </div>
                    <div id="article-content" class="article-content">
                    </div>
                </div>
        </div>
    </div>

Первым сигналом о том, что применяется библиотека Dojo, является использование tundra в качестве класса CSS для тега body. Визуальное оформление интерфейса реализуется в Dojo при помощи CSS. Таким образом, в результате установки tundra в качестве класса для body будет использоваться оформление tundra из набора Dojo.

Первым созданным нами интерфейсным элементом будет компонент для выбора ленты, расположенный в верхней части интерфейса. Данный элемент управления представляет собой HTML-элемент select и две кнопки Dojo. Обратите внимание на следующие моменты:

  • Мы создали виджет Dojo поверх элемента select. Благодаря этому нам удалось консолидировать всю логику представления, относящуюся к выбору ленты (например, добавление и удаление лент), в одном месте.
  • На данном этапе мы не задаем функции контроллера в качестве обработчиков обратных вызовов. Далее мы будем связывать JavaScript-методы контроллера и обработчики событий элементов управления программным образом.
  • Мы добавили оформление кнопок в виде иконок, идентифицируемых классами CSS.

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

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

Подобное поведение будет реализовано при помощи Dojo-контейнера split, который позволяет разделить окно браузера на секции, загрузив в каждую компонент ContentPane. Данные компоненты будут разделяться элементами sizer, позволяющими пользователю менять размер панелей. Каждый компонент ContentPane содержит HTML-элемент div, внутри которого заключается полезная информация: список записей ленты или содержимое выбранной ленты. Благодаря использованию элементов div мы можем легко управлять режимом видимости содержимого (для этого служит CSS-стиль diplay).

Список записей ленты выводится при помощи еще одного стандартного виджета Dojo.

Секция 3: диалоговые окна

В третьей и последней секции создаются диалоговые окна, использующиеся в приложении. В идеале каждое из них должно представлять собой отдельный виджет. Однако в целях сокращения объема кода мы не стали отделять данные окна от главного интерфейса. При этом относящиеся к ним события будут обрабатываться контроллером.

Взгляните на рисунок 6, на котором показан внешний вид диалогового окна для добавления ленты. Оно является наиболее сложным из всех диалоговых окон данного приложения.

Рисунок 6. Диалог добавления ленты
Add Feed dialog box

Реализация диалогового окна для добавления новой ленты приведена в листинге 8.

Листинг 8. Реализация диалога для добавления ленты
<div dojoType="dijit.Dialog" 
    id="add-feed-dlg" 
    title="Add Feed"
    closeNode="cancel-add-feed-btn"
    style="width: 400px;">
    <form onsubmit="return false;"> 
        <table class="table-no-border">
            <tr>
                <td>
                    <label for="feed-name">Title: </label>
                </td>
                <td width="100%">
                    <div dojoType="dijit.form.TextBox"
                        id="feed-name" 
                        type="text"
                        required="true"
                        value=""
                        trim="true"
                        autocomplete="on"
                        classPrefix="noColors"
                        class="text-box"></div>
                </td>
            </tr>
            <tr>
                <td>
                    <label for="feed-url">URL: </label>
                </td>
                <td width="100%">
                    <div dojoType="dijit.form.TextBox"
                            id="feed-url" 
                            type="test"
                            required="true"
                            value=""
                            trim="true"
                            classPrefix="noColors"
                            class="text-box"></div>
                </td>
            </tr>
        </table>
        <fieldset>
            <button dojoType="dijit.form.Button" id="ok-add-feed-btn">
                OK
            </button>                                         
            <button dojoType="dijit.form.Button" id="cancel-add-feed-btn">
                Cancel
            </button>                                         
        </fieldset>
    </form>
</div>

Как видите, данное окно легко реализуется на основе виджетов с добавлением небольшого количества HTML-кода. Вся процедура создания окна заключается в следующем:

  • Задействовав виджет dijit.Dialog, мы сразу же получили простое и легковесное диалоговое окно, открывающееся в контексте текущей страницы.
  • Далее в dijit.Dialog помешаются другие виджеты и добавляется разметка HTML, определяющая внешний вид диалогового окна.
  • Обработка нажатия на кнопку Cancel легко реализуется при помощи атрибута closeNode. Таким образом, нам даже необязательно добавлять обработчик для данного события.
  • За все остальное отвечает Dojo, которая предоставляет API для открытия и закрытия данного окна вместе со всем его содержимым.

Слой контроллера

Контроллер приложения для чтения блогов будет следить за событиями, генерируемыми компонентами представления, и реагировать на них, вызывая методы модели. Основными событиями являются следующие:

  • Выбор новой ленты: контроллер должен загрузить выбранную ленту и показать ее пользователю.
  • Выбор записи ленты: контроллер должен показать выбранную запись пользователю.
  • Активация кнопки добавления ленты: контроллер должен вывести диалоговое окно для добавления ленты.
  • Активация кнопки удаления всех лент: контроллер должен очистить список лент.
  • Нажатие на кнопку OK в диалоге добавления лента: контроллер должен добавить ленту в список, приняв в качестве параметров ее заголовок и URI.

При взаимодействии контроллера с моделью приложения существует одна особенность, которая может показаться неожиданной для новичков в Ajax. Дело в том, что данное взаимодействие происходит асинхронно. Как правило, код на JavaScript выполняется в браузере в однопоточном режиме, поэтому для того, чтобы интерфейс оставался отзывчивым в те моменты, когда данные от модели передаются по сети, приложение должно работать в асинхронном режиме. Вследствие этого контроллер также следит за следующими событиями модели:

  • feedDataArrived: генерируется моделью после окончания загрузки ленты. В этот момент контроллер знает, что данные ленты находятся в кэше модели и могут быть переданы в слой представления.
  • feedReadFailed: генерируется моделью в результате неудачной попытки загрузки ленты. В этот момент контроллер понимает, что взаимодействие модели и ленты Atom завершилось неудачей и необходимо вывести сообщение об ошибке.

Далее мы рассмотрим реализацию контроллера и его взаимодействие с различными компонентами представления, включая реагирование на различные события при помощи Dojo.

Связь контроллера с компонентами интерфейса и модели

В листинге 9 показана общая структура контроллера. При этом особое внимание уделяется методу initialize(), который выполняет следующие функции:

  • Обнаружение компонентов интерфейса при помощи их идентификаторов.
  • Установка слушателей различных событий, генерируемых моделью и интерфейсом.
Листинг 9. Связывание контроллера с компонентами интерфейса и модели
var BlogController = {
    mHideProgressFunction: null,
    mFeedsCombo:           null,
    mAddFeedDlg:           null,
    mFeedURLTextbox:       null,
    mFeedTitleTextbox:     null,
    mArticleWrapper:       null,
    mProgressDlg:          null,
    mProgressDlgMsg:       null,
    mContentDiv:           null,
    mContentTitle:         null,
    mContentPane:          null,
    mErrDlgContent:        null,
    mErrDlg:               null,
    mArticleList:          null,
            
    // Инициализация страницы          
    initialize: function() {
        this.mFeedsCombo       = dijit.byId("feed-combo");
        this.mArticleList      = dijit.byId("article-list");
        this.mAddFeedDlg       = dijit.byId("add-feed-dlg");
        this.mFeedURLTextbox   = dijit.byId("feed-url");
        this.mFeedTitleTextbox = dijit.byId("feed-name"); 
        this.mArticleWrapper   = dojo.byId("articles-table-wrapper");
        this.mProgressDlg      = dijit.byId("progress-dlg");
        this.mProgressDlgMsg   = dojo.byId("progress-msg");
        this.mContentDiv       = dojo.byId("article-content");
        this.mContentTitle     = dojo.byId("article-title-id");
        this.mContentPane      = dojo.byId("article-content-pane");
        this.mErrDlgContent    = dojo.byId("error-msg-content");
        this.mErrDlg           = dijit.byId("error-msg-dlg");
        
        dojo.connect(BlogModel, 
                     "feedDataArrived", 
                     this, 
                     this.feedDataArrived);
    
        dojo.connect(BlogModel, 
                     "feedReadFailed", 
                     this, 
                     function(errMsg) {
                         this.hideProgressDlg();
                         this.cleanFeedUI();
                         this.showErrorMsgDlg(errMsg);
                         this.mFeedsCombo.setValue("");
                         this.mArticleWrapper.style.visibility = "hidden";
                     });

        BlogModel.initialize(this);

        this.populateFeedsCombo();
        
        // добавление обработчиков событий добавления ленты и удаления лент
        dojo.connect(dojo.byId("add-feed-btn"), 
                     "onclick", 
                     this, 
                     function() {
                         this.mAddFeedDlg.show();
                         this.mFeedTitleTextbox.focus();
                     });
        dojo.connect(dojo.byId("clear-feed-btn"), 
                     "onclick", 
                     this, 
                     this.cleanFeeds);
                     
        // установка функций обратного вызова
        dojo.connect(dojo.byId("ok-add-feed-btn"), 
                     "onclick", 
                     this, 
                     this.createFeed);
                     
        // слежение за изменением выбранной ленты в выпадающем списке  
        dojo.connect(this.mFeedsCombo, 
                     "onSelectionChanged", 
                     this, 
                     function() {                    
                         this.readFeed(this.mFeedsCombo.getSelectedValue());
                     });
                     
        // обработчик выбора записи 
        dojo.connect(this.mArticleList, 
                     "onSelectionChanged", 
                     this, 
                     this.updateArticleContent);
    },

    createFeed: function(event) {
      // Создание ссылки на новую ленту, добавление ее в
      // модель и в выпадающий список лент
    },

    cleanFeeds: function() {
    // Удаление ссылок на все ленты из модели и представления
    },
            
    // Обновляет содержимое страницы в соответствии с XML-содержимым ленты
    feedDataArrived: function() {
    // Лента прочитана, необходимо обновить интерфейс соответствующим образом
    },  
            
    updateArticleContent: function() {
    // Выбрана новая запись, необходимо обновить
    // секцию для вывода содержимого записей
    },

    // Вспомогательные методы

    populateFeedsCombo: function() {
    // Заполняет выпадающий список лентами, зарегистрированными в модели
    },

    readFeed: function(url) {
    // Очищает текущее представление ленты и инициализирует чтение новой ленты
    },
    
    // Удаляет содержимое ленты из интерфейса
    cleanFeedUI: function() {
    // Очищает представление ленты в окне приложения
    },

    showErrorMsgDlg: function(errorMsgContent) {
    // Выводит диалоговое окно с заданным сообщением об ошибке
    },
    
    showProgressDlg: function(msg) {
    // Выводит диалоговое окно со статусом операции и заданным сообщением
    }
};

На примере метода initialize() видно, как в приложениях Dojo происходит обнаружение и обращение к компонентам UI, таким как элементы HTML и виджеты, на основе идентификаторов компонентов. При этом используются два метода:

  • dojo.byId(id): возвращает ссылку на элемент HTML по его идентификатору. Элемент представляет собой объект DOM.
  • dijit.byId(id): возвращает ссылку на виджет Dojo по его идентификатору. Результатом является объект JavaScript, описывающий виджет.

Если вы внимательно следили за вызовами методов byId() в контроллере, то могли заметить, что результаты этих вызовов в методе initialize() затем сохранялись в переменных-членах класса. Это имеет смысл не только для повышения быстродействия, но и для облегчения поддержки приложения в будущем. Идентификаторы компонентов интерфейса могут часто меняться, пока программа находится в фазе активной разработки, поэтому с точки зрения развития приложения лучше изолировать их использование в одном участке кода.

Слежение за событиями, генерируемыми в слоях представления и модели, осуществляется при помощи метода dojo.connect. Данный метод используется в приложениях Dojo для связывания функций с вызовами методов объектов. Например, во фрагменте кода, приведенном ниже, функция foo связывается с методом bar объекта obj.

Листинг 10. Слежение за событиями
dojo.connect(obj, "bar", expectedThis, foo);

В качестве третьего параметра метод dojo.connect принимает объектную ссылку, которая будет использоваться в качестве указателя this при вызове метода. Это может быть полезно, если вы захотите создать шаблонную функцию, работающую с различными объектами.

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

Листинг 11. Указание функции для вызова при наступлении события
dojo.connect(this.mFeedsCombo, 
             "onSelectionChanged", 
             this, 
             function() {                    
                 this.readFeed(this.mFeedsCombo.getSelectedValue());
             });

Эта возможность оказывается очень удобной, если приходится иметь дело с множеством коротких функций (2-3 строки кода), которые используются только внутри метода connect. В этом случае объявление функции непосредственно в момент ее использования повышает удобочитаемость кода.

Обратные вызовы виджетов Dojo

Как было показано выше, виджеты Dojo управляют контроллером при помощи событий. При этом остается вопрос: а как обработчики событий смогут делать обратные вызовы виджетов? Это решается очень просто: в виду того, что виджеты Dojo представляют собой обычные JavaScript-объекты с открытыми методами, все что требуется – это вызывать эти методы. Аналогичным образом можно обращаться к элементам и атрибутам HTML, представленным в виде объектов DOM. В результате функция showProgressDlg(msg), показанная ниже, может легко передать нужную строку в диалоговое окно процесса загрузки и вывести его на экран.

Листинг 12. Обратное обращение к виджетам Dojo из обработчика события
initialize: function() {
    // Сохранение ссылок на виджеты Dojo и элементы DOM
    this.mProgressDlg      = dijit.byId("progress-dlg");
    this.mProgressDlgMsg   = dojo.byId("progress-msg");
...
}
...
showProgressDlg: function(msg) {
    // Использование методов виджетов и DOM
    this.mProgressDlgMsg.innerHTML = msg;
    this.mProgressDlg.show();
},
...

За более подробной информацией о методах, предоставляемых объектами Dojo и DOM, обратитесь к документации по этим технологиям, ссылки на которые приведены в разделе Ресурсы.

Центр материалов по Ajax на сайте developerWorks
Обратитесь к центру материалов по Ajax – единому собранию бесплатных утилит, кода и информации о разработке приложений Ajax. На активно действующем форуме сообщества Ajax, модерируемом экспертом Джеком Херрингтоном (Jack Herrington), вы сможете общаться с коллегами, возможно, знающими решение проблемы, над которой вы ломаете голову в данный момент.

Инициализация

Инициализация контроллера должна происходить в момент завершения загрузки страницы, когда все компоненты Dojo готовы к работе. В нашем приложении инициализация контроллера означает вызов метода BlogController.initialize. Dojo предоставляет простой механизм на основе dojo.addOnLoad, гарантирующий вызов данного метода в нужное время. Пример его использования приведен во фрагменте кода ниже.

Листинг 13. Инициализация контроллера
dojo.addOnLoad(BlogController, BlogController.initialize);

Метод dojo.addOnLoad может принимать один или два параметра.

  • Если передается один параметр, то Dojo интерпретирует его как функцию, которую необходимо выполнить после загрузки Dojo.
  • Если передаются два параметра, то первый интерпретируется как указатель this, а второй – как метод, который необходимо выполнить после загрузки Dojo.

При этом существуют и другие способы. В частности, при помощи метода dojo.connect можно связать свою функцию с вызовами методов жизненного цикла объекта dojo. Пример такого подхода приведен во фрагменте кода ниже. Однако данный вариант не рекомендуется использовать, потому что методы жизненного цикла могут быть подвержены изменениям в будущем.

Листинг 14. Альтернативный вариант инициализации контроллера
dojo.connect(dojo, 
             "loaded", 
             BlogController, 
             BlogController.initialize);

Заключение

В этой статье рассказывалось об основах архитектуры приложений, работающих с блогами, а также рассматривались стандарты Atom и их использование в программе для чтения блогов. Была показана реализация простых, но обладающих необходимой функциональностью слоев представления и контроллера приложения на основе Dojo. В следующей статье мы обратимся к вопросам реализации модели. Оставайтесь с нами!


Загрузка

ОписаниеИмяРазмер
Исходный код приложения для чтения блоговwa-aj-basics2.zip18KБ

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура, XML
ArticleID=473524
ArticleTitle=Ajax – путеводитель для колеблющихся: Часть 2. Создание программы для чтения блогов на основе Dojo
publish-date=03102010