- •Введение
- •Несколько слов о книге
- •Глава 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
- •Формирование объектов
242 Часть III. Создание профессиональных Ajax-приложений
Далее в этой главе будет рассмотрен ряд средств, которые можно реализовать в рамках Ajax-приложения. Мы уделим много внимания оповещению пользователя о процессах, скрытых от него, например, о вычислениях или обмене по сети. Предоставляя пользователю информацию о ходе работы программы, мы повышаем качество взаимодействия с приложением. Реализуя оповещение посредством общего базового набора средств, мы обеспечиваем согласованность и упрощаем работу пользователя. Если все сообщения оформлены по одному и тому же принципу, пользователь лучше воспринимает их.
Рассмотрим различные способы оповещения пользователя о событиях, возникающих во время работы программы.
6.2. Предоставление сведений пользователю
При работе Ajax-приложения очень часто возникает необходимость обращения по сети и загрузки ресурсов с сервера. В получении результатов обычно участвуют функции обратного вызова. При синхронной обработке запросов к серверу процесс взаимодействия по сети так или иначе находит отражение в пользовательском интерфейсе. Инициализация запроса приводит к тому, что элементы интерфейса блокируются и в течение некоторого времени не реагируют на действия пользователя. После получения результатов содержимое окна клиентской программы обновляется и пользователь снова получает возможность взаимодействовать с приложением. Подобное решение реализуется очень просто, однако пользоваться такой программой неудобно. По этой причине мы будем использовать асинхронные запросы, однако при этом процесс обновления данных существенно усложняется.
6.2.1. Поддержка ответов на собственные запросы
Рассмотрим конкретный пример. В главе 5 мы создали приложение для просмотра информации о планетах. Оно позволяло пользователям обновлять некоторые свойства, в частности, задавать диаметр планеты и ее расстояние от Солнца (см. раздел 5.5). Информация, введенная пользователем, передавалась на сервер, а в ответе сервера содержались сведения о том, приняты или отвергнуты изменения. В разделе 5.5.3 было введено понятие очереди команд; следуя этому принципу, мы рассматривали каждый ответ сервера как оповещение о некоторых обновлениях, выполненных по инициативе конкретного пользователя. Ответ представлял собой XML-документ, содержащий информацию об успешно выполненных или отвергнутых командах.
<commands>
<command id='001_diameter' status='ok' /> <command id='003_albedo' status»'failed' message='value out of range'/> </commands>
Пользователь, работающий с приложением, редактирует свойство и принимается за другую задачу, может быть, даже переходит к другому окну. На-
Глава 6. Информация для пользователя 243
пример, обновив сведения о диаметре планеты Меркурий, он больше не уделяет внимание этому вопросу. В это время значение для обновления оформляется в фоновом режиме в виде JavaScript-объекта Command, который помещается в очередь исходящих сообщений, а затем передается серверу. После этого объект Command перемещается в массив sent и извлекается оттуда при получении ответа. В составе ответа может содержаться информация сразу о нескольких обновлениях. Объект Command отвечает за обработку обновления и выполнение необходимых действий.
Давайте вспомним, какой вид имел код приложения перед началом реструктуризации. Ниже показан код метода parseResponse () объекта Command. Именно так мы сформировали его в главе 5.
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; alert("failed to update "+this.field
+" to "+this.value+"\n\n"+reason);
}
}
Начнем его реструктуризацию. Если обновление было осуществлено успешно, ничего не происходит. Локальная модель предметной области уже была обновлена перед передачей информации на сервер, поэтому модели на стороне клиента и на стороне сервера синхронизированы. В случае неудачи мы генерируем сообщение. Такое сообщение легко сформировать, но, как мы увидим, оно плохо вписывается в концепцию практичной программы.
Давайте вернемся к пользователю, который уже успел забыть о планете Меркурий. Неожиданно он получает сообщение наподобие следующего: Failed to update albedo to 180 value out of range. Вне контекста такое сообщение совершенно неинформативно. Мы можем изменить текст, чтобы он выглядело приблизительно так: Failed to update albedo of Mercury..., но при этом действия пользователя все равно прерываются, а именно этого мы и хотели избежать, переходя к асинхронной обработке сообщений.
В данном случае может возникнуть и более серьезная проблема. При реализации полей редактирования передача данных серверу инициируется по событию onblur. Метод onblur () получает управление тогда, когда поле редактирования теряет фокус ввода, причем потеря фокуса может быть вызвана появлением окна с сообщением. Таким образом, если пользователь отредактировал свойство и начал вводить значение другого, ввод может прерваться на середине. В результате неполные данные будут переданы на сервер, а это приведет к генерации следующей ошибки, или, что еще хуже, модель может быть обновлена на основе некорректной информации.
Необходимо более элегантное решение, чем обычное окно с сообщением. Но, прежде чем заняться его разработкой, завершим разговор об обновлении Модели. Рассмотрим ситуацию, когда несколько пользователей одновременно обновляют модель предметной области.
244Часть III. Создание профессиональных Ajax-приложений
12.2.Обработка обновлений, выполненных другими пользователями
'усматриваемое приложение допускает одновременную работу различных юльзователей, поэтому редактирование данных может осуществляться сра- у с нескольких клиентских машин. Каждый пользователь хотел бы иметь перативную информацию об изменениях, внесенных другими участниками. 1одобные требования типичны для большинства приложений, предполагающих взаимодействие браузера с Web-сервером.
Для того чтобы учесть данное требование, мы должны модифицировать [ML-ответ и очередь объектов Command следующим образом. Для каждого зароса, предполагающего обновление модели предметной области на стороне ервера, должна генерироваться метка с информацией о времени (временная [етка). Процесс, выполняющийся на стороне сервера, который учитывает бновления, должен проверять модель, выявлять недавние изменения, внеенные другими пользователями, и включать их в возвращаемый документ. Ъперь полученный ответ будет выглядеть приблизительно так, как показа- о ниже.
•«responses updateTime='1120512761877'> <command id='001_diameter' status='ok'/> <command id='003_albedo' status='failed'
message='value out of range'/>
<update planetId='OO2' fieldName='distance1 value='0.76' user='jim'/>
</responses>
Помимо дескрипторов <command>, содержащих идентификаторы объектов ommand, в составе ответа присутствует также дескриптор <update>, который данном случае определяет расстояние от Венеры до Солнца. Значение этого войства, равное 0,76, задал пользователь Jim. Кроме того, мы можем добаить к дескриптору верхнего уровня атрибуты, состав и назначение которых ы обсудим несколько позже.
Ранее запрос передавался серверу только в том случае, если в очереди рисутствовали команды. Теперь нам надо получать сведения об обновлении, следовательно, придется запрашивать сервер даже тогда, когда очередь пуга. Для того чтобы это стало возможным, нам надо изменить код в нескольих местах. Модифицированный объект CommandQueue показан в листинге 6.1. [зменения выделены полужирным шрифтом.
ЛИСТИНГ 6.1. Объект CommandQueue // О Глобальная ссылка
net.cmdQueues-new Array() ;
// © Дополнительные параметры функции
n e t . C o m m a n d Q u e u e = f u n c t i o n ( i d , u r l , o n U p d a t e , freq){ t h i s . i d = i d ;
net . cmdQueues[id] - this ; t h i s . u r l = u r l ;
this.queued=new Array(); this.sent=new Array();
Глава 6. Информация для пользователя 245
this.onUpdate-onOpdate; // 0 Инициализировать
повторные обращения if (freq){ this.repeat(freq);
}
this.lastUpdateTime»O;
}
net.CommandQueue.prototype.fireRequest=function(){ if (!this.onUpdate && this.queued.length==0){ return;
}
// Временная метка var data=
"lastUpdate«"+this.lastUpdateTime +"&data=";
for(var i=0;i<this.queued.length;i++){ var cmd=this.queued[i];
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.onerror,
"POST",data
);
}
net.CommandQueue.onload=function(loader) { var xmlDoc=net.req.responseXML;
var elDocRoot=xmlDoc.getElementsByTagName("responses")[0];
var lastUpdate=elDocRoot.attributes.getNamedItem("updateTime") ; if (parseInt(lastUpdate)>this.lastUpdateTime){
// О Обновленная временная метка this.lastUpdateTime=lastUpdate;
}
if (elDocRoot){
for(i=0; KelDocRoot.childNodes.length;i++){ elChild=elDocRoot.childNodes[i];
if (elChild.nodeName«-"command"){
var attrs=elChild.attributes;
var id=attrs.getNamedItem("id").value; var command=net.CommandQueue.sent[id] ; if (command){ command.parseResponse(elChild);
}
}else if (elChild.nodeName=="update"){ if (this.implementsFunc("onUpdate")){ // © Обновленный обработчик this.onUpdate.call(this,elChild);
}
}
246 Часть III. Создание профессиональных Ajax-приложений
)
}
}
// 0 Опрос сервера 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);
}
}
// О Включение/отключение повторных обращений net.CommandQueue.prototype.unrepeat=function(){
if (this.repeater){ clearInterval(this.repeater);
}
this.repeater=null;
—1 |
|
• |
Рассмотрим новые функции, которые мы реализовали, изменив код. Во-первых, введена глобальная ссылка на очередь объектов Command О.
С необходимостью этого шага приходится смириться, учитывая ограничения метода setlnterval (), которые мы рассмотрим несколько позже. Конструктору CommandQueue передается в качестве параметра уникальный идентификатор, и объект регистрируется в элементе массива, соответствующем идентификатору.
Теперь при вызове конструктора CommandQueue указываются два новых параметра ®. В качестве параметра onUpdate передается объект Function, используемый для поддержки дескрипторов <update>, который теперь может присутствовать в составе ответа. Параметр freq задает числовое значение, определяющее интервал в секундах между последовательными обращениями к серверу за информацией об обновлении. Если значение не равно нулю, конструктор инициализирует обращения к функции repeat () ©, в которой для организации повторного выполнения кода использован встроенный метод JavaScript setlnterval. Метод setlnterval () и метод setTimeout, предоставляющий аналогичные возможности, допускают параметры, заданные лишь в виде строк, поэтому нельзя непосредственно передавать ссылки на код, подлежащий выполнению. Чтобы обойти эту проблему, мы используем в теле функции repeat () глобальную переменную и уникальный идентификатор. Мы также сохраняем ссылку на временной интервал и можем в любой момент прекратить периодический опрос сервера, вызвав функцию unrepeat () в, в теле которой осуществляется обращение к clearlnterval.
Ранее, если очередь команд оказывалась пустой, метод f ireRequest О завершал работу. Теперь проверка выполняется несколько по-другому. При установленном обработчике onUpdate работа функции продолжается, независимо от того, есть ли в очереди элементы. Если очередь пуста, то серверу передается пустой запрос, на который сервер передает ответ, содержащий дескрипторы <update>. Вместе с результатами редактирования мы теперь по-
Глава 6. Информация для пользователя |
247 |
сылаем значение времени, сообщая серверу о моменте внесения изменений ©. Эту информацию сервер использует для того, чтобы правильно сформировать передаваемую информацию об обновлении. Значение времени хранится в виде свойства очереди команд и первоначально устанавливается равным нулю.
Информацию о времени мы передаем в формате, принятом в системе Unix для хранения даты, т.е. сообщаем число миллисекунд, прошедших с 1 января 1970 года. Такой выбор продиктован соображениями переносимости. Если бы мы выбрали формат более удобный для чтения, нам пришлось бы уделять внимание интернационализации и обеспечению адекватного представления на других платформах. Вопросы интернационализации имеют большое значение для Aj ax-приложений, так как для большинства из них планируется доступ по глобальной сети.
В функции onload () мы добавили код, необходимый для обновления сохраненной информации о времени О и для разбора дескрипторов <update> 0. Функция обработчика onUpdate вызывается в контексте очереди команд, а элемент DOM, соответствующей дескриптору <update>, передается ей в качестве параметра.
Для приложения, содержащего модель Солнечной системы, функция, которая выполняет роль обработчика обновления, представлена в листинге 6.2.
Листинг 6.2. Функция updatePlanets() |
Д Ц : |
||||
function updatePlanets(updateTag){ |
|||||
var |
attribs=updateTag.attributes; |
||||
var |
planetld=attribs.getNamedltemC'planetld").value; |
||||
var |
planet=solarSystem.planets[planetld]; |
||||
|
if |
(planet){ |
|
|
|
var |
fld=attribs.getNamedItem("fieldName").value; |
||||
var |
val=attribs.getNamedItem("value").value; |
||||
|
if |
(planet.fid){ |
|
|
|
planet[fId]=val; |
|
|
|
||
|
}else{ |
|
|
|
|
|
alert('unknown planet |
attribute |
*+fld); |
||
} |
|
|
|
|
|
|
}else{ |
|
|
|
|
|
alert('unknown planet |
id '+planetld); |
|||
} |
|
|
|
|
|
) |
|
|
|
я |
Атрибуты дескриптора <update> предоставляют нам всю информацию, необходимую для обновления модели предметной области на уровне JavaScript. Конечно, данные, полученные с сервера, могут быть некорректными, поэтому нам надо предусмотреть на этот случай соответствующие действия. Например, можно вывести сообщение с описанием проблемы, формируемое с помощью функции a l e r t (). Этот вопрос обсуждался в разделе 6.2.1.
Теперь мы реализовали более сложное поведение очереди объектов Command, в частности, предусмотрели поддержку обновлений, выполненных другими пользователями, включили в состав данных, которыми обмениваются клиент и сервер, информацию о времени и предусмотрели включаемый об-