Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Ajax в действии

.pdf
Скачиваний:
95
Добавлен:
01.05.2014
Размер:
6.34 Mб
Скачать

552 Часть IV. Ajaxв примерах

ление — это HTML-код, наш класс представления отвечает за генерацию HTML-страницы. Итак, рассмотрим для начала конструктор, представленный в листинге 13.25-

Листинг 13.25. Класс представления RSSItemView

RSSItemView = C l a s s . c r e a t e ( ) ;

 

RSSItemView.prototype = {

 

i n i t i a l i z e : function(rssltem,

feedlndex, itemlndex, numFeeds) {

t h i s . r s s l t e m =

rssltem;

 

t h i s . f e e d l n d e x

• feedlndex

+ 1;

t h i s . i t e m l n d e x = i t e m l n d e x + 1;

t h i s . n u m F e e d s

= numFeeds;

 

Ь

}

Давайте внимательно рассмотрим параметры. Первый параметр — экземпляр RSSltem. С его помощью мы сообщаем представлению, для какого экземпляра модели обеспечивается это представление. Обратите внимание на то, что в общем случае классам модели не разумно знать что-либо о представлении, однако представление по необходимости имеет детальные знания о модели. Другие параметры обеспечивают некоторый дополнительный контекст для представления. Параметр feedlndex сообщает представлению номер ленты. Параметр itemlndex указывает представлению, где размещается данная статья в родительском массиве статей RSSFeed. Параметр numFeeds сообщает представлению, сколько всего лент. Все указанные параметры необходимы для получения информации о месте данного представления в мире. Представление может пожелать отобразить область содержимого, указывающую, например, "это лента 1 из 7 и статья 3 из 5". Данные атрибуты можно внедрить в модель, однако в действительности они не являются атрибутами, за которые в общем случае должна отвечать модель, поэтому этот контекст (требуемые представлению) передается клиентом в конструктор представления.

Как отмечалось выше, представление отвечает за генерацию HTML-кода. Поэтому нашему классу представления понадобится еще один метод. Посмотрим, как это он выглядеть (листинг 13.26).

Листинг 13.26. Метод генерации HTML-кода

toHTML: function () { var out = ""

out += '<span class="rssFeedTitlePrompt">RSS Feed '

out += '(' + this.feedlndex + • of ' + this.numFeeds + ') : '; out += '</span>';

out += '<span class="rssFeedTitle">';

out += '<a href="' + this.rssltem.rssFeed.link + '">' + this.rssltem.rssFeed.title + '</a>';

out += '</span>'; out += '<br/>';

out += '<span class="rssFeedItemTitlePrompt">Article '; out += '(' + this.itemlndex + ' of * + this.rssltem.rssFeed.items.length + ') : ' ;

out += '</span>';

Глава 13. Создание приложений Ajax, не использующих сервер 553

Рис. 13.15. RSS-лента (х из у): название RSS-ленты

o u t + = ' < s p a n c l a s s = " r s s F e e d I t e m T i t l e " > ' ;

o u t += ' < a h r e f - " 1 + t h i s . r s s l t e m . l i n k + ' " > ' + t h i s . r s s l t e m . t i t l e + ' < / a > ' ;

o u t + = ' < / s p a n > ' ;

o u t + = ' < d i v c l a s s = " r s s I t e m C o n t e n t " > ' ; o u t + - t h i s . r s s l t e m . d e s c r i p t i o n ;

o u t + = ' < / d i v > ' ;

r e t u r n o u t ;

Ь

Метод toHTML создает на экране контекстные элементы, за которыми сле-

дует текст статьи. Первый фрагмент кода отображает текст RSS-лента (х

из

у) : Заголовок RSS-ленты. Атрибут link родительского элемента rssFeed

ис-

пользуется для генерации атрибута HREF анкера, а атрибут t i t l e — для генерации текста анкера. Для каждого элемента span генерируется имя класса CSS (одно для запроса, второе для анкера), что позволяет независимо определять стили этих элементов. Сказанное иллюстрируется на рис. 13.15.

Следующий фрагмент кода генерирует текст Статья (х из у) : Заголовок RSS-статьи. Для генерации атрибута HREF анкера используется атрибут link RSS-статьи, а с помощью атрибута t i t l e статьи генерируется текст анкера. Кроме того, как показано на рис. 13.16, код предоставляет имена классов CSS для запроса и заголовка. Несколько последних строк метода toHTML генерируют элемент div, который будет вмещать содержимое статьи RSSltem (атрибут description). Соответствующий код выглядит следующим образом:

