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

238 Часть III. Создание профессиональных Ajax-приложений

Вглаве 1 мы обсуждали практичность — одно из основных качеств прикладной программы. Независимо от того, насколько грамотно написан код какие применены технические решения, если приложение недостаточно практично, у пользователя останется плохое впечатление от него. Может быть программистам это и покажется несправедливым, но положение вещей именно таково. Глядя на фотографию Альберта Эйнштейна, обыватель вряд ли поймет, что этот человек внес огромный вклад в формирование научной картины мира, но отметит его неряшливый внешний вид и всклокоченные волосы. Первое впечатление о человеке или предмете очень важно для формирования отношения к нему.

Вглавах 2-5 мы рассмотрели важные технологии и решили ряд интересных задач, пользуясь подходом, специфическим для Ajax. Дальнейший нащ разговор пойдет в основном о том, как оформить их. Надо заметить, что рассмотренные примеры внешне выглядят достаточно грубо. Это не удивительно, ведь мы уделяли основное внимание изяществу самого решения. Теперь нам надо выяснить, что мы можем сделать, чтобы пользователю понравилось созданное нами приложение и он согласился работать с ним по нескольку часов в день. Темы, рассмотренные в данной главе, призваны помочь читателю правильно представить Ajax-приложение внешнему миру.

Одна из мер, которые необходимо принять для того, чтобы ваши пользователи чувствовали себя комфортно, — информировать их о событиях, "скрытых" за интерфейсом. Более того, сделать это надо так, чтобы представленная информация органично сочеталась с остальными данными, отображаемыми в интерфейсе. Сведения об основных процессах, происходящих при работе программы, сами по себе еще не обеспечивают практичности приложения, но мы покажем, что подробная проработка деталей идет на пользу продукту в целом. Во многих Ajax-приложениях принимаются меры для оповещения пользователей, поэтому мы надеемся, что выработанные здесь решения вы сможете применить в своих проектах.

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

икак сделать его таковым.

6.1.Создание качественного приложения

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

Глава 6. Информация для пользователя 239

ловятся похожи друг на друга. Создать в сложившихся условиях хороший интерфейс — непростая задача. Если вы не сможете ее решить, результаты ваших усилий по разработке бизнес-логики окажутся невостребованными.

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

6.1.1. Отклик программы

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

Планируя время отклика, важно представлять себе типичную аудиторию вашей системы. Если запись конфигурационного файла на локальный жесткий диск выполняется практически мгновенно, то запись того же файла на удаленный носитель в условиях большой загрузки локальной сети произойдет значительно медленнее, запись во флэш-память, подключенную черед порт USB, имеет свои особенности, и т.д. Разработчики Web-приложений при тестировании своих продуктов часто организуют взаимодействие клиента и сервера через интерфейс обратной петли; Это ошибка, так как становится невозможно оценить задержку, связанную с передачей данных. Поэтому все Webприложения должны тестироваться в реальной сети или хотя бы в системе, имитирующей реальное прохождение трафика по линиям связи.

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

6.1.2. Надежность

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

240 Часть III. Создание профессиональных Ajax-приложений

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

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

6.1.3. Согласованность

Как уже упоминалось выше, на практичность Ajax-приложений существенное влияние оказывает тенденция к сближению программ интерфейсов для настольных систем и Web-страниц. Некоторые инструменты, ориентированные на Ajax, например Bindows, qooxdoo и Backbase, даже предлагают наборы компонентов, которые выглядят подобно кнопкам, деревьям, таблицам и другим элементам пользовательского интерфейса, типичным для настольных систем.

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

Глава 6. Информация для пользователя 241

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

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

6.14. Простота

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

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

6.1.5. Как получить результат

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

242 Часть III. Создание профессиональных Ajax-приложений

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

Рассмотрим различные способы оповещения пользователя о событиях, возникающих во время работы программы.

6.2. Предоставление сведений пользователю

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

6.2.1. Поддержка ответов на собственные запросы

Рассмотрим конкретный пример. В главе 5 мы создали приложение для просмотра информации о планетах. Оно позволяло пользователям обновлять некоторые свойства, в частности, задавать диаметр планеты и ее расстояние от Солнца (см. раздел 5.5). Информация, введенная пользователем, передавалась на сервер, а в ответе сервера содержались сведения о том, приняты или отвергнуты изменения. В разделе 5.5.3 было введено понятие очереди команд; следуя этому принципу, мы рассматривали каждый ответ сервера как оповещение о некоторых обновлениях, выполненных по инициативе конкретного пользователя. Ответ представлял собой XML-документ, содержащий информацию об успешно выполненных или отвергнутых командах.

<commands>

<command id='001_diameter' status='ok' /> <command id='003_albedo' status»'failed' message='value out of range'/> </commands>

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

