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

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

}

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