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

Глава 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");

}

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

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;

a l e r t ( " f a i l e d

to update "

+ t h i s . f i e l d + "

to "+this . value

+ "\n\n"+reason);

}

}

Данный объект предоставляет уникальный идентификатор команды и инкапсулирует параметры, необходимые серверу. Функция toRequeststring() оформляет сама себя в виде фрагмента XML-кода, используя специальную функцию, которую мы присоединили к прототипу Object.

Object.prototype.simpleXmlify=function(tagname){ var xml="<"+tagname;

for

(i in

this){

if

(!this[i] instanceof Function)

{

xml+="

"+i+"=\""+this[i]+"\"";

}

 

 

}

xml+="/>";- return xml;

)

В результате создается простой XML-дескриптор (для удобства восприятия он отформатирован вручную).

<command type='update Property' id='001_diameter' planetld='mercury' field='diameter'

value='3'

/>

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

В составе запроса POST, передаваемого серверу, может содержаться один или несколько описанных выше дескрипторов, в зависимости от частоты опроса и активности пользователя. Сервер обрабатывает каждую команду и сохраняет результаты, формируя ответ. Обработчик onload, принадлежащий CommandQueue, распознает дескрипторы в ответе, сравнивает их с объектами Command в массиве sent и вызывает метод parseResponse () объекта Command. Ответ может выглядеть следующим образом:

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

<commands>

<command id='001_diameter' status='ok'/> <command id='003_albedo'

s t a t u s = ' f a i l e d '

message='value out of range'/>

<command id='004_hairColor'

s t a t u s = ' f a i l e d '

message='invalid property name'/>

< / cornmands >

Как видно из сообщения, информация о диаметре Меркурия была обновлена, но другие два обновления отклонены. Причины описаны посредством атрибута message. Пользователь оповещается о возникающих проблемах (обычно для этого используется функция a l e r t ()) и может предпринять ответные действия.

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

Листинг 5.15. Содержимое файла CommandServlet .Java public class CommandServlet extends HttpServlet

{

private Map commandTypes=null;

public void init() throws ServletException

{

ServletConfig config=getServletConfig(); // О Конфигурация обработчиков commandTypes=new HashMap();

boolean more=true;

f o r ( i n t counter=l;more;counter++){

String typeName=config.getlnitParameter("type"+counter); String typelmpl=config.getlnitParameter("impl"+counter);

if (typeName==null || typelmpl==null){ more=false;

}

else

{

try{

Class cls=Class.forName(typelmpl); commandTypes.put(typeName,els);

}

catch (ClassNotFoundException clanfex)

{

t h i s . l o g (

"couldn'tresolve handler class name"+typelmpl);

}

}

}

}

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

protected void doPost

(

HttpServletRequest req, HttpServletResponse resp

)

throws IOException{

// 0 Обработать запрос resp.setContentType("text/xml"); Reader reader=req.getReader(); Writer writer=resp.getWriter() ;

try{ SAXBuilder builder=new SAXBuilder(false);

// © Обработать XML-данные Document doc=builder.build(reader); Element root=doc.getRootElement();

if ("commands".equals(root.getName())){

for(Iterator iter=root.getChildren("command").iterator(); iter.hasNext();)

{

Element el=(Element)(iter.next());

String type=el.getAttributeValue("type"); XMLCommandProcessor command=getCommand(type,writer); if (command!=null)

{

// О Делегировать обработку

Element result=command.processXML(el); writer.write(result.toString());

}

}

}

else

{

sendError(writer,

"incorrect document format - " +"expected top-level command tag");

}

}

catch (JDOMException jdomex){

sendError(writer,"unable to parse request document");

}

}

private XMLCommandProcessor getCommand (String type,Writer writer)

throws IOException{

// © Соответствие обработчика команде XMLCommandProcessor cmd=null;

Class cls=(Class)(commandTypes.get(type)); if (cls!=null){

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

229

try{ cmd=(XMLCommandProcessor)(els.newlnstance()) ; }catch (ClassCastException castex){

sendError(writer,

"class "+cls.getName() +" is not a command");

}

catch (InstantiationException instex)

{

sendError(writer,

"not able to create class "+cls.getName()); } catch (IllegalAccessException illex)

{

sendError(writer,

"not allowed to create class "+cls.getName());

}

}else{

sendError(writer,"no command type registered for "+type);

}

 

 

 

return

cmd;

}

 

 

 

p r i v a t e v o i d s e n d E r r o r

 

(Writer

w r i t e r , S t r i n g message) throws IOException{

 

w r i t e r . w r i t e ( " < e r r o r msg='"+message+" ' / > " ) ;

 

w r i t e r . f l u s h ( ) ;

}

 

 

_ }

 

 

 

 

Данный сервлет поддерживает карту объектов XMLCommandProcessor, для конфигурирования которой используется интерфейс ServletConfig О. При использовании более мощных базовых средств можно воспользоваться для этой цели конфигурационным XML-файлом. При обработке запроса POST © для разбора XML-данных используется JDOM ©, а затем осуществляется перебор дескрипторов <coramand>, для которых атрибуты соответствуют объекту обработчика XMLCommandProcessor О. В карте содержатся определения классов, на основе которых в методе getCommand () мы создаем конкретные экземпляры, используя механизм отражения ©.

В интерфейсе XMLCommandProcessor объявлен единственный метод.

public interface XMLCommandProcessor { Element processXML(Element el); }

В данном интерфейсе предполагается, что для представления XMLДанных будут применяться библиотеки JDOM. Объектами Element являются как параметры, так и возвращаемое значение. Простой класс, реализующий Данный интерфейс и предназначенный для обновления данных о планетах, показан в листинге 5.16.

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

Листинг 5.16. Содержимое файла PlanetUpdateCoimandProcessor. Java public class PlanetUpdateCommandProcessor

implements XMLCommandProcessor

{

public Element processXML(Element el)

{

//О Создать XML-узел результатов Element result=new Element("command"); String id-el.getAttributeValue("id"); result.setAttribute("id", id);

String status=null; String reason=null;

String planetld=el.getAttributeValue("planetld"); String field=el.getAttributeValue("field"); String value=el.getAttributeValue("value");

//@ Обращение к модели предметной области Planet planet=findPlanet(planetld);

if (planet==null){ status-"failed";

reason="no planet found for id "+planetld; }else{

Double numValue=new Double(value); Object[] args=new Object[]{ numValue

};

String method = "set"+field.substring(O,1).toUpperCase() +field.substring(1);

Statement statement=new Statement(planet,method,args); try {

// © Обновить модель предметной области statement.execute();

status="ok";

} catch (Exception e) { status="failed";

reason="unable to set value "+value+" for field "+field;

}

}

result.setAttribute("status",status); if (reason!=null){

result.setAttribute("reason",reason);

}

return result;

}

private Planet findPlanet(String planetld)

{

// О Использовать ORM для модели предметной области return null;

}

}

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