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

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

димости представлять пользователю все свойства объекта. Выбрать свойства для отображения можно, присваивая объекту перед воспроизведением специальное свойство, представляющее собой массив. Этот подход иллюстрируется кодом, приведенным в листинге 4.12.

Листинг 4.12. Применение свойства objviewspec

objviewer.ObjectViewer=function(obj,div,isInline,addNew){ styling.removeAllChildren(div) ;

this.object=obj;

this.spec=objviewer.getSpec(obj);

this.mainDiv=div;

this.mainDiv.viewer=this;

this.islnline=islnline;

this.addNew=addNew;

var table=document.createElement("table"); this.tbod=document.createElement("tbody") ; table.appendChiId(this.tbod);

this. fields=new Array (); this.children=new Array();

for (var i=0,-i<this.spec.length;i++){ this.fields[i]=new objviewer.PropertyViewer(

this,this.specfi]

);

}

objviewer.getSpec=function (obj){ return (obj.objViewSpec) ?

obj.objViewSpec : objviewer.autoSpec(obj) ;

} objviewer.autoSpec=function(obj){ var members=new Array();

for (var propName in obj){ var spec={name:propName}; members.append(spec);

}

return members;

}

obj viewer.PropertyViewer=function(obj ectViewer,memberSpec) { this.objectViewer=objectViewer;

this.spec=memberSpec;

this.name=this.spec.name;

}

Мы определили свойство objViewSpec, которое используется конструктором Objectviewer. Если данное свойство отсутствует, оно формируется в функции autoSpecO путем анализа объекта. Свойство objViewSpec представляет собой массив, каждый элемент которого является таблицей свойств. В данном случае мы генерируем только свойство name. Конструктору PropertyViewer передается описание свойства, из которого можно извлечь инструкции о порядке воспроизведения.

Предоставляя спецификацию для объекта, обрабатываемого Objectviewer, мы можем ограничить набор отображаемых свойств.

Глава 4. Web-страница в роли приложения 177

Вторая проблема, связанная с использованием ObjectViewer, состоит в том, что этот объект плохо обрабатывает сложные свойства. При добавлении к строке объектов, массивов и функций вызывается метод toString (). В случае объекта она возвращает совершенно неинформативное описание типа [Object object]. Для объекта Function возвращается исходный код функции. По этой причине необходимо различать разные типы свойств. Сделать это можно с помощью оператора instanceof. Учитывая выше названные особенности, рассмотрим, как можно улучшить средства отображения объекта.

4.5.2. Обработка массивов и объектов

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

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

objviewer.TYPE_SIMPLE="simple";:

objviewer.TYPE_ARRAY="array"; objviewer.TYPE_FUNCTION="function"; obj viewer.TYPE_IMAGE_URL="image ur1" ; objviewer.TYPE_OBJECT="object";

Модифицируем функцию, которая генерирует спецификации объектов, не имеющих собственных описаний, как показано в листинге 4.13.

Листинг 4.13. Модифицированная функция autoSpec()

