Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ajax_v_deystvii.pdf
Скачиваний:
34
Добавлен:
05.03.2016
Размер:
5.83 Mб
Скачать

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]