out += '<div class="rssItemContent">'; out += this.rssltem.description;

out += '</div>';

Для текста статьи генерируется имя класса CSS rssltemContent. Чтобы текст не подходил вплотную к границе блока, в соответствующем классе необходимо задать небольшие поля и заполнение. Кроме того, данный блок должен иметь фиксированную высоту и значение auto параметра overflow, чтобы его содержимое можно было при необходимости прокручивать независимо от показанной ранее контекстной информации. Типичное определение класса CSS для блока текста статьи приводится ниже.

554 Часть IV. Ajax в примерах

Рис. 13.16. Статья (х из у) заголовок RSS-статьи

Рис. 13.17. Статья (х из у) заголовок RSS-статьи

.rssItemContent {

border-top : lpx solid black; width : 590px;

height : 350px; overflow : auto; padding : 5px; margin-top : 2px;

}

При таком стилевом оформлении область содержимого должна выглядеть примерно так, как показано на рис. 13.17. Собирая вместе все составляющие, получаем показанное на рис. 13.18 представление, генерируемое классом RSSitemView. Прежде чем завершить рассмотрение представления, добавим в него еще один маленький метод, который немного облегчит его использование.

toString: function() { return this.toHTMLO;

J

RSSItemView Result

Рис. 13.18. Окончательный вид класса RSSItemView

Причина, по которой мы дали представлению метод tostring, заключается в том, что это позволяет нам взаимозаменяемо использовать экземпляр представления и генерируемую им строку HTML. Например, мы можем присвоить значение атрибуту innerHTML элемента, равное классу представления, и далее будет использоваться строковое представление (сгенерированный HTML-файл). Например, ниже приводится код, присваивающий сгенерированный HTML-документ представления атрибуту innerHTML элемента div с идентификатором contentDiv.

var rssItemView -

new RSSItemView( anRSSFeed, 0, 0, 5 );

$('contentDiv1 ).

innerHTML = rssItemView;

(Помните, $() — это функция из библиотеки Prototype, позволяющая извлекать элементы DOM по их идентификаторам.) Теперь, когда у нас есть хороший набор абстракций для классов модели и представления, рассмотрим контроллер приложения, который свяжет воедино все эти компоненты.

13.7.3. Контроллер приложения

Класс RSSReader будет объединять функции, связанные с манипуляцией классами модели и представления, координируя все действия, соотнесенные с приложением чтения RSS-лент. Напомним, что интерфейс приложения реализован в виде слайд-шоу лент, когда каждая статья будет отображаться на определенный период времени, а затем постепенно переходить в следующую

556 Часть IV. Ajax в примерах

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

1.Построение объектов и первоначальная настройка.

2.Реализация слайд-шоу.

3.Создание эффектов перехода.

4.Загрузка RSS-лент средствами Ajax.

5.Обновление пользовательского интерфейса.

Чтобы уменьшить сложность и объем кода, требуемого для выполнения всех этих задач, используем библиотеку Prototype (позволяющую получить синтаксическую лаконичность), библиотеку Rico (обеспечивающую функциональные возможности, требуемые для эффектов перехода) и объект net.ContentLoader (предлагающий поддержку Ajax). Итак, начнем с первоначального построения и настройки.

Построение и настройка

Фактически разработка всех наших предыдущих компонентов начиналась с конструкторов. Будем верны традиции и сейчас — начнем с определения конструктора, которым в данном случае является простой метод, задающий первоначальные значения по умолчанию для какого-то состояния компонента и, как в других примерах, устанавливающего конфигурационные опции и выполняющего инициализацию. Учитывая это, конструктор RSSReader определяется так, как показано в листинге 13.27.

Листинг 13.27.* Конструктор RSSReader ;

^ Х ^ В

RSSReader = Class.create*);

 

RSSReader.prototype = {

 

initialize: function( readerld, options )

{

// О Установить значения по умолчанию

 

t h i s . i d = r e a d e r l d ;

 

t h i s . t r a n s i t i o n T i m e r = n u l l ;

 

t h i s . p a u s e d = f a l s e ;

 

t h i s . v i s i b l e L a y e r = 0;

 

// © Настроить опции конфигурации

 

t h i s . s e t O p t i o n s ( o p t i o n s ) ;

 

// © Инициализировать поведение

 

t h i s . s t a r t { ) ;

 

) ,

 

Приведенный выше конструктор принимает два аргумента: идентификатор и объект опций. Идентификатор используется как уникальная метка приложения и добавляется как префикс к идентификаторам кнопок, кото-

Глава 13. Создание приложений Ajax, не использующих сервер 557

рые он должен будет определить в структуре DOM. Реализация сказанного будет показана ниже, в методе applyButtonBehaviors. Первое, что делает конструктор О, — это устанавливает значения по умолчанию для состояния. Далее (как и в большинстве других проектов) используется объект опций ©, с помощью которого задаются конфигурационные опции компонента. Для этого применяется метод setoptions. Наконец, в методе star t © происходит все, что требуется для активизации компонента. Далее мы сначала рассмотрим конфигурацию, а затем перейдем к поведению.

За конфигурацию отвечает идиома setoptions, показанная в листинге 13.28. Давайте разберемся с реализацией setoptions и обсудим опции конфигурации.

Листинг 13.28. Метод setoptions

j j j l

setOptions: function(options)

{

 

t h i s . o p t i o n s = {

 

 

slideTransitionDelay: 7000,

 

fadeDuration

: 300,

 

 

errorHTML :

'<hr/>Error retrievin g content.<br/>'

 

}.extend(options);

 

ь

 

 

ш

 

 

 

В приведенном методе setOptions указаны свойства приложения, которые мы решили сделать настраиваемыми. Свойство slideTransitionDelay задает число миллисекунд, в течение которых один "слайд" статьи виден до перехода во второй. Свойство fadeDuration задает время в миллисекундах, требуемое для полного затухания "слайда" (а следовательно, для "проявления" следующего "слайда"). Наконец, при возникновении ошибки в процессе загрузки RSS-ленты используется свойство errorHTML, задающее HTMLдокумент, отображаемый в качестве сообщения об ошибке. В приведенном коде также показаны значения данных свойств по умолчанию, которые сохраняются, если пользователь явно не укажет другие. Здесь стоит отметить, что компонент ожидает, что ему в качестве исходного набора лент для обработки будет передано свойство rssFeeds объекта опций. Поскольку мы в действительности не можем предложить разумное значение по умолчанию для этой величины, в методе setoptions она не определяется. Подразумевается, что приложение будет создано с объектом опций, подобным показанному ниже.

var options =

{

rssFeeds: [

"http://radio.javaranch.com/news/rss.xml",

 

"http://radio.j avaranch.com/pascarello/rss.xml",

 

"http://radio.javaranch.com/bear/rss.xml",

 

"http://radio.javaranch.com/lasse/rss.xml" ] };

var rssReader

= new RSSReader{'rssReader', options );

Итак, мы довольно быстро закодировали создание и настройку конфигурации. Теперь можно переходить к магическому методу start, который и запустит весь процесс. В следующих разделах мы кратко рассмотрим этот метод и посмотрим, к чему приводит его использование. Начнем, как обычно, с реализации, показанной в листинге 13.29.

558 Часть IV. Ajax в примерах

Листинг 13.29. Метод start :ЯНННИНИННННННННН|

start: function() { this.applyButtonBehaviors();

new Effect.FadeTo( this.getLayer(1), 0.0, 1, 1, {} ); this.loadRSSFeed(0,true);

this.startSlideShow{false);

},

 

щ

Метод applyButtonBehaviors настраивает обработчики событий onclick для кнопок перехода к предыдущей/последующей статье, паузы/возобновления, а также кнопки добавления новой ленты. Этот метод мы рассмотрим следующим. Эффект затенения, представленный во второй строке, вызывает постепенное исчезновение с экрана видимого элемента div, чтобы его можно было скрыть при загрузке первого слайда. Обратите внимание на то, что

вданной реализации мы не кодируем эффект самостоятельно, а используем готовое решение из библиотеки Rico, что уменьшает объем кода, который требуется писать, отлаживать и сопровождать. Метод loadRSSFeed инициализирует запрос Ajax на загрузку в первую ленту, а метод startSlideShow запускает таймер со значением slideTransitionDelay, инициируя таким образом слайд-шоу. Подробнее метод loadRSSFeed будет рассмотрен в разделе "'Загрузка RSS-лент с помощью Ajax", а метод startSlideShow — в разделе 'Реализация слайд-шоу". На этом мы, как и обещали, завершаем обсуждение построения и настройки, рассмотрев упомянутый в листинге 13.29 метод applyButtonBehaviors.

Как отмечалось ранее, метод applyButtonBehaviors подключает кнопки

кметодам, реализующим их поведение. Реализация этого метода показана

влистинге 13.30.

Листинг 13.30. Метод applyButtonBehaviors .

applyButtonBehaviors: function!)

{

 

 

 

S(this.id + '__prevBtn') .onclick

=

this.previous.bind(this) ;

 

$ ( t h i s . i d + ' _ n e x t B t n ' ) . o n c l i c k • t h i s . n e x t . b i n d ( t h i s ) ;

 

$ ( t h i s . i d + ' _ p a u s e B t n ' ) . o n c l i c k - t h i s . p a u s e . b i n d ( t h i s ) ;

 

$ ( t h i s . i d + ' _ a d d B t n ' ) - o n c l i c k = t h i s . a d d F e e d . b i n d ( t h i s ) ;

_ ±

 

 

 

.

Здесь, пожалуй, стоит напомнить, какой синтаксис и какие идиомы мы используем. В частности, мы задействовали пару синтаксических элементов библиотеки Prototype. Во-первых, это метод $, который можно рассматривать как вызов document .getElementByld. Во-вторых, метод bind неявно создает для нас замыкание, так что обработчик событий onclick для каждой кнопки может вызывать методы первого класса нашего компонента. Рассмотрим теперь детали реализации.

Реализация открывает неявный контракт между компонентом и HTMLразметкой приложения. Компонент создается с идентификатором, который хранится в атрибуте this . id . Позже этот идентификатор используется как префикс для нахождения различных элементов в разметке. В данном случае предполагается, что идентификаторы кнопок -— это метки, переданные в

Глава 13 Создание приложений Ajax, не использующих сервер 559

Таблица 13.4. Атрибуты контроллера

Атрибут

Описание

t h i s . currentFeed

Экземпляр RSSFeed, в текущий момент загруженный в память

t h i s . feedlndex

Номер ленты, показываемой в текущий момент. Является

 

индексом массива t h i s . o p t i o n s .rssFeeds

t h i s . itemlndex

Номер статьи, показываемой в текущий момент Является

 

индексом массива статей объекта RSSFeed, показываемого

 

в текущий момент

конструктор, к которым добавлено _prevBtn, _nextBtn, _pauseBtn и addBtn. Чтобы проиллюстрировать сказанное, рассмотрим приведенный выше код. В качестве идентификатора здесь используется rssReader, а компонент ожидает, что кнопки будут заданы следующим образом:

<input type="button" id="rssReader_prevBtn" value=" « " /> <input type="button" id="rssReader_pauseBtn" value=" I I " /> <input type="button" id="rssReader_nextBtn" value=" » " /> <input type="button" id="rssReader_addBtn" value="Add Feed" />

Видно, что контроллер RSSReader начинает приобретать форму, и теперь мы можем рассмотреть детали реализации слайд-шоу.

Реализация слайд-шоу

Теперь самое время поговорить об изменении семантики по сравнению

спредыдущей версией сценария. Изначально мы загружали все RSS-ленты

впамять в момент запуска, а затем просто формировали переходы между внутренними представлениями лент. Это было просто, но масштабируемость такого решения вызывала определенные сомнения. Если мы регулярно прочитываем десятки или сотни RSS-лент, содержащих десятки статей, предварительная загрузка их всех была бы чересчур сильной нагрузкой на браузер. Поэтому в разделе реструктуризации мы попытаемся добиться нормальной масштабируемости и производительности приложения, так изменив семантику, чтобы за раз в память загружалась единственная RSS-лента. Все статьи RSSItems одной ленты побывают в памяти, однако в каждый отдельный момент времени в памяти будет присутствовать только один элемент RSSFeed. За то, на каком этапе находится слайд-шоу в процессе отображения содержимого, отвечают три атрибута контроллера, описанных в табл. 13.4.

Разобравшись с семантикой, переходим к навигации. Если мы планируем реализовать навигацию по всем статьям (элементам item) всех RSS-лент, то должны изучить несколько методов. Рассмотрим для начала пару методов перехода к предыдущей/следующей статье. Механизм перехода требуется не только для реализации явных событий, связанных с кнопками, но и для пассивного чтения при просмотре автоматизированного слайд-шоу.

Итак, рассмотрим пару булевых методов, сообщающих читателю, может ли он переходить вперед или назад. Реализация этих двух методов hasPrevious и hasNext показана в листинге 13.31.

560 Часть IV Ajax в примерах

 

... ЛИСТИЙГ 13.31. Пара методов has Preyious/has Next

: ,

hasPrevious: function() {

 

return ! (this.feedlndex == 0 && this .iteinlndex == 0);

 

},

 

hasNext: function() {

return !(this.feedlndex == this.options.rssFeeds.length - 1 && this.itemlndex == this.currentFeed.items.length - 1);

}, щ

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

Рассмотрим теперь, что такое переход на предыдущую и последующую статью ленты. Начнем с метода previous (), показанного в листинге 13.32.

I Листинг 13.32. Метод previous ()Г ^ 1 Д | Д

previous: function() {

if ( !this.hasPrevious() ) return;

var requiresLoad = this.itemlndex == 0;

this.fadeOut( this.visibleLayer, Prototype.emptyFunction ); this.visibleLayer = {this.visibleLayer + 1 ) % 2;

if { requiresLoad )

this.loadRSSFeedf this.feedlndex - 1, false ); else

setTimeout( this.previousPartTwo.bind(this), parselnt (this. options. fadeDuration/4) ). ;

Ь

previousPartTwo: function()

{

 

this.itemlndex'—; this.updateView();

Ь

 

m

Первое, что мы сделали при написании метода previous (), — поместили в самом его начале защитное условие. Если предыдущей статьи не существует, метод previous () ничего не делает. Если значение requiresLoad равно true, тогда содержимое RSS-статьи, на которую планируется переход, еще не загружено. Если мы находимся на первой статье ленты и переходим назад, требуется загрузка предыдущей ленты. Метод затухания, подробно рассмотренный в разделе "Эффекты перехода", постепенно ослабляет видимый слой. Дальнейшие действия этого метода зависят от того, требуется ли перед отображением загрузка какого-либо содержимого. Если да, то мы инициируем загрузку необходимых данных посредством метода loadRSSFeed(). Первым параметром данного метода является номер загружаемой ленты, вторым — булево значение, указывающее направление: true — вперед, false (как в данном случае) — назад. Если же содержимое статьи уже загружено, то мы вызываем previousPartTwo() после паузы, равной одной четвертой общей длительности fadeDuration. Во "второй части" данного метода про-

Глава 13 Создание приложений Ajax, не использующих сервер 561

сто обновляется свойство itemlndex и вызывается функция updateView (), приводящая к затуханию соответствующего слайда.

Ну что, запутались? Ладно, объясняем нормальным языком: если текст, который нужно отобразить, не загружен, его загрузка начинается немедленно, что приводит к обновлению пользовательского интерфейса сразу же после поступления данных. Время, которое требуется на прием данных, используется для естественной реализации затухания! С другой стороны, если содержимое уже загружено {т.е. мы переходим на другую статью загруженной ленты), то мы вводим искусственную задержку (четверть времени затухания) перед проявлением следующей статьи. Довольно хитро, правда?

Метод next (), показанный в листинге 13.33, представляет собой реализацию алгоритма, обратного к приведенному выше.

next:

function{}

{

 

if

(

ithis.hasNext()

) return;

var

requiresLoad =

 

 

this.itemlndex == (this.currentFeed.items.length - 1);

 

this.fadeOut( this.visibleLayer, Prototype.emptyFunction ) ;

 

this.visibleLayer = (this.visibleLayer + 1 ) % 2;

if

(

requiresLoad >

 

 

this.loadRSSFeed{

this.feedlndex + 1, true );

else

 

 

 

setTimeout(

this.nextPartTwo.bind(this),

 

 

 

parselnt(this.options.fadeDuration/4) );

Ь

nextPartTwo: function() {

 

this.itemlndex++; this.updateView();

 

Ь

m

Выглядит знакомо? Метод next () использует противоположную логику индексирования, а во всем остальном идентичен приведенному выше алгоритму. Обратите внимание на то, что при каждом переходе пара методов previous () /next {) переключает видимый слой с одного слайда на другой с помощью такого выражения:

this.visibleLayer = (this.visibleLayer + 1) % 2;

Таким образом, мы сообщаем коду, который в конечном счете обновит пользовательский интерфейс (после загрузки содержимого или явного вызова функции updateView ()), на какой слой помещать результат. Напомним, что контекстная область приложения содержит HTML-разметку, которые выглядит примерно следующим образом:

<!— Контекстная область —>

<div class="content" id="rssReader_content"> <div class="layerl">Layer 0</div>

<div class="layer2">Layer K/div> </div>

Здесь visibleLayer — целочисленное свойство, отслеживающее, в какой элемент div требуется помещать содержимое. Индекс 0 указывает, что при об-