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

216 Часть II. Основные подходы к разработке приложений

var jsonObj=eval("("+jsonTxt+")") ;

Заметьте, что перед обработкой выражения нам надо поместить его в скобки. Теперь мы можем обращаться к свойствам объекта по имени, а это позволяет сократить размеры кода и сделать его более удобным для восприятия, чем методы обработки структуры DOM, которые мы использовали при работе с XML. Здесь метод showPopup() не приводится, поскольку он выглядит точно так же, как и в листинге 5.7.

Какой же вид имеют JSON-данные? В листинге 5.10 показана информация о планете Земля, представленная как строка JSON.

Листинг 5.10. Содержимое файла earth, json

{ "planet":

{

"name": "earth", "type": "small", "info":

[

"Earth is a small planet, third from the sun", "Surface coverage of water is roughly two-thirds",

"Exhibits a remarkable diversity of climates and landscapes"

]

 

 

_J}

-

 

 

 

 

С помощью фигурных скобок определяются ассоциативные

массивы,

а квадратные скобки обозначают массивы с числовыми индексами. Допускается вложенность любых видов скобок. В приведенном примере мы определили объект planet, содержащий три свойства. Свойства name и type представляют собой обычные строки, а свойство info является массивом.

Формат JSON используется реже, чем XML, но JSON-данные могут быть обработаны любым интерпретатором JavaScript, включая Mozilla Rhino на базе Java и Microsoft JScript .NET. Библиотеки JSON-RPC содержат средства разбора JSON для различных языков программирования (соответствующие ссылки приведены в конце данной главы), а также инструмент JavaScript "Stringiner", предназначенный для преобразования JavaScriptобъектов в строки JSON. Таким образом, JSON можно рассматривать как формат для двустороннего взаимодействия. Если интерпретатор JavaScript доступен и на стороне клиента, и на стороне сервера, целесообразно выбрать формат JSON. В рамках проекта JSON-RPC были также разработаны библиотеки, предназначенные для разбора и генерации JSON-сообщений и ориентированные на языки, которые часто используются при разработке программ, выполняемых на стороне сервера.

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

Глава 5. Роль сервера в работе Ajax-приложения

217

Использование XSLT

Альтернативой написанию программ для обработки дерева DOM и создания flTML-данных (этот подход был описан в разделе 5.7.3) является использование XSLT-преобразования для автоматического конвертирования XMLинформации в формат XHTML. Этот подход представляет собой нечто среднее между взаимодействием, ориентированным на данные, и взаимодействием ориентированным на содержимое. С точки зрения сервера эта архитектура ориентирована на данные, а с точки зрения клиента — на содержимое. Такой подход позволяет быстрее и проще получить требуемый результат, но для него характерны те же ограничения, что и для взаимодействия, ориентированного на содержимое, а именно, данные ответа затрагивают лишь прямоугольную область в окне. XSLT-преобразование будет подрой гее обсуждаться в главе 11.

Проблемы и ограничения

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

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

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

5.5. Передача данных серверу

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

Рассмотрим сначала обновление с учетом изменений, которые мы вносим сами. Существуют два механизма передачи данных на сервер: HTML-формы и объект XMLHttpRequest. Рассмотрим кратко каждый из них.

218 Часть II. Основные подходы к разработке приложений

5.5.1. Использование HTML-форм

В классическом Web-приложении элементы HTML-форм реализуют стандартный механизм ввода данных пользователем. Элементы формы объявляются с помощью средств разметки HTML.

<form method="POST" action="myFormHandlerURL.php">

<input type="text" name="username"/> <input type="password" name="password"/> <input type="submit" value="login"/>

</form>

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

иletmein и активизирует кнопку submit, будет сформирован HTTP-запрос POST и передан ресурсу myFormHandlerURL.php. В теле запроса будет содержаться текст username=dave&password=letmein. В большинстве систем Webпрограммирования разработчику непосредственно не доступны закодированные данные формы. Вместо этого ему предоставляются пары имя-значения в виде ассоциативного массива или специальных переменных.

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

<form id="myForm" method="POST" action="" onsubmit="validateForm(); return false;">

<input type="text" name="username"/> <input type="password" name="password"/> <input type="submit" value="login"/>

</form>

В этом случае функция, выполняющая проверку, будет иметь следующий вид:

function validateForm(){

var form=document.getElementById('myForm') ; var user=form.elements[0].value;

var pwd=form.elements[1].value;

if (user && user.length>0 && pwd && pwd.length>0){ form.action='myFormHandlerURL.php'; form.submit();

}

else

{

a l e r t ( " p l e a s e f i l l in your credentials before logging i n " ) ;

}

}

Первоначально форма определяется без атрибута action. Обращение по реальному URL будет произведено только в случае положительного результата проверки. С помощью JavaScript-кода можно также модифицировать форму, организовав блокирование кнопки submit для предотвращения повторной передачиданных, кодирование пароля перед формированием запроса и т.д. Эти и многие другие приемы многократно описаны в литературе, поэтому

Глава 5. Роль сервера в работе Ajax-приложения 219

^ibi не будем рассматривать их здесь. В главах 9 и 10 приведены примеры расширения возможностей HTML-форм.

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

Листинг 5.11. Функция submitData() function addParam(form,key,value)

{

var input=document.createElement("input"); input.name=key;

input.value=value;

form.appendChild(input);

}

function submitData(url,data)

{

var form=document.createElement("form"); form.action=ur1;

form.method="POST"; for (var i in data){

addParam(form,i,data[i]);

}

form.style.display="none";

document.body.appendChild(form);

form.submit();

_}

Функция submitData () создает форму и в цикле включает в нее данные, используя для этого функцию addParam (). Обращение к функции submitData () имеет следующий вид:

submitData(

"myFormHandlerURL.php",

{username:"dave",password:"letmein"}

);

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

5.5.2. Использование объекта XMLHttpRequest

Объект XMLHttpRequest уже рассматривался нами в этой главе, а также в главе 2. С точки зрения клиентского кода разница между чтением и обновлением незначительна. Нам надо лишь указать метод POST и передать параметРы формы.

220Часть II. Основные подходыкразработке приложений

Влистинге 5.12 показан код объекта ContentLoader, который мы раз-Я работали в разделе 3.1. Мы несколько изменили его для того, чтобы можнпИ было включать в запрос параметры и указывать произвольный HTTP-метод

Листинг 5.12. Объект ContentLoader

/ / О Для конструктора предусмотрены дополнительные параметры net.ContentLoader=function

(url,onload,onerror, method,params,contentType)

this.onload=onload; this.onerror=(onerror) ? onerror : this.defaultError;

this.loadXMLDoc(url,method,params,contentType); } net.ContentLoader.prototype.loadXMLDoc

