- •Введение
- •Несколько слов о книге
- •Глава 1. Каким должен бытъ Web-интерфейс
- •Действия пользователя при работе с приложением
- •Накладные расходы при работе в сети
- •Асинхронное взаимодействие
- •Независимый и переходный образы использования
- •Четыре основных принципа Ajax
- •Браузер имеет дело с приложением, а не с содержимым
- •Сервер доставляет данные, а не содержимое
- •Реальное кодирование требует порядка
- •Применение богатых клиентов Ajax
- •Системы, созданные с использованием Ajax
- •Google Maps
- •Альтернативные технологии
- •Macromedia Flash
- •Java Web Start
- •Резюме
- •Ресурсы
- •Основные элементы Ajax
- •JavaScript изучался не зря
- •Определение внешнего вида с помощью CSS
- •Селекторы CSS
- •Свойства стилей
- •Простой пример использования CSS
- •Обработка DOM с помощью JavaScript
- •Поиск узла DOM
- •Создание узла DOM
- •Добавление стилей к документу
- •Свойство innerHTML
- •Асинхронная загрузка с использованием XML
- •Элементы IFrame
- •Объекты XmlDocument и XMLHttpRequest
- •Использование фуниции обратного вызова для контроля запроса
- •Жизненный цикл процедуры поддержки запроса
- •Отличия Ajax от классических технологий
- •Резюме
- •Ресурсы
- •Порядок из хаоса
- •Образы разработки
- •Реструктуризация и Ajax
- •Во всем надо знать меру
- •Реструктуризация в действии
- •Варианты применения реструктуризации
- •Несоответствие браузеров: образы разработки Fagade и Adapter
- •Управление обработчиками событий: образ разработки Observer
- •Повторное использование обработчиков событий: образ разработки Command
- •Обеспечение единственной ссылки на ресурс: образ разработки Singleton
- •"Модель-представление-контроллер "
- •Серверная программа Ajax, созданная без применения образов разработки
- •Реструктуризация модели
- •Разделение содержимого и представления
- •Библиотеки независимых производителей
- •Библиотеки, обеспечивающие работу с различными браузерами
- •Компоненты и наборы компонентов
- •Элементы, располагаемые на стороне сервера
- •Резюме
- •Ресурсы
- •Применение архитектуры MVC к программам различных уровней
- •Применение архитектуры MVC к объектам, присутствующим в среде браузера
- •Представление в составе Ajax-приложения
- •Отделение логики от представления
- •Отделение представления от логики
- •Контроллер в составе Ajax-приложения
- •Классические JavaScript-обработчики
- •Модель обработки событий W3C
- •Реализация гибкой модели событий в JavaScript
- •Модель в составе Ajax-приложения
- •Использование JavaScript для моделирования предметной области
- •Взаимодействие с сервером
- •Генерация представления на основе модели
- •Отражение объектов JavaScript
- •Обработка массивов и объектов
- •Включение контроллера
- •Резюме
- •Ресурсы
- •Программы, выполняемые на сервере
- •Создание программ на стороне сервера
- •N-уровневые архитектуры
- •Управление моделью предметной области на стороне клиента и на стороне сервера
- •Принципы создания программ на сервере
- •Серверные программы, не соответствующие основным принципам разработки
- •Использование архитектуры Model!
- •Использование архитектуры на базе компонентов
- •Архитектуры, ориентированные на использование Web-служб
- •Частные решения: обмен данными
- •Взаимодействие, затрагивающее только клиентскую программу
- •Пример отображения информации о планетах
- •Взаимодействие, ориентированное на содержимое
- •Взаимодействие, ориентированное на сценарий
- •Передача данных серверу
- •Использование HTML-форм
- •Использование объекта XMLHttpRequest
- •Управление обновлением модели
- •Резюме
- •Ресурсы
- •Создание качественного приложения
- •Отклик программы
- •Надежность
- •Согласованность
- •Простота
- •Как получить результат
- •Предоставление сведений пользователю
- •Поддержка ответов на собственные запросы
- •Обработка обновлений, выполненных другими пользователями
- •Создание системы оповещения
- •Основные принципы оповещения
- •Реализация базовых средств оповещения
- •Отображение пиктограмм в строке состояния
- •Отображение подробных сообщений
- •Формирование готовой системы
- •Предоставление информации в запросах
- •Информация о новизне данных
- •Простой способ выделения данных
- •Выделение данных с использованием библиотеки Scriptaculous
- •Резюме
- •Ресурсы
- •JavaScript и защита браузера
- •Политика "сервера-источника"
- •Особенности выполнения сценариев в Ajax-приложении
- •Проблемы с поддоменами
- •Взаимодействие с удаленным сервером
- •Взаимодействие с Web-службами
- •Защита конфиденциальной информации
- •Вмешательство в процесс передачи данных
- •Организация защищенного НТТР-взаимодействия
- •Передача шифрованных данных в ходе обычного HTTP-взаимодействия
- •Управление доступом к потокам данных Ajax
- •Создание защищенных программ на уровне сервера
- •Ограничение доступа к данным из Web
- •Резюме
- •Ресурсы
- •Что такое производительность
- •Скорость выполнения JavaScript-программ
- •Определение времени выполнения приложения
- •Использование профилировщика Venkman
- •Оптимизация скорости выполнения Ajax-приложения
- •Использование памяти JavaScript-кодом
- •Борьба с утечкой памяти
- •Особенности управления памятью в приложениях Ajax
- •Разработка с учетом производительности
- •Простой пример управления памятью
- •Как уменьшить объем используемой памяти в 150 раз
- •Резюме
- •Ресурсы
- •Сценарий двойной комбинации
- •Недостатки клиентского решения
- •Недостатки клиентского решения
- •Архитектура клиента
- •Разработка взаимодействия клиент/сервер
- •Реализация сервера: VB.NET
- •Написание кода сервера
- •Представление результатов
- •Применение каскадных таблиц стилей
- •Дополнительные вопросы
- •Запросы при выборе нескольких элементов
- •Переход от двойного связного выбора к тройному
- •Реструктуризация
- •Новый и улучшенный объект netContentLoader
- •Создание компонента двойного списка
- •Резюме
- •Глава 10. Опережающий ввод
- •Изучаем опережающий ввод
- •Типичные элементы приложений опережающего ввода
- •Google Suggest
- •Ajax как средство опережающего ввода
- •Структура серверной части сценария: С#
- •Сервер и база данных
- •Тестирование серверного кода
- •Структура клиентской части сценария
- •HTML
- •JavaScript
- •Обращение к серверу
- •Дополнительные возможности
- •Реструктуризация
- •День 1: план разработки компонента TextSuggest
- •День 3: включаем Ajax
- •День 4: обработка событий
- •День 5: пользовательский интерфейс всплывающего окна с предлагаемыми вариантами
- •Итоги
- •Резюме
- •Эволюционирующий портал
- •Классический портал
- •Портал с богатым пользовательским интерфейсом
- •Создание портала с использованием Java
- •Таблица пользователя
- •Серверная часть кода регистрации: Java
- •Структура регистрации (клиентская часть)
- •Реализация окон DHTML
- •База данных окон портала
- •Серверный код окна портала
- •Добавление внешней библиотеки JavaScript
- •Возможность автоматического сохранения
- •Адаптация библиотеки
- •Автоматическая запись информации в базе данных
- •Реструктуризация
- •Определение конструктора
- •Адаптация библиотеки AjaxWindows.js
- •Задание команд портала
- •Выводы
- •Резюме
- •Понимание технологий поиска
- •Классический поиск
- •"Живой" поиск с использованием Ajax и XSLT
- •Возврат результатов клиенту
- •Код клиентской части сценария
- •Настройка клиента
- •Инициализация процесса
- •Код серверной части приложения: РНР
- •Создание XML-документа
- •Создание документа XSLT
- •Объединение документов XSL и XML
- •Совместимость с браузером Microsoft Internet Explorer
- •Совместимость с браузерами Mozilla
- •Последние штрихи
- •Применение каскадных таблиц стилей
- •Улучшение поиска
- •Поддержка браузерами Opera и Safari
- •Использовать ли XSLT
- •Решение проблемы закладок
- •Реструктуризация
- •Объект XSLTHelper
- •Компонент "живого" поиска
- •Выводы
- •Резюме
- •Считывание информации из внешнего мира
- •Поиск XML-лент
- •Изучение структуры RSS
- •Богатый пользовательский интерфейс
- •Чтение лент
- •HTML-структура без таблиц
- •Гибкое CSS-форматироеание
- •Глобальный уровень
- •Предварительная загрузка средствами Ajax
- •Богатый эффект перехода
- •Правила прозрачности, учитывающие индивидуальность браузеров
- •Реализация затухающего перехода
- •Интеграция таймеров JavaScript
- •Дополнительные возможности
- •Введение дополнительных лент
- •Интеграция функций пропуска и паузы
- •Как избежать ограничений проекта
- •Обход системы безопасности браузеров Mozilla
- •Изменение масштаба приложения
- •Реструктуризация
- •Модель приложения
- •Представление приложения
- •Контроллер приложения
- •Выводы
- •Резюме
- •Отладчики
- •Для чего нужен отладчик
- •Средство Safari DOM Inspector для Mac OS X
- •Ресурсы
- •JavaScript — это не Java
- •Формирование объектов
Глава 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;
}