objviewer.autoSpec=function(obj){ var members=new Array();

for (var propName in obj){ var propValue=obj[name];

var propType=objviewer.autoType(value); var spec={name:propName,type:propType}; members.append(spec);

}

if (obj && obj.length>0){ for(var i=0;i<obj.length;i++){

var propName="array ["+!+"]"; var propValue=obj[i];

var propType=objviewer.ObjectViewer.autoType(value); var spec={name:propName,type:propType}; members.append(spec);

)

1

return members;

}

objviewer.autoType=function(value){ var type=objviewer.TYPE_SIMPLE; if ((value instanceof Array)){

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

type=objviewer.TYPE_ARRAY;

J e l s e i f

(value

i n s t a n c e o f

F u n c t i o n ) {

type=objviewer.TYPE_FUNCTION;

}els e i f

(value

i n s t a n c e o f

Object){

type=objviewer.TYPE_OBJECT;

}

 

 

 

r e t u r n t y p e ;

 

 

}

 

 

_

Заметьте, что мы также реализовали поддержку массивов с числовыми индексами, элементы которых нельзя обработать в цикле for... in.

Далее нам необходимо изменить объект PropertyViewer так, чтобы он учитывал различия типов и соответственно воспроизводил данные. Модифицированный вариант конструктора PropertyViewer показан в листинге 4.14.

Листинг4.14. Модифицированный конструктор PropertyViewer

objviewer.PropertyViewer=function

(objectViewer,memberSpec,appendAtTop){

this.objectViewer=objectViewer; this.spec=memberSpec; * this.name=this.spec.name; this.type=this.spec.type; this.value=objectViewer.object[this.name]; this.rowTr=document.createElement("tr") ;

this.rowTr.className='objViewRow' ;

var isComplexType=(this.type==objviewer.TYPE_ARRAY

I| this.type==objviewer.TYPE_OBJECT); if ( !(isComplexType &&

this.objectViewer.isInline )

this.nameTd=this.renderSideHeader() ; this.rowTr.appendChild(this.nameTd);

this.valTd=document.createElement("td") ;

this.valTd.className='obj ViewValue• ; this.valTd.viewer=this; this.rowTr.appendChild(this.valTd) ; if (isComplexType){

if (this.viewer.islnline){ this.valTd.colSpan=2 ;

var nameDiv=this.renderTopHeader(); this.valTd.appendChild(nameDiv);

var valDiv=this.renderInlineObject(); this.valTd.appendChild(valDiv);

}else{

var valDiv=this.renderPopoutObject(); this.valTd.appendChild(valDiv);

else if (this.type==objviewer.TYPE_IMAGE_URL){

var vallmg=this.renderlmage();

Глава 4. Web-страница в роли приложения 179

this.valTd.appendChild(vallmg);

}

else if (this.type==objviewer.TYPE_SIMPLE){

var valTxt=this.renderSimple(); this.valTd.appendChild(valTxt);

}

if (appendAtTop){

styling.insertAtTop(viewer.tbod,this.rowTr);

}else{

viewer.tbod.appendChild(this.rowTr);

}

}

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

Итак, мы практически полностью решили задачу автоматизации представления модели. Для того чтобы объекты модели были видимыми, нам осталось лишь связать свойства objViewSpec с прототипами. Объект Planet, показанный на рис. 4.7, содержит в составе конструктора следующее'выражение:

this.objViewSpec=[

 

 

{name:"name",

type:"simple"},

{name:"distance",

type:"simple",

e d i t a b l e : t r u e } ,

 

 

{name:"diameter",

type:"simple",

e d i t a b l e : t r u e } ,

 

 

{name:"image",

type:"image

url"} ,

{name:"facts",

type:"array",

addNew:this.newFact,

i n l i n e : t r u e } ];

 

 

Для данной спецификации использован принцип записи JSON (JavaScript object notation). Квадратные скобки обозначают числовой массив, а фигурные скобки — ассоциативный массив объектов (на самом деле оба типа массивов реализуются одинаково). Более подробно мы обсудим JSON в приложении Б.

В данном фрагменте кода остались некоторые выражения, которые мы еще не рассмотрели. Например, что означают addNew, inline и editable? Они оповещают представление о том, что данные разделы модели могут не только просматриваться, но и модифицироваться пользователем, а это область ответственности контроллера. Использованию контроллера посвящен следующий раздел.

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

4.5.3. Включение контроллера

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

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

ЛИСТИНГ4.15.ФункцияrenderSimple()

objviewer.PropertyViewer.prototype.renderSimple=function(){ var valDiv=document.createElement("div");

// Отображение значения, предназначенного для чтения var valTxt=document

. createTextNode(this.value); valDiv.appendChild(valTxt);

//О Если допустимо редактирование,

//добавляются интерактивные средства if (this.spec.editable){

valDiv.className+=" editable"; valDiv.viewer=this;

valDiv.onclick=obj viewer.PropertyViewer.editSimpleProperty ;

}

return valDiv;

}

// © Начало редактирования

obj viewer.PropertyViewer.editSimpleProperty=function(e){

var viewer=this.viewer; if (viewer){

viewer.edit();

}

}

obj viewer.PropertyViewer.prototype.edit=function(){ if (this.type=objviewer.TYPE_SIMPLE){

var editor=document.createElement("input") ; editor.value=this.value; document.body.appendChild(editor);

var td=this.valTd; xLeft(editor,xLeft(td)); xTop(editor,xTop(td)) ; xWidth(editor,xWidth(td)); xHeight(editor,xHeight(td));

//© Замена метки на поле редактирования td.replaceChild(editor,td.firstChild);

//О Связывание обработчика обратного вызова

Глава 4. Web-страница в роли приложения 181

editor.onblur=objviewer.

PropertyViewer.editBlur;

editor.viewer=this;

editor.focus();

}

}

// © Окончание редактирования objviewer.PropertyViewer

.editBlur=function(e){ var viewer=this.viewer; if (viewer){

viewer.coiranitEdit(this.value);

}

}

objviewer.PropertyViewer.prototype.commitEdit=function(value){ if (this.type==objviewer.TYPE_SIMPLE){

this.value=value;

var valDiv=this.renderSimple(); var td=this.valTd;

td.replaceChild(valDiv,td.firstChild); // О Оповещение обработчиков

this.obj ectViewer

.notifyChange(this);

}

}

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

Функция editSimpleProperty() © представляет собой простой обработчик событий. Он извлекает из узла DOM, на котором пользователь щелкнул мышью, ссылку на объект PropertyViewer и вызывает метод e d i t ( ) . Способ связывания представления с контроллером знаком вам по разделу 4.3.1. Мы проверяем корректность типа свойства, а затем заменяем метку, допускающую только чтение, полем HTML-формы соответствующего размера, обеспечивающим редактирование. В этом поле отображается значение свойства ©. Мы также связываем с текстовой областью обработчик onblur О, который заменяет редактируемую область меткой, допускающей только чтение ©, и обновляет модель.

Таким образом, мы можем обрабатывать модель, но в общем случае при обновлении модели бывает необходимо выполнять другие действия. В этом случае включается в работу метод notifyChangeO объекта Ob j ectViewer ©, вызываемый из функции commitEdit (). Соответствующий код показан в листинге 4.16.

Листинг 4.16. Функция ObjectViewer.noti£yChange() obj viewer.Obj ectViewer.prototype

.notifyChange=function(propViewer){ if (this.onchangeRouter){

this.onchangeRouter.notify(propViewer);

}

if (this.parentObjViewer){ this.parentObjViewer.notifyChange(propViewer) ;

}

} obj viewer.Obj ectViewer.prototype

.addChangeListener=function(lsnr){ if (!this.onchangeRouter){

this.onchangeRouter=new

jsEvent.EventRouter(this, "onchange");

}

this . onchangeRouter . addListener(lsnr);

}obj viewer.Obj ectViewer.prototype

.removeChangeListener=function(lsnr){

if (this.onchangeRouter){

this.onchangeRouter.removeListener(lsnr) ;

}

}

Проблема, с которой мы столкнулись, а именно оповещение процессов об изменениях в модели, идеально решается посредством образа разработки Observer и объекта EventRouter, о котором шла речь в разделе 4.3.3. Мы могли бы связать EventRouter с событием onblur редактируемых полей, однако сложная модель обычно предполагает большое их количество, а наш код не имеет доступа к деталям реализации Obj ectViewer.

Вместо этого мы определим в Obj ectViewer событие специального типа, onchange, и свяжем с ним EventRouter. Поскольку наши объекты ОЬjectViewer включены в состав древовидной структуры (это сделано для просмотра свойств, представляющих собой объекты и массивы), событие onchange должно передаваться родительским объектам. В общем случае мы можем присоединить обработчик к корневому объекту Obj ectViewer, который мы создаем в приложении, и получать изменения свойств модели на более нижних уровнях.

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

var topview=new objviewer.ObjectViewer (planets.solarSystem,mainDiv);

topview.addChangeListener(testListener);

где testListener — функция поддержки события, которая выглядит следующим образом:

function testListener(propviewer){ window.status=propviewer.name+" ["+propviewer.type+"J =

"+propviewer.value;

}

Глава 4. Web-страница в роли приложения 183

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

4Л Резюме

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

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

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

Что касается модели, то мы сделали первые шаги по пути создания распределенного многопользовательского приложения. Разговор на эту тему будет продолжен в главе 5.

Для создания модели, представления и контроллера приходится затрачивать много усилий. Обсуждая объект Objectviewer, мы выяснили, что существуют способы, позволяющие автоматизировать взаимодействие элементов архитектуры MVC. Мы создали простую систему, способную представлять модель и позволяющую пользователю взаимодействовать с ней.

Разговор об образах разработки мы продолжим в следующей главе. В ней мы перейдем к рассмотрению взаимодействия клиента и сервера.

4.7. Ресурсы

Библиотека Behaviour, использованная в данной главе, находится по адресу http://ripcord.co.nz/behaviour/. Библиотеку х Майка Фостера можно скопировать, обратившись к серверу http://www.cross-browser.com.

Попытки реализовать автоматическую генерацию представления на основе модели были предприняты в рамках проекта Naked Objects (http://

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

www.nakedobjects.org/). Книга Ричарда Посона (Richard Pawson) и Роберта Метьюса (Robert Matthews) Naked Objects (John Wiley <fe Sons, 2002) немного устарела, но в ней можно найти полезные сведения, касающиеся разработки компонентов архитектуры "модель-представление—контроллер".

Изображения планет, использованные для демонстрации возможностей Object Viewer, были предоставлены Jim's Cool Icons (http://snaught.com/ jimsCoolicons/) и, как сказано на Web-узле, смоделированы с помощью POV-Ray, а в качестве текстуры использованы реальные изображения, полученные от NASA.

I

Роль сервера Ajax-приложепия

Вэтой главе ...

Использование существующих архитектур

вAjax-приложении

Обмен содержимым, кодом сценариев

иданными с сервером

Передача обновленных данных серверу

Объединение нескольких запросов

в одном HTTP-обращении