=function(ur1,method,params,contentType){ if (!method){

method="GET";

if (icontentType && method=="POST"){ contentType="application/x-www-form-urlencoded";

if (window.XMLHttpRequest){ this.req=new XMLHttpRequest();

else

if (window.ActiveXObject){

this.req=new ActiveXObject("Microsoft.XMLHTTP");

if (this.req){ try{

this.req.onreadystatechange=net.ContentLoader.onReadyState;

// НТТР-метод

this.req.open( method,url,true);

// Тип содержимого if (contentType){

this.req.setRequestHeader("Content-Type", contentType);

// Параметры запроса this.req.send( params);

}catch (err){ this.onerror.call(this) ;

}

_ •

. ;

Глава 5. Роль сервера в работе Ajax-приложения

221

Конструктору объекта мы передаем несколько новых параметров

О.

Из них обязательными являются URL (в соответствии с атрибутом action формы) и обработчик onload. Можно также задать HTTP-метод, параметры и тип запроса. Заметьте, что при передаче пар ключ-значение посредством метода POST надо указать тип содержимого application/x-www-f ormurlencoded. Если при вызове функции тип явно не задан, мы устанавливаем это значение автоматически. HTTP-метод используется при вызове метода open () объекта XMLHttpRequest а параметры — при обращении к методу send( ). Таким образом, вызов конструктора имеет следующий вид:

var loader=net.ContentLoader( 'myFormHandlerURL.php', showResponse, null, ' POST', 'username=dave&password=letmein'

); В результате выполнения этого выражения будет сформирован такой же

запрос, как и при использовании метода submitData(), представленного в листинге 5.11. Заметьте, что параметры передаются в виде строки, которая кодируется так же, как и при передаче данных формы, например:

name=daveSjob=book&work=Ajax_In+Action

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

5.5.3. Управление обновлением модели

В главе 3 мы рассмотрели универсальный объект Objectviewer, предназначенный для представления сложных моделей, и обсудили простой пример,

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

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

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

вбраузере строки состояния.) Средства обработки событий почти идеально подходят для передачи серверу информации об обновлениях.

222 Часть II. Основные подходы к разработке приложений

Предположим, что на сервере выполняется сценарий updateDomainModel. jsp, который получает от клиента следующую информацию.

Уникальный идентификатор планеты, информация о которой подлежит обновлению.

Имя обновляемого свойства.

Значение, присвоенное свойству.

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

function updateServer(propviewer)

{

var planetObj=propviewer.viewer.object; var planetId=planetObj.id;

var propName=propviewer.name; var val=propviewer.value; net.ContentLoader

(

'updateDomainModel.jsp', someResponseHandler, null, 'POST', 'planetId='+encodeURI(planetId) +'&propertyName='+encodeURI(propName)

+'&value='+encodeURI(val)

);

}

Этот обработчик надо связать с ObjectViewer. myObjectViewer.addChangeListener(updateServer) ;

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

ЛИСТИНГ 5.13. Объект CommandQueue

/ / О Создать обьехт очереди net.CommandQueue=function(id, u r l , freq)

{

t h i s . i d = i d ; net.cmdQueues[id]=this; this.url=url; this.queued=new Array(); this.sent=new Array(); if (freq)

{

this.repeat(freq);

}

}

Глава 5. Роль сервера в работе Ajax-приложения 223

// 0 Передать запрос серверу net.CommandQueue.prototype.addCommand=function(command){

if (this.isCommand(command))

{

this.queue.append(command,true);

}

}

net.CommandQueue.prototype.fireRequest=function(){ if (this.queued.length==0)

{

return;

}

var data="data=";

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

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.onerro: "POST",data

);

}

// © Проверить тип объекта net.CommandQueue.prototype.isCommand=function(obj)

{

return

(

obj.implementsPropC'id")

&&obj.implementsFunc("toRequestString")

&&obj.implementsFunc("parseResponse")

);

}

// О Выполнить разбор ответа сервера net.CommandQueue.onload=function(loader)

{

var xmlDoc=net.req.responseXML;

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

{

for(i=0;KelDocRoot.childNodes.length; i++)

{

elChild=elDocRoot.childNodes[i]; if (elChild.nodeName=="command")

224 Часть II. Основные подходы к разработке приложений

{

var attrs=elChild.attributes;

var id=attrs.getNamedItem("id").value; var command=net.commandQueue.sent[id]; if (command)

{

command.parseResponse(elChild);

}

}

}

}

}

net.CommandQueue.onerror=function(loader){ alert("problem sending the data to the server"); }

// © Опрос сервера 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) ;

}

}

// 0 Отключить опрос сервера net.CommandQueue.prototype.unrepeat=function()

{

if (this.repeater)

{

clearlnterval(this.repeater) ;

}

this.repeater=null;

При инициализации О объекта CommandQueue (он назван так потому, что в очереди содержатся объекты Command) указывается уникальный идентификатор, URL сценария на стороне сервера и в качестве необязательного параметра — флаг, указывающий на необходимость повторного опроса. Алы тернативный вариант — активизация опроса вручную. Каждый из этих режимов может быть полезен, поэтому оба они предусмотрены в коде объекта. Когда очередь формирует запрос серверу, все содержащиеся в ней команды преобразуются в строки и передаются серверу ©.

В составе объекта содержатся два массива. Массив queued предполагает указание числовых индексов; в него помещаются новые обновления. Массив sent — ассоциативный. Он содержит те обновления, которые были отправлены серверу, но ответы на которые еще не были получены. В обоих массивах содержатся объекты Command, интерфейс которых определяется функцией isCommand() ©. Эти объекты обладают следующими свойствами.

Глава 5. Роль сервера в работе Ajax-приложения 225

Объект может иметь уникальный идентификатор.

Объект допускает сериализацию для включения в состав запроса POST ©.

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

Для проверки выполнения условий используется функция implementsFunc(). Поскольку данный метод принадлежит базовому классу Object, может показаться, что он является стандартным инструментом JavaScript, но на самом деле мы объявляем функцию implements Func О в составе вспомогательной библиотеки.

Obj ect.prototype.implementsFunc=function(funcName){ return this[funcName] && this[funcName] instanceof

Function; }

Прототипы JavaScript подробно описаны в приложении Б. Теперь вернемся к объекту, реализующему очередь. Метод onload очереди О ожидает ответа сервера, содержащего XML-документ. В составе документа должны присутствовать дескрипторы <command>, помещенные в дескриптор <commands>.

Методы repeat () © и unrepeat () © используются для управления объектом таймера при периодическом опросе сервера.

Объект Command, предназначенный для обновления свойств объекта, описывающего планету, показан в листинге 5.14.

Листинг 5.14. Объект UpdatePropertyCommand planets.commands.UpdatePropertyCommand=function(owner,field,value)

{

this.id=this.owner.id+"_"+field; this.obj=owner; this.field-field; this.value=value;

}

planets.commands.UpdatePropertyCommand.toRequestString=

function()

{

return

{

type:"updateProperty",

id:this.id,

planetld:this.owner.id,

field:this.field,

value:this.value

}.simpleXmlifу("command");

}