Глава 6. Информация для пользователя 243

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

Давайте вспомним, какой вид имел код приложения перед началом реструктуризации. Ниже показан код метода parseResponse () объекта Command. Именно так мы сформировали его в главе 5.

planets.commands.UpdatePropertyCommand

.parseResponse=function(docEl){ var attrs=docEl.attributes;

var status=attrs.getNamedItem("status").value; if (status!="ok"){

var reason=attrs.getNamedItem("message").value; alert("failed to update "+this.field

+" to "+this.value+"\n\n"+reason);

}

}

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

Давайте вернемся к пользователю, который уже успел забыть о планете Меркурий. Неожиданно он получает сообщение наподобие следующего: Failed to update albedo to 180 value out of range. Вне контекста такое сообщение совершенно неинформативно. Мы можем изменить текст, чтобы он выглядело приблизительно так: Failed to update albedo of Mercury..., но при этом действия пользователя все равно прерываются, а именно этого мы и хотели избежать, переходя к асинхронной обработке сообщений.

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

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

244Часть III. Создание профессиональных Ajax-приложений

12.2.Обработка обновлений, выполненных другими пользователями

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

Для того чтобы учесть данное требование, мы должны модифицировать [ML-ответ и очередь объектов Command следующим образом. Для каждого зароса, предполагающего обновление модели предметной области на стороне ервера, должна генерироваться метка с информацией о времени (временная [етка). Процесс, выполняющийся на стороне сервера, который учитывает бновления, должен проверять модель, выявлять недавние изменения, внеенные другими пользователями, и включать их в возвращаемый документ. Ъперь полученный ответ будет выглядеть приблизительно так, как показа- о ниже.

•«responses updateTime='1120512761877'> <command id='001_diameter' status='ok'/> <command id='003_albedo' status='failed'

message='value out of range'/>

<update planetId='OO2' fieldName='distance1 value='0.76' user='jim'/>

</responses>

Помимо дескрипторов <command>, содержащих идентификаторы объектов ommand, в составе ответа присутствует также дескриптор <update>, который данном случае определяет расстояние от Венеры до Солнца. Значение этого войства, равное 0,76, задал пользователь Jim. Кроме того, мы можем добаить к дескриптору верхнего уровня атрибуты, состав и назначение которых ы обсудим несколько позже.

Ранее запрос передавался серверу только в том случае, если в очереди рисутствовали команды. Теперь нам надо получать сведения об обновлении, следовательно, придется запрашивать сервер даже тогда, когда очередь пуга. Для того чтобы это стало возможным, нам надо изменить код в нескольих местах. Модифицированный объект CommandQueue показан в листинге 6.1. [зменения выделены полужирным шрифтом.

ЛИСТИНГ 6.1. Объект CommandQueue // О Глобальная ссылка

net.cmdQueues-new Array() ;

// © Дополнительные параметры функции

n e t . C o m m a n d Q u e u e = f u n c t i o n ( i d , u r l , o n U p d a t e , freq){ t h i s . i d = i d ;

net . cmdQueues[id] - this ; t h i s . u r l = u r l ;

this.queued=new Array(); this.sent=new Array();

Глава 6. Информация для пользователя 245

this.onUpdate-onOpdate; // 0 Инициализировать

повторные обращения if (freq){ this.repeat(freq);

}

this.lastUpdateTime»O;

}

net.CommandQueue.prototype.fireRequest=function(){ if (!this.onUpdate && this.queued.length==0){ return;

}

// Временная метка var data=

"lastUpdate«"+this.lastUpdateTime +"&data=";

for(var i=0;i<this.queued.length;i++){ var cmd=this.queued[i];

if (this.isCommand(cmd)){ data+=cmd.toRequestString(); this.sent[cmd.id]=cmd;

}

}

this.queued=new ArrayO; this.loader=new net.ContentLoader( this.url,

net.CommandQueue.onload,net.CommandQueue.onerror,

"POST",data

);

}

net.CommandQueue.onload=function(loader) { var xmlDoc=net.req.responseXML;

var elDocRoot=xmlDoc.getElementsByTagName("responses")[0];

var lastUpdate=elDocRoot.attributes.getNamedItem("updateTime") ; if (parseInt(lastUpdate)>this.lastUpdateTime){

// О Обновленная временная метка this.lastUpdateTime=lastUpdate;

}

if (elDocRoot){

for(i=0; KelDocRoot.childNodes.length;i++){ elChild=elDocRoot.childNodes[i];

if (elChild.nodeName«-"command"){

var attrs=elChild.attributes;

var id=attrs.getNamedItem("id").value; var command=net.CommandQueue.sent[id] ; if (command){ command.parseResponse(elChild);

}

}else if (elChild.nodeName=="update"){ if (this.implementsFunc("onUpdate")){ // © Обновленный обработчик this.onUpdate.call(this,elChild);

}

}

246 Часть III. Создание профессиональных Ajax-приложений

)

}

}

// 0 Опрос сервера net.CommandQueue.prototype.repeat=function(freq){

this.unrepeat(); if (freq>0){ this.freq-freq;

var cmd="net.cmdQueues["+this.id+"].fireRequest{)" ; this.repeater=setlnterval(cmd,freq*1000);

}

}

// О Включение/отключение повторных обращений net.CommandQueue.prototype.unrepeat=function(){

if (this.repeater){ clearInterval(this.repeater);

}

this.repeater=null;

1

 

Рассмотрим новые функции, которые мы реализовали, изменив код. Во-первых, введена глобальная ссылка на очередь объектов Command О.

С необходимостью этого шага приходится смириться, учитывая ограничения метода setlnterval (), которые мы рассмотрим несколько позже. Конструктору CommandQueue передается в качестве параметра уникальный идентификатор, и объект регистрируется в элементе массива, соответствующем идентификатору.

Теперь при вызове конструктора CommandQueue указываются два новых параметра ®. В качестве параметра onUpdate передается объект Function, используемый для поддержки дескрипторов <update>, который теперь может присутствовать в составе ответа. Параметр freq задает числовое значение, определяющее интервал в секундах между последовательными обращениями к серверу за информацией об обновлении. Если значение не равно нулю, конструктор инициализирует обращения к функции repeat () ©, в которой для организации повторного выполнения кода использован встроенный метод JavaScript setlnterval. Метод setlnterval () и метод setTimeout, предоставляющий аналогичные возможности, допускают параметры, заданные лишь в виде строк, поэтому нельзя непосредственно передавать ссылки на код, подлежащий выполнению. Чтобы обойти эту проблему, мы используем в теле функции repeat () глобальную переменную и уникальный идентификатор. Мы также сохраняем ссылку на временной интервал и можем в любой момент прекратить периодический опрос сервера, вызвав функцию unrepeat () в, в теле которой осуществляется обращение к clearlnterval.

Ранее, если очередь команд оказывалась пустой, метод f ireRequest О завершал работу. Теперь проверка выполняется несколько по-другому. При установленном обработчике onUpdate работа функции продолжается, независимо от того, есть ли в очереди элементы. Если очередь пуста, то серверу передается пустой запрос, на который сервер передает ответ, содержащий дескрипторы <update>. Вместе с результатами редактирования мы теперь по-

Глава 6. Информация для пользователя

247

сылаем значение времени, сообщая серверу о моменте внесения изменений ©. Эту информацию сервер использует для того, чтобы правильно сформировать передаваемую информацию об обновлении. Значение времени хранится в виде свойства очереди команд и первоначально устанавливается равным нулю.

Информацию о времени мы передаем в формате, принятом в системе Unix для хранения даты, т.е. сообщаем число миллисекунд, прошедших с 1 января 1970 года. Такой выбор продиктован соображениями переносимости. Если бы мы выбрали формат более удобный для чтения, нам пришлось бы уделять внимание интернационализации и обеспечению адекватного представления на других платформах. Вопросы интернационализации имеют большое значение для Aj ax-приложений, так как для большинства из них планируется доступ по глобальной сети.

В функции onload () мы добавили код, необходимый для обновления сохраненной информации о времени О и для разбора дескрипторов <update> 0. Функция обработчика onUpdate вызывается в контексте очереди команд, а элемент DOM, соответствующей дескриптору <update>, передается ей в качестве параметра.

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

Листинг 6.2. Функция updatePlanets()

Д Ц :

function updatePlanets(updateTag){

var

attribs=updateTag.attributes;

var

planetld=attribs.getNamedltemC'planetld").value;

var

planet=solarSystem.planets[planetld];

 

if

(planet){

 

 

 

var

fld=attribs.getNamedItem("fieldName").value;

var

val=attribs.getNamedItem("value").value;

 

if

(planet.fid){

 

 

 

planet[fId]=val;

 

 

 

 

}else{

 

 

 

 

alert('unknown planet

attribute

*+fld);

}

 

 

 

 

 

}else{

 

 

 

 

alert('unknown planet

id '+planetld);

}

 

 

 

 

)

 

 

 

я

Атрибуты дескриптора <update> предоставляют нам всю информацию, необходимую для обновления модели предметной области на уровне JavaScript. Конечно, данные, полученные с сервера, могут быть некорректными, поэтому нам надо предусмотреть на этот случай соответствующие действия. Например, можно вывести сообщение с описанием проблемы, формируемое с помощью функции a l e r t (). Этот вопрос обсуждался в разделе 6.2.1.

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