Виктор Вахтуров
Санкт-Петербург
«БХВ-Петербург»
2007
УДК 681.3.06
ББК 32.973.26-018.1
В22
Вахтуров В. В.
В22 JavaScript. Освой на примерах. — СПб.:
БХВ-Петербург, 2007. — 400 с.: ил. + CD-ROM
ISBN 978-5-94157-877-1
На наглядных примерах даны практические приемы програм-
мирования клиентских сценариев для Web-браузеров. Кратко из-
ложены основы создания Web-страниц и скриптов: язык
JavaScript, каскадные таблицы стилей (CSS) и объектная модель
документа (DOM). Рассмотрено решение типовых задач про-
граммирования скриптов: работа с датой и временем, cookies,
регулярными выражениями и протоколами. Даны примеры соз-
дания динамических эффектов: управление окном браузера, раз-
работка динамических форм, средства и способы работы с изо-
бражениями, анимационные эффекты, реализация перетаскивания
(Drag and Drop), эмуляция элементов управления пользователь-
ского интерфейса. Рассмотрено написание функционально закон-
ченных приложений: реализация визуального редактора HTML и
нескольких известных игр на JavaScript. Исходные тексты всех
примеров находятся на прилагаемом компакт-диске.
Для широкого круга Web-программистов
УДК 681.3.06
ББК 32.973.26-018.1
Группа подготовки издания:
Главный редактор Екатерина Кондукова
Зам. главного редактора Евгений Рыбаков
Зав. редакцией Григорий Добин
Редактор Анна Кузьмина
Компьютерная верстка Ольги Сергиенко
Корректор Зинаида Дмитриева
Дизайн обложки Игоря Цырульникова
Оформление обложки Елены Беляевой
Зав. производством Николай Тверских
Лицензия ИД № 02429 от 24.07.00. Подписано в печать 22.03.07.
Формат 60 901/16. Печать офсетная. Усл. печ. л. 25.
Тираж 2500 экз. Заказ №
"БХВ-Петербург", 194354, Санкт-Петербург, ул. Есенина, 5Б.
Санитарно-эпидемиологическое заключение на продукцию
№ [Link].Д.006421.11.04 от 11.11.2004 г. выдано Федеральной службой
по надзору в сфере защиты прав потребителей и благополучия человека.
Отпечатано с готовых диапозитивов
в ГУП "Типография "Наука"
199034, Санкт-Петербург, 9 линия, 12
ISBN 978-5-94157-877-1 © Вахтуров В. В., 2007
© Оформление, издательство "БХВ-Петербург", 2007
Оглавление
Введение ............................................................................................. 1
О чем и для кого эта книга ............................................................................1
Как организована книга, и как ее читать ......................................................2
Что содержится на компакт-диске ................................................................5
Благодарности ................................................................................................5
ЧАСТЬ I. ОСНОВЫ СОЗДАНИЯ WEB-СТРАНИЦ
И СКРИПТОВ ................................................................................... 7
Глава 1. Структура и синтаксис языка JavaScript .................... 9
Определения и термины ................................................................................9
Структура языка ...........................................................................................12
JavaScript и HTML................................................................................12
Способы внедрения JavaScript-кода в документы HTML ............12
Порядок исполнения скриптов в документе ..................................16
Основные характеристики языка ........................................................17
Регистрозависимость .......................................................................17
Интерпретация пробельных символов ...........................................17
Комментарии ....................................................................................18
Запись инструкций, символы-разделители ....................................18
Операторные блоки, составной оператор ......................................19
Ключевые и зарезервированные слова .......................................................19
Переменные и константы ............................................................................20
Правила записи имен ...........................................................................20
Объявление переменных и констант ..................................................21
Область видимости переменных и констант......................................21
IV Оглавление
Типы данных ................................................................................................22
Специальные типы данных..................................................................23
Тип данных null ...............................................................................23
Тип данных undefined ......................................................................23
Скалярные типы данных ......................................................................24
Логический тип данных boolean.....................................................24
Числовой тип данных ......................................................................25
Строки...............................................................................................26
Массивы ........................................................................................................27
Объекты ........................................................................................................29
Основные понятия ................................................................................29
Свойства ...........................................................................................29
Методы .............................................................................................30
Создание объектов ...............................................................................30
Создание объектов в литеральной нотации ...................................30
Создание объектов оператором new. Конструкторы объектов.
Типы объектов .................................................................................31
Наследование ........................................................................................33
Операторы ....................................................................................................34
Структуры управления.................................................................................37
Конструкции условного исполнения ..................................................37
Оператор if...else ..............................................................................37
Оператор switch................................................................................37
Циклы ....................................................................................................38
for ......................................................................................................38
for...in ................................................................................................39
while ..................................................................................................39
do...while ...........................................................................................39
Метки, операторы break и continue.....................................................40
Метки ................................................................................................40
Оператор break .................................................................................40
Оператор continue ............................................................................40
Генерация и обработка исключений ...................................................41
Оператор throw ................................................................................41
Конструкция try...catch...finally .......................................................41
Функции ........................................................................................................43
Специфицирование функций...............................................................43
Декларирование с помощью ключевого слова function................43
Создание с помощью литерала функции .......................................43
Создание в виде объекта с помощью оператора new....................43
Вызов функций, аргументы функций .................................................44
Оглавление V
Глава 2. Язык HTML ..................................................................... 45
Чем является и чем не является HTML ......................................................45
Немного истории ..........................................................................................46
Ссылки на документацию ............................................................................46
Языковые конструкции HTML ...................................................................47
Элементы ..............................................................................................47
Атрибуты...............................................................................................48
Комментарии ........................................................................................48
Структура документа HTML .......................................................................49
Основные элементы Web-страниц ..............................................................50
Текст ......................................................................................................50
Набор символов документа HTML ................................................50
Элементы форматирования текста .................................................51
Элементы структурирования текста...............................................52
Шрифты ............................................................................................52
Ссылки ..........................................................................................................53
Создание ссылок и якорей ...................................................................54
URI.........................................................................................................54
Списки ...........................................................................................................55
Таблицы ........................................................................................................56
Изображения, объекты, апплеты ................................................................56
Формы ...........................................................................................................57
Фреймы .........................................................................................................58
Глава 3. Каскадные таблицы стилей CSS2 ............................... 59
Ссылки на документацию ............................................................................59
Основные сведения о CSS2 .........................................................................60
Возможности CSS2 ......................................................................................60
Использование CSS2 в HTML-документах ................................................62
Язык таблиц стилей по умолчанию ....................................................62
Встроенная информация о стиле ........................................................62
Таблицы стилей, внедренные в документ ..........................................63
Внешние таблицы стилей ....................................................................64
Предпочитаемые, альтернативные и постоянные таблицы
стилей................................................................................................64
Связывание документа с внешними таблицами стилей ...............65
Разработка таблиц стилей CSS2..................................................................66
Основные синтаксические конструкции CSS ....................................66
Правила "at" .....................................................................................66
Наборы правил, селекторы, объявления, свойства .......................66
Комментарии ....................................................................................67
VI Оглавление
Селекторы .............................................................................................67
Типы селекторов ..............................................................................68
Каскады таблиц стилей. Наследование ..............................................71
Модель представления документа в виде блоков ..............................73
Модель визуального форматирования .......................................................73
Визуальные эффекты ...................................................................................74
Цвет ...............................................................................................................75
Шрифты ........................................................................................................76
Текст ..............................................................................................................77
Курсоры ........................................................................................................78
Глава 4. Объектные модели браузера и документа ................. 79
Понятия объектных моделей браузера, документа ...................................79
Введение в объектную модель документа (DOM) ....................................80
W3C DOM и DHTML Object Model ....................................................81
Объекты модели документа, их связь с HTML ..................................82
Иерархия объектов модели документа браузера ...............................84
Способы доступа к объектам модели документа ..............................86
Доступ через методы объекта document ........................................87
Доступ через предопределенные коллекции объектов .................88
Доступ путем прохождения дерева документа .............................89
Основы использования объектной модели ................................................91
Объект window. Методы ввода/вывода информации ........................91
Метод alert .......................................................................................91
Метод confirm...................................................................................92
Метод prompt ...................................................................................92
Объекты document и body ....................................................................93
Объект body ......................................................................................95
DOM и CSS ...........................................................................................96
Управление таблицами стилей .......................................................96
Управление встроенной информацией о стиле ...........................103
Управление стилем на основе значения атрибута class
элемента ..........................................................................................106
События в объектной модели....................................................................107
Несколько слов о моделях обработки событий ...............................107
Базовая модель обработки событий..................................................108
Принципы обработки событий .....................................................108
Основные события DOM...............................................................110
Программный запуск и отмена обработки событий ...................113
Оглавление VII
Специфические модели обработки событий ....................................114
Модель обработки событий Netscape...........................................115
Модель обработки событий Microsoft Internet Explorer .............116
Модель обработки событий DOM уровня 2 .....................................117
ЧАСТЬ II. РЕШЕНИЕ ТИПОВЫХ ЗАДАЧ
ПРОГРАММИРОВАНИЯ СКРИПТОВ................................... 121
Глава 5. Определение параметров операционного
окружения ...................................................................................... 123
Определение версии JavaScript .................................................................124
Определение параметров совместимости браузера ................................126
Определение параметров дисплея ............................................................132
Глава 6. Дата и время .................................................................. 134
Объект Date ................................................................................................134
Операции с объектом Date ................................................................135
Конвертирование даты/времени в строку ........................................139
Разработка объекта, содержащего метод конвертирования
даты/времени в строку произвольного формата .........................140
Применение объектов Date и CVDate ......................................................142
Вывод текущего времени на Web-странице ....................................143
Редирект с задержкой и выводом времени до перехода .................144
Глава 7. Работа с cookie ............................................................... 147
Что такое cookie? .......................................................................................147
Cookie в деталях .................................................................................148
Cookie и HTTP ...............................................................................148
Cookie и JavaScript .........................................................................150
Cookie со стороны браузера ..........................................................153
Практическое применение cookie .............................................................154
Библиотека функций работы с cookie ...............................................155
Использование библиотеки работы с cookie: скрипт,
запоминающий имя пользователя, количество и дату посещений
страницы .............................................................................................159
Глава 8. Регулярные выражения .............................................. 163
Основы регулярных выражений ...............................................................163
Создание регулярных выражений .....................................................163
VIII Оглавление
Шаблоны регулярных выражений................................................164
Флаги регулярных выражений......................................................170
Работа с регулярными выражениями в JavaScript ...................................171
Объект RegExp ....................................................................................171
Методы объекта RegExp ................................................................172
Свойства объекта RegExp ..............................................................173
Методы объекта String, обрабатывающие текст на основе
регулярных выражений......................................................................175
Практическое использование регулярных выражений ...........................178
Примеры часто употребляемых регулярных выражений ...............179
Глава 9. Протоколы mailto и javascript .................................... 188
Протокол mailto ..........................................................................................189
JavaScript и URL типа mailto .....................................................................191
URL типа mailto и JavaScript в ссылках ............................................191
URL типа mailto и JavaScript в формах.............................................193
Протокол javascript ....................................................................................195
Возможности применения URL типа javascript ...............................196
Генерация изображений через протокол javascript .........................201
ЧАСТЬ III. СОЗДАНИЕ ДИНАМИЧЕСКИХ
ЭФФЕКТОВ ................................................................................... 203
Глава 10. Управление интерфейсом браузера ........................ 205
Создание окон браузера с заданными параметрами ...............................205
Изменение параметров окна браузера ......................................................210
Примеры перемещения и изменения размеров окна браузера .......210
Изменение текста заголовка, строки состояния, управление
прокруткой документа .......................................................................213
Изменение текста заголовка окна браузера .................................213
Изменение текста строки состояния окна браузера ....................213
Управление прокруткой документа .............................................215
Глава 11. Работа с формами ....................................................... 217
Работа с формами средствами JavaScript .................................................219
Формы в объектной модели документа ............................................219
Методы доступа к объектам элементов управления форм .............220
Принципы динамической обработки форм ......................................224
Манипулирование элементами управления форм ......................224
Манипуляции общего типа элементами управления форм ........237
Оглавление IX
Улучшение эксплуатационных характеристик пользовательского
интерфейса форм средствами JavaScript ..................................................238
Принципы работы с формами при реализации дружественного
пользовательского интерфейса .........................................................239
Элементы создания дружественного пользовательского
интерфейса ..........................................................................................241
Управление состоянием взаимосвязанных элементов
управления......................................................................................241
Примеры автоматизации действий пользователя .......................244
Пример формы с автокалькуляцией .............................................247
Пример формы обратной связи ....................................................249
Усложненный пример формы обратной связи ............................250
Глава 12. Работа с изображениями ........................................... 253
Средства и приемы работы с изображениями в гипертекстовом
документе....................................................................................................254
Изображения в объектной модели документа .................................254
Объект Image и коллекция images ....................................................256
Общие принципы работы с изображениями ....................................258
Обработчики событий onload и onerror изображений.
Свойство complete ..........................................................................259
Несколько рекомендаций по работе с изображениями ..............260
Практические примеры работы с изображениями ..................................264
Предварительная загрузка изображений с обработкой ошибок
загрузки ...............................................................................................265
Пример создания слайд-шоу с предварительной загрузкой
изображений .......................................................................................269
Скрипт галереи изображений ............................................................271
Глава 13. Создание анимационных эффектов ........................ 277
Средства и принципы создания анимации на Web-страницах ...............277
Примеры создания анимации ....................................................................278
Плавное "проявление" и "исчезновение" текста .............................278
Текст, движущийся на наблюдателя .................................................280
Примеры создания "бегущих строк" ................................................280
Вариант 1 ........................................................................................281
Вариант 2 ........................................................................................281
Волнообразно движущийся текст .....................................................284
Эффект 1 .........................................................................................284
Эффект 2 .........................................................................................286
Эффект 3 .........................................................................................288
X Оглавление
Текст, прилетающий по частям.........................................................290
Имитация движения текста по кругу в 3D-пространстве ...............292
Движение фонового рисунка страницы ...........................................294
Летящие звезды ..................................................................................294
Эффект фейерверка ............................................................................298
Падающий снег...................................................................................301
Часы со стрелками .............................................................................303
Использование фильтров в Microsoft Internet Explorer для
создания анимационных эффектов ...................................................305
Что такое фильтры? .......................................................................305
Применение фильтров ...................................................................306
Волнообразное движение текста на основе фильтра Wave.............310
Динамическое изменение прозрачности изображений с
помощью фильтра Alpha....................................................................311
Анимационное изменение содержимого с помощью фильтра
Fade .....................................................................................................311
Эффектная анимация на основе использования фильтра Light ......313
ЧАСТЬ IV. ПРИМЕРЫ РАЗРАБОТКИ
СЛОЖНЫХ СКРИПТОВ ........................................................... 315
Глава 14. Разработка визуального редактора HTML
на основе режима редактирования документа
Microsoft Internet Explorer .......................................................... 317
Редактирование гипертекстового содержимого в Microsoft Internet
Explorer .......................................................................................................318
Реализация визуального редактора HTML ..............................................320
Структура документа [Link] ........................................................320
Процесс инициализации редактора ..................................................321
Создание нового документа ..............................................................324
Обработка команд редактирования и форматирования текста,
печати и сохранения документа ........................................................327
Обработка событий изменения гарнитуры и размера шрифта
текста ...................................................................................................329
Установка цвета шрифта и фона текста ...........................................331
Глава 15. Приемы эффективного объектно-
ориентированного программирования на JavaScript ........... 335
Анализ модели объектно-ориентированного программирования
на JavaScript ................................................................................................336
Оглавление XI
Класс или объект? ..............................................................................336
Проблемы идентификации типов объектов в JavaScript .................340
Использование классических конструкций
объектно-ориентированного программирования в JavaScript ........343
Несколько слов о наследовании ...................................................343
Пространства имен ........................................................................346
Классы, инкапсулирующие классы ..............................................347
Нюансы использования свойств объектов и вызовов
методов классов .............................................................................350
Общие рекомендации относительно объектно-
ориентированного программирования на JavaScript ..................353
Примеры эффективного объектно-ориентированного
программирования на JavaScript ...............................................................354
Реализация алгоритма наследования ................................................355
Класс VSimpleObject — корневой класс в иерархии
наследования ......................................................................................357
Классы для работы с геометрическими типами данных .................359
Класс VPoint ...................................................................................360
Класс VSize .....................................................................................362
Класс VRect.....................................................................................364
Класс VObject......................................................................................369
Модель событий, сигналы и слоты ..............................................370
Класс таймера VTimer ........................................................................376
Заключение .................................................................................... 381
Приложение. Описание компакт-диска ................................... 382
Предметный указатель ................................................................ 384
Введение
Давным-давно, когда я делал свой первый сайт, только начиная пости-
гать азы Web-разработки, передо мной встала проблема изучения
JavaScript. Сайт казался мне простым и неинтересным. Я хотел сделать
его более динамичным и красочным, добавив на страницы несколько
интерактивных элементов. Я знал, что язык JavaScript решит мои про-
блемы, но ничего не знал о его применении. Когда я прочел соответст-
вующую документацию, всесторонне описывающую структуру и син-
таксис языка, я осознал, что так и не понимаю, как конкретно решать
поставленные передо мной задачи. Тогда я принялся искать в Интернете
готовые примеры скриптов на JavaScript и изучать их.
Уже через пару недель я с улыбкой вспоминал о проблемах, ранее ка-
завшихся мне неразрешимыми. Изучение готовых примеров дало мне
самое главное — понимание принципов разработки скриптов и то, в ка-
ком направлении стоит двигаться для совершенствования своих навы-
ков.
А сегодня, по прошествии нескольких лет, имея за плечами большой
опыт Web-разработки с применением самых различных Web-техно-
логий, я рад представить вам свою книгу, рассказывающую о програм-
мировании клиентских сценариев на JavaScript. Помня о трудностях,
возникших передо мной в свое время, я решил изложить материал са-
мым доступным способом — на примерах.
О чем и для кого эта книга
Книга, которую вы сейчас держите в руках, рассказывает о принципах,
приемах и тонких аспектах разработки клиентских сценариев (скриптов)
для Web-браузеров на самом популярном в настоящий момент языке
2 Введение
создания сценариев JavaScript. Применение клиентских скриптов (фраг-
ментов программного кода, загружаемых вместе с Web-страницей в
браузер), позволяет создавать на страницах Web-сайта красочные и ин-
тересные эффекты, оригинальные элементы оформления, управления и
навигации, контролировать действия пользователя, изменять страницу
без ее перезагрузки и многое другое, что недоступно при помощи только
HTML и CSS.
Основу материала книги составляют практические примеры, сложность
которых варьируется от минимальной до достаточно большой, пред-
ставляющие собой законченные и готовые к применению на сайте
скрипты различного назначения. Однако каждый раздел, посвященный
определенной теме, содержит теоретический материал, освещающий
соответствующие вопросы.
Книга предназначена для широкого круга читателей — как для тех, кто
абсолютно не знаком с какими бы то ни было принципами создания
Web-страниц и скриптов, так и тех, кто достаточно хорошо владеет зна-
ниями в этой области. Благодаря наличию вводных глав, посвященных
изучению основ — структуры и синтаксиса JavaScript, HTML, CSS,
DOM, книгу можно читать независимо от начального уровня квалифи-
кации. Иными словами, если вы желаете изучить JavaScript с нуля, со-
вершенствуете и развиваете свои навыки, или просто ищете для себя
что-то новое в данной области — эта книга для Вас!
Как организована книга,
и как ее читать
Структурно книга состоит из четырех частей, включающих главы, объ-
единяемые общими идеями и концепциями. В части I описываются
средства создания статических и динамических Web-страниц — язык
HTML, каскадные таблицы стилей, объектная модель документа браузе-
ра, синтаксис языка JavaScript. К ней относятся главы 1—4. В части II
обсуждаются решения типовых задач, очень часто встречающихся при
программировании скриптов для Web-браузеров — получение парамет-
ров операционного окружения, работа с cookie, объектом даты/времени,
регулярными выражениями и протоколами. В эту часть входят главы 5—
9. Часть III, включающая главы 10—13, посвящена рассмотрению
приемов программирования и примеров реализации функционально за-
конченных скриптов, готовых для практического применения. В ней
рассказывается о работе с окном браузера, формами, изображениями и
создании анимации на Web-страницах. Часть IV содержит главы 14 и
15, в которых обсуждаются более сложные вопросы, чем в предыду-
Введение 3
щих — реализация визуального редактора HTML и приемы эффектив-
ного объектно-ориентированного программирования на JavaScript. Да-
лее приводятся более подробные сведения о содержании каждой из глав.
В главе 1 приводится описание языка JavaScript — синтаксиса,
структур управления, типов данных, методов определения и манипу-
лирования данными, объектно-ориентированного подхода, реализо-
ванного в данном языке.
Глава 2 содержит сведения о языке гипертекстовой разметки HTML
и принципах его применения при создании Web-страниц.
Глава 3 описывает синтаксис и применение каскадных таблиц стилей
второго уровня, а также модель представления документа в виде
блоков, модель визуального форматирования.
Глава 4 посвящена изучению объектных моделей браузеров, объект-
ной модели документа (DOM) и способам их использования.
В главе 5 рассматриваются способы получения параметров среды, в
которой работает сценарий (версия JavaScript, различные параметры
браузера, параметры экрана).
Глава 6 описывает приемы работы с датой и временем в JavaScript.
В ней содержатся примеры оперирования объектом Date, вывода
времени в различных форматах, вывода текущего времени на Web-
странице, разрабатывается объект CVDate, имеющий расширенные
возможности форматирования даты/времени и скрипт редиректа с
заданной задержкой и выводом времени до перехода.
Глава 7 подробно освещает аспекты применения и работы с cookie
(идентификация пользователя, формирование содержимого страницы
в зависимости от установленных cookie и т. д.). В этой главе разраба-
тывается библиотека, упрощающая работу с cookie в JavaScript.
Глава 8 содержит теоретический материал и практические примеры
применения регулярных выражений. Здесь разрабатывается библио-
тека функций для проверки корректности различных данных (иден-
тификаторы, имена пользователей, дата/время, адреса e-mail и т. д.)
с помощью регулярных выражений.
В главе 9 рассматриваются возможности использования протоколов
mailto (отправка писем с заданным текстом, например, введен-
ным на Web-странице, через почтовую систему пользователя) и
javascript (генерация содержимого документов во фреймах, popup-
окнах, а также изображений) при программировании сценариев.
4 Введение
В главе 10 обсуждаются средства и реализуются скрипты для работы
с окнами браузера (создание, изменение размеров, положения окна),
их частями (заголовок, строка состояния) и прокруткой документа.
Глава 11 посвящена работе с формами и элементами управления
форм. Рассматриваются различные примеры создания динамических
форм. Большое внимание уделяется контролю корректности данных
и способам организации дружественного интерфейса.
В главе 12 все внимание сосредоточено на работе с
изображениями — изображения в объектной модели, организация
предварительной загрузки изображений, обработка ошибок загрузки
и т. д. Здесь же разрабатываются скрипт слайд-шоу с предваритель-
ной загрузкой изображений и скрипт фотогалереи.
Глава 13 рассказывает о принципах создания анимации на Web-
страницах. В рамках этой главы разрабатываются двадцать скриптов,
создающих на странице яркие анимационные эффекты — бегущие
строки, вертикальные скроллеры, движение текста по различным
траекториям, эффекты летящих звезд, падающего снега, аналоговые
часы, а также несколько очень интересных эффектов анимации на
основе использования фильтров в Microsoft Internet Explorer.
Глава 14 полностью посвящена разработке одного скрипта — визу-
ального редактора HTML, использующего режим редактирования
документа Microsoft Internet Explorer. В ней также описываются ин-
тереснейшие специфические возможности этого браузера.
В главе 15 обсуждаются тонкие аспекты, неочевидные моменты и
некоторые трудности ведения объектно-ориентированной разработки
на JavaScript, изучаются приемы повышения эффективности объект-
но-ориентированного программирования на данном языке, а также
создается библиотека классов, реализующая расширенный алгоритм
наследования, поддерживающая идентификацию типа пользователь-
ских объектов и синхронную обработку событий на основе механиз-
ма слотов и сигналов, а также включающая классы для работы с дан-
ными геометрических типов и класс таймера.
Порядок чтения книги может быть различным и зависеть от уровня ва-
шей подготовки относительно излагаемых в ней вопросов. Если вы не
имеете либо имеете слабые знания в области JavaScript, HTML, CSS и
DOM, то можете читать книгу последовательно, с первой до последней
главы. Однако для вас может оказаться удобным изучить сначала гла-
вы 2 ("Язык HTML") и 3 ("Каскадные таблицы стилей CSS2"), затем
перейти к главе 1, а потом читать книгу последовательно, начиная с гла-
Введение 5
вы 4. Если вы чувствуете себя уверенно в перечисленных вопросах, то
можете начать чтение с главы 5. Поскольку в главе 1 излагаются прин-
ципы объектно-ориентированного программирования на JavaScript, а
глава 15 посвящена обсуждению продвинутых методов эффективного
объектно-ориентированного программирования, главу 15 можно про-
честь сразу после главы 1.
Что содержится на компакт-диске
Компакт-диск, прилагаемый к книге, содержит:
файлы примеров к главам 1—15. Примеры к конкретной главе нахо-
дятся в подкаталоге с номером главы в каталоге examples. В подката-
логе examples\Lib содержатся файлы библиотек, разработка которых
описана в книге: библиотека для работы с cookie, библиотека для
работы с регулярными выражениями, библиотека для работы с да-
той/временем, библиотека для динамической генерации изображе-
ний. В каталоге examples\Lib\jsvcl содержится объектно-ориенти-
рованная библиотека JSVCL, разработанная в рамках главы 15;
документацию по: языку гипертекстовой разметки HTML версий 4.0
и 4.01, каскадным таблицам стилей первого и второго уровней (CSS1
и CSS2), объектным моделям документа первого, второго и третьего
уровней (DOM1, DOM2, DOM3). Документация располагается в ка-
талоге w3c в соответствующих подкаталогах (html, css, dom);
файлы, содержащие дополнительную справочную информацию к
различным главам книги: таблицу предопределенных имен цветов
CSS, таблицу соответствия имен свойств CSS свойствам объекта
style в JavaScript, таблицу идентификаторов форм курсоров в CSS,
таблицу соответствия элементов HTML интерфейсам объектов DOM,
а также листинг регулярного выражения, описывающего адрес элек-
тронной почты в формате, определяемом RFC 822. Эти файлы рас-
полагаются в каталоге addons.
Более полное описание содержимого и структуры компакт-диска приве-
дено в приложении к книге.
Благодарности
В заключение я хочу выразить благодарность людям, чье участие значи-
тельным образом сказалось на судьбе настоящей книги.
6 Введение
В первую очередь, я благодарю заместителя главного редактора изда-
тельства "БХВ-Петербург" Рыбакова Евгения Евгеньевича за предложе-
ние написать книгу по данной тематике. Его консультации и замечания
относительно структуры, содержания и оформления книги оказались
поистине бесценными для меня, начинающего автора, впервые участ-
вующего в подобном проекте. Он точно и ясно отвечал на все вопросы,
которые, первое время, возникали у меня очень часто, и прощал много-
численные задержки сдачи рукописи в редакцию.
Я также очень благодарен редактору издательства "БХВ-Петербург"
Кузьминой Анне Сергеевне за огромную работу, проделанную ей по
редактированию данной книги. Она исправила кучу опечаток, ошибок
оформления и стилистических неточностей, допущенных мной в про-
цессе создания материала. Именно ее упорный труд придал книге ее
настоящий вид.
Особую благодарность я выражаю Александру Пирамидину за предос-
тавленную возможность размещения его переводов спецификаций
HTML 4.01 и CSS 2 на прилагаемом к книге компакт-диске. Благодаря
ему, читатели смогут иметь в своем распоряжении русские версии этих
спецификаций.
Некоторые термины, употребляемые мной в главе 2 "Язык HTML" и
главе 3 "Каскадные таблицы стилей CSS2" книги, отличаются от со-
ответствующих терминов, используемых в указанных переводах спе-
цификаций. Это, видимо, объясняется моей приверженностью к не-
сколько другому диалекту специальной терминологии, относящейся к
области Web-разработки. В переводах Александр Пирамидин исполь-
зует ряд терминов, близких по звучанию к их английским вариантам.
Я же обозначаю соответствующие понятия их русскими аналогами
(например, вместо термина "бокс", употребляемого в переводе спе-
цификации CSS 2, я использую слово "блок" в книге). Поскольку
смысл терминов, используемых в переводах и в книге, воспринимает-
ся идентично и однозначно, я думаю, у читателей не возникнет каких-
либо проблем с их сопоставлением.
Наконец, я хочу сказать о человеке, чье участие в судьбе данной книги
имеет исключительное значение лично для меня, ее автора — моем ма-
леньком сыне Даре. В череде жизненных неурядиц, так некстати сва-
лившихся на мою голову в период написания материала, иногда только
он был причиной, побуждавшей меня с новыми силами браться за рабо-
ту. Эту книгу я посвящаю ему.
ЧАСТЬ I
Основы создания
Web-страниц и скриптов
Глава 1. Структура и синтаксис языка JavaScript
Глава 2. Язык HTML
Глава 3. Каскадные таблицы стилей CSS2
Глава 4. Объектные модели браузера и документа
Мы начинаем путешествие в увлекательный и красочный мир дина-
мического HTML — мир ярких эффектов, создаваемых на Web-
страницах клиентскими сценариями (скриптами) — фрагментами
внедряемого или подключаемого извне программного кода. Наши
скрипты будут написаны на JavaScript — самом популярном на сего-
дняшний день языке программирования сценариев. При помощи это-
го языка на Web-страницах можно создавать множество интересней-
ших эффектов. Мы будем уделять основное внимание разработке
скриптов, имеющих практическое применение: работа с формами,
изображениями, регулярными выражениями, временем, cookie, реа-
лизация редактора HTML. Разработанные скрипты вполне готовы к
применению на страницах реально действующих Web-сайтов.
На данный момент практически каждая программа-браузер имеет
поддержку JavaScript. Однако, к сожалению, для конкретных моделей
браузеров существует ряд индивидуальных особенностей, которые
необходимо учитывать при разработке скриптов. Данная книга ори-
ентируется на кроссбраузерные решения, работающие одинаково под
управлением различных обозревателей. Замечания, касающиеся ас-
пектов реализации для конкретных платформ, будут приводиться по
мере необходимости.
Прежде чем перейти к углубленному изучению методов и приемов
разработки сложных интерактивных элементов, необходимо разо-
браться с базовыми принципами создания динамических Web-стра-
ниц, играющими важную роль в понимании материала следующих
разделов книги. В данной части приводятся основные сведения о
языке JavaScript (его структуре и синтаксисе), языке гипертекстовой
разметки (HTML), каскадных таблицах стилей (CSS) и объектной
модели документа браузеров (DOM).
ГЛАВА 1
Структура и синтаксис
языка JavaScript
Существует множество языков программирования — низкоуровневые
(Assembler), процедурно-ориентированные (такие как C, Pascal), объект-
но-ориентированные (C++, Python), языки, реализующие логическую
парадигму программирования (Prolog), и т. д. JavaScript является высо-
коуровневым объектно-ориентированным языком. Пожалуй, в качестве
главной его особенности можно назвать межплатформенность, предпо-
лагающую возможность внедрения поддержки языка в различные про-
граммные продукты. JavaScript не предназначен для создания самостоя-
тельных приложений, однако это не означает скудность его языковых
средств. Напротив, в нем есть все, что необходимо для эффективного
решения широкого круга алгоритмических задач.
В этой главе приводится достаточно полное описание языка — его
структуры, синтаксиса, основных языковых конструкций.
Определения и термины
Изучение любой области знаний обычно начинается с определения
терминов и понятий, используемых для точного описания объектов этой
области. Языки программирования не являются исключением, поэтому,
прежде чем начать знакомство с основами JavaScript, следует ввести
несколько специальных терминов, которые достаточно часто будут ис-
пользоваться в нескольких следующих разделах книги. Приведенная
далее терминология применима ко всем языкам программирования, од-
нако, поясняющие примеры корректны именно в отношении JavaScript.
10 Часть I. Основы создания Web-страниц и скриптов
Символ языка. Под символами языка будем понимать множество воз-
можных неделимых лексических единиц (лексем), корректных с точки
зрения грамматики конкретного языка и составляющих его лексику.
Символами языка являются: допустимые имена идентификаторов, клю-
чевые слова, операторы, разделительные знаки, т. е. все объекты исход-
ного текста программы, имеющие самостоятельное значение.
Если выражаться формально, то под множеством символов языка
здесь подразумевается множество терминальных символов грамма-
тики языка.
Примеры:
5, 10.843, 2e+6, "Строка символов" — литералы;
function, var — ключевые слова;
oDiv, strTemp — идентификаторы.
Ключевое слово. Это символ языка, являющийся частью его синтаксиса.
Некоторые ключевые слова могут использоваться в качестве литералов:
true, false, null. Ключевые слова не могут быть использованы в каче-
стве идентификаторов.
Зарезервированное слово. Под зарезервированными словами обычно
понимают лексемы, корректные с точки зрения грамматики языка, но не
являющиеся частью его синтаксиса. Зарезервированные слова не могут
употребляться в качестве идентификаторов, поскольку планируется их
введение в состав языка в будущем.
Литерал. Литерал — это элемент данных, содержащийся непосредст-
венно в программном коде. Литерал представляется некоторой последо-
вательностью символов (литер), имеющей самостоятельное значение.
Можно сказать, что литерал — это фиксированное значение (опреде-
ленное литерально), содержащееся в коде программы. Литералами мо-
гут являться: числовые, строковые константы, инициализаторы масси-
вов, объектов. Литералами не являются: имена переменных, констант
и т. д.
Понятие литерала тесно связано с понятием типа данных. Применитель-
но к JavaScript можно выделить следующие типы литералов:
булевы (например, true, false);
целочисленные (например, 10, 109);
действительные, или с плавающей точкой (1.15, 2.64E10);
Ãëàâà 1. Структура и синтаксис языка JavaScript 11
строковые (например: "ABCD", 'aBcD');
литералы массивов ([10, 20, 40] или ['one', 1, "two", 2]);
литералы объектов ({ a: 10, b: 20 }).
Некоторые литералы являются ключевыми словами: true, false, null,
undefined.
Подробнее методы определения литералов различных типов будут рас-
смотрены в соответствующих разделах.
Идентификатор. Идентификатор представляет собой строку символов,
обозначающую в программном коде некоторый объект. Идентификато-
рами являются имена переменных, объектов, функций, констант и т. д.
Пример:
a = 10; // здесь a - идентификатор.
Оператор. Применительно к языкам программирования, термин "опе-
ратор" в русском языке имеет двойственное значение (в английском
языке существуют два разных термина, которые обычно переводятся на
русский одинаково).
Оператор (statement) — предложение языка, определяющее некое
законченное действие в виде последовательности операций, задаю-
щей алгоритм решения задачи (исполняемые операторы), либо за-
дающее набор описаний, необходимых для трансляции программно-
го кода (невыполняемые операторы). Исполняемые операторы вызы-
вают изменения состояния среды исполнения, служат для управления
потоком вычислений, как, например, операторы цикла, условия, при-
сваивания, перехода. С этой точки зрения программа представляется
просто списком операторов. Следует заметить, что под данное опре-
деление гораздо больше подходят такие термины, как "утвержде-
ние", "инструкция". Но существует много устоявшихся выражений,
включающих понятие "оператор" в приведенном контексте (напри-
мер, "операторный блок"), поэтому в данной книге термин "опера-
тор" часто используется как синоним слова "инструкция".
Оператор (operator) — это символ языка, определяющий операцию
обработки данных (например, сложение, умножение, сдвиг).
Выражение. Это синтаксическая конструкция, представляющая собой
последовательность идентификаторов и/или литералов, комбинируемых
при помощи операторов (имеет обычно вид формулы), которая может
быть вычислена в определенное значение.
12 Часть I. Основы создания Web-страниц и скриптов
Ключевые и зарезервированные слова, переменные, типы данных, опе-
раторы, структуры управления, функции, типы данных, их применение и
использование будут подробно рассмотрены далее в этой главе.
Структура языка
Теперь, когда мы определились с терминологией, можно приступить
непосредственно к изучению JavaScript. Изучение любого языка про-
граммирования логично начинать с рассмотрения его структуры и ос-
новных характеристик, таких как: правила записи кода (различных
предложений языка, комментариев, операторных блоков и т. д.), порядка
интерпретации/компиляции кода, перечня и правил использования раз-
делительных и пробельных символов. Мы начнем именно с этого, учи-
тывая специфику использования JavaScript в исполняющей среде брау-
зера.
JavaScript и HTML
Как уже говорилось, в этой книге мы будем рассматривать исключи-
тельно программирование клиентских сценариев для Web-браузеров,
т. е. наши скрипты будут оперировать различными объектами Web-
страниц, загружаемых в программу-обозреватель. Для того чтобы брау-
зер был в состоянии исполнить код скрипта, этот код необходимо
внедрить в Web-страницу. Для более полного восприятия информации
данного раздела необходимо понимание основ языка гипертекстовой
разметки HTML (описание HTML см. в главе 2).
Способы внедрения JavaScript-кода
в документы HTML
Программный код на языке JavaScript может быть внедрен в HTML-
документ несколькими способами:
непосредственно включаться в HTML-документ как содержимое
элемента SCRIPT;
подключаться из внешнего источника, указанного в атрибуте SRC
элемента SCRIPT;
содержаться в виде кода обработчика события элемента HTML, зада-
ваемого такими атрибутами, как, например, onclick, onmousemove;
указываться в виде псевдо-URL c протоколом доступа javascript:
как значение атрибутов href, action, src и т. д.
Ãëàâà 1. Структура и синтаксис языка JavaScript 13
Как видно, в первых двух случаях должен использоваться элемент
SCRIPT, поэтому рассмотрим его подробнее. Для этого элемента обяза-
тельны начальный и конечный теги (<SCRIPT> и </SCRIPT>). Также для
него допустимы следующие атрибуты:
src — этот атрибут задает URI внешнего скрипта. Если атрибут не
определен, то все содержимое элемента SCRIPT интерпретируется как
программный код. Если атрибут определен, то содержимое элемента
полностью игнорируется, а пользовательский агент пытается загру-
зить и исполнить содержимое, URI которого задается этим пара-
метром;
type — этот атрибут задает язык скрипта элемента SCRIPT. Учиты-
вая, что браузеры могут поддерживать различные языки клиентских
сценариев (например, JavaScript, VBScript, TCL), необходимо либо
указывать язык сценариев по умолчанию для документа, либо явно
определять его для каждого элемента SCRIPT путем задания значения
данному атрибуту. Язык скрипта указывается аналогично определе-
нию MIME-типа содержимого (например, "text/javascript",
"text/vbscript", "text/tcl"). Значение данного атрибута имеет
приоритет над языком скрипта, заданным по умолчанию. Во избежа-
ние возможных ошибок неверной интерпретации скриптов пользова-
тельскими агентами, этот атрибут желательно указывать для каждого
элемента SCRIPT, однако практика показывает, что все популярные
программы-обозреватели на данный момент при отсутствии инфор-
мации о языке скрипта пытаются интерпретировать код, исходя из
предположения, что он написан на JavaScript;
language — данный атрибут, также как и type, определяет язык
скрипта элемента. Значением атрибута является идентификатор язы-
ка (например, "javascript" или "VBS"). Поскольку идентификаторы
языков не стандартизированы, этот атрибут помечен в спецификации
HTML 4.0 как нежелательный, поэтому вместо него следует исполь-
зовать атрибут type;
defer — это логический атрибут, его определение в теге <SCRIPT>
обеспечивает возможность отложенного исполнения скрипта, сооб-
щая браузеру о том, что данный сценарий не производит генериро-
вания содержимого Web-страницы. Если этот атрибут не установлен,
то отображение страницы пользовательским агентом прерывается до
момента завершения исполнения всех инструкций, определяемых
в элементе SCRIPT.
14 Часть I. Основы создания Web-страниц и скриптов
Теперь рассмотрим указанные выше способы включения кода скрипта
в HTML-документ более подробно.
Включение кода в элемент SCRIPT
Должно быть, это наиболее часто встречающийся способ добавления
скриптов в Web-страницы. Последовательность инструкций просто за-
писывается между парой тегов <SCRIPT> и </SCRIPT>, корректно разме-
щенных в HTML-документе:
<HTML><BODY>
...
<SCRIPT type="text/javascript">
<!--
var x = 10;
alert('JavaScript на примерах');
// -->
</SCRIPT>
...
</BODY></HTML>
Браузер интерпретирует полностью содержимое такого блока, как про-
граммный код, написанный на языке, указанном в атрибуте type. Как
можно заметить, в приведенном примере код заключен в HTML-
комментарий (<!-- и -->). Это стандартный прием для обеспечения со-
вместимости со старыми версиями браузеров. Такой подход позволяет
избежать вывода на страницу кода скриптов, если обозреватель не под-
держивает их исполнение. В действительности, браузеров, не "знаю-
щих" о существовании тега <SCRIPT>, сейчас просто не найти. Тем не
менее этот прием широко используется и в настоящее время.
Включение кода из внешнего источника
Этот способ подразумевает указание атрибута src в теге <SCRIPT>, зна-
чением которого является URI внешнего скрипта:
<HTML><BODY>
...
<SCRIPT type="text/javascript" src="[Link]"></SCRIPT>
...
</BODY></HTML>
Как уже упоминалось, встречая тег <SCRIPT> с атрибутом src, браузер
пытается получить данные из источника, указанного значением этого
атрибута, и интерпретировать их как программный код на языке, обо-
Ãëàâà 1. Структура и синтаксис языка JavaScript 15
значенном в атрибуте type. При этом все содержимое элемента SCRIPT
полностью игнорируется. Фактически, это выглядит так, как если бы
содержимое элемента SCRIPT заменялось на содержимое внешнего
скрипта.
Данный метод обладает несколькими существенными преимуществами
по сравнению с непосредственным включением кода в Web-страницу:
отделение логики от представления. Это подразумевает раздельное
существование структуры, предоставляющей пользовательский ин-
терфейс (в нашем случае — HTML-документ) и алгоритмов работы с
данными и управления этой структурой (в нашем случае — внешний
скрипт). Такой подход, в частности, позволяет вести раздельную раз-
работку интерфейса и алгоритмов обработки данных (часто — раз-
ными разработчиками);
возможность использования одного внешнего скрипта многими Web-
страницами;
возможность динамической генерации кода скрипта на сервере;
интенсивное кэширование подключаемых данным способом скрип-
тов современными браузерами. Это приводит к уменьшению трафика
для конечных пользователей и увеличению скорости загрузки стра-
ниц сайтов.
Включение кода в обработчики событий элементов
Web-страниц
Для многих элементов Web-страниц существует возможность назначе-
ния обработчиков событий пользовательского ввода (движение указате-
ля мыши, потеря и получение фокуса, нажатия клавиш). Обработчики
событий задаются путем указания значений специальных атрибутов те-
гов этих элементов. Например, для назначения обработчика события
движения мыши для некоторого элемента необходимо определить для
него атрибут onmousemove. Значениями таких атрибутов являются строки
программного кода на одном из языков сценариев, поддерживаемых
браузером. Например, следующий код демонстрирует назначение обра-
ботчика события щелчка мышью элементу DIV:
<HTML><BODY>
...
<DIV onclick="alert('Щелчок мышью');">
Щелкните мышью здесь</DIV>
...
</BODY></HTML>
16 Часть I. Основы создания Web-страниц и скриптов
Включение кода в виде псевдо-URL
Этот способ похож на предыдущий. Он подразумевает формирование
URL специального вида путем указания протокола доступа к данным
javascript:, после чего записывается строка программного кода. По-
добный URL может быть задан, например, как значение свойства href
тега <A>, свойства action тега <FORM>, свойства src тега <FORM> или
<IMG>. Например:
<HTML><BODY>
...
<a href="javascript: alert('Щелчок');">Ссылка JavaScript</a>
...
</BODY></HTML>
Данный способ часто используют с целью создания на странице ссылок
для добавления этой страницы в список закладок браузера и других по-
хожих задач. Подробнее об использовании протокола javascript: будет
рассказано в главе 8.
Следует заметить, что при использовании двух последних методов
включения JavaScript-кода в HTML-документ обязательно указание
для документа языка сценариев по умолчанию. Это может быть
сделано путем включения соответствующего объявления META в тег
HEAD: <META http-equiv="Content-Script-Type" content="text
/javascript">, либо HTTP-заголовком Content-Script-Type:
text/javascript.
Порядок исполнения скриптов в документе
Блоки кода JavaScript, внедренные в HTML-документ с помощью тега
<SCRIPT> (как непосредственно, так и из внешнего источника), испол-
няются последовательно в том порядке, в котором они найдены браузе-
ром в документе. При этом блоки с установленным атрибутом defer
могут быть проигнорированы и исполнены позже (установка атрибута
defer не обеспечивает безусловного отложенного исполнения скрипта, а
лишь декларирует такую возможность).
Код, добавленный в документ в виде обработчиков событий элементов
либо в виде псевдо-URL, исполняется при возникновении соответст-
вующих событий.
Ãëàâà 1. Структура и синтаксис языка JavaScript 17
Основные характеристики языка
Теперь рассмотрим характеристики JavaScript, обуславливающие прави-
ла написания программного кода.
Регистрозависимость
JavaScript-код интерпретируется с учетом регистра символов. Это озна-
чает, что исполняющая система различает строчные и прописные буквы
в написании идентификаторов, ключевых слов, операторов и других
символов языка. Таким образом, в следующем фрагменте кода произво-
дится объявление, и присваиваются значения трем различным пере-
менным:
var nvalue = 10;
var nValue = 20;
var nVALUE = 30;
Интерпретация пробельных символов
Пробельные символы (символы перевода строки, символы табуляции,
пробелы) в JavaScript служат преимущественно для форматирования
кода. Они не определяют логическую структуру программы (как, на-
пример, в языке Python). Количество следующих подряд пробельных
символов не имеет значения. В случаях простых и однозначных конст-
рукций пробелы могут опускаться. Таким образом, следующие строки
кода семантически идентичны:
x=[Link](10);
x = [Link](10);
x = [Link] ( 10 ) ;
Тем не менее во многих случаях удаление пробелов (например, между
некоторыми операторами и идентификаторами) приводит к образова-
нию новых конструкций, не идентичных исходным. Рассмотрим сле-
дующий код:
var oMyObject = new MyObject;
Удалив здесь один пробел, получим совершенно иную по смыслу инст-
рукцию:
var oMyObject = newMyObject;
Количество пробельных символов и их тип также имеют значение при
определении литералов строк.
18 Часть I. Основы создания Web-страниц и скриптов
Комментарии
В JavaScript существуют два вида комментариев: однострочные и мно-
гострочные. Однострочный комментарий начинается с двух знаков
"прямой слэш" (//) и заканчивается символом перевода строки (т. е.
в конце строки вы нажимаете клавишу <Enter>). Пример однострочного
комментария:
var x = 10; // однострочный комментарий
Многострочные комментарии начинаются последовательностью симво-
лов /* и заканчиваются */. Например:
/*
Многострочный комментарий
Многострочный комментарий
*/
Следует учесть, что не стоит пытаться записывать вложенные много-
строчные комментарии, т. к. первая же последовательность символов */
вложенного комментария будет воспринята интерпретатором как при-
знак завершения всего комментируемого блока, а оставшаяся часть за-
комментированного текста будет рассматриваться как программный
код, что, скорее всего, вызовет возникновение синтаксических ошибок.
Запись инструкций, символы-разделители
Инструкции JavaScript могут быть записаны в одну или несколько строк
и могут разделяться символом ;. При записи нескольких инструкций в
одну строку, их разделение точкой с запятой обязательно. Могут суще-
ствовать пустые инструкции, образующиеся при записи подряд не-
скольких символов ;. Разделение инструкций не обязательно, если каж-
дая из них начинается с новой строки (правда, это не лучший стиль
оформления кода, к тому же при таком подходе повышается вероят-
ность допустить синтаксические ошибки). Например, следующий код
корректен:
var x = 10;
x =
x +
4; ;; x = x - 2;
y = x + 5
x = y - 10
Ãëàâà 1. Структура и синтаксис языка JavaScript 19
Операторные блоки, составной оператор
Путем использования фигурных скобок ({}) несколько следующих под-
ряд инструкций на языке JavaScript могут быть объединены в единый
логический блок, называемый операторным блоком. Такая конструкция
может рассматриваться как один оператор, который часто называют
составным оператором. Обычно операторные блоки используются для
определения тел функций, циклов и т. д.
Следует отметить одну особенность операторных блоков языка
JavaScript — в отличие от многих языков программирования (таких как
C++, Pascal), они не порождают пространств имен, если только не опре-
деляют тела функции.
Ключевые и зарезервированные слова
Спецификации различных реализаций JavaScript определяют списки
зарезервированных слов, которые не могут быть использованы в качест-
ве идентификаторов. Перечень зарезервированных слов может попол-
няться в процессе эволюции языка. Далее приведен список известных на
данный момент зарезервированных слов.
break default finally new throw
case delete for null true
catch do function protected try
class else if return typeof
const export import super var
continue extends in switch while
debugger false instanceof this with
Также спецификация JScript (версия JavaScript от Microsoft) определяет
дополнительный список зарезервированных слов. Они могут быть ис-
пользованы как идентификаторы, но при этом перестают интерпретиро-
ваться как ключевые слова далее в скрипте. Список таких слов таков:
abstract enum interface public ulong
boolean final internal sbyte ushort
byte float long set void
char get package short
decimal implements private static
double int protected uint
20 Часть I. Основы создания Web-страниц и скриптов
В спецификации JScript также указаны слова, которые планируется сде-
лать зарезервированными в будущем. Пока они также могут быть ис-
пользованы в качестве идентификаторов:
assert goto native throws volatile
ensure invariant require transient
event namespace synchronized use
Учитывая постоянное совершенствование диалектов JavaScript, пред-
ставляется разумным вообще не использовать слова из вышеприведен-
ных групп в качестве идентификаторов в своих скриптах, чтобы исклю-
чить возможную несовместимость со следующими версиями языка.
Переменные и константы
Переменные и константы можно рассматривать как символические име-
на соответствующих им значений, т. е. данных. Отличие константы от
переменной состоит в том, что значение переменной может изменяться
во время исполнения скрипта, а константы — нет.
Правила записи имен
Имена переменных и констант являются идентификаторами. В JavaScript
существуют следующие правила записи имен переменных и констант:
имя переменной или константы должно начинаться с символа $ (знак
доллара), _ (символ подчеркивания) либо латинской буквы;
следующими за первым символами могут быть: $, _, цифры, латин-
ские буквы. Начиная с JavaScript версии 1.5, это могут быть также
символы ISO 8859-1, Unicode и escape-последовательности, как на-
пример \u2211.
Следующие имена являются корректными в JavaScript 1.5 и выше:
variable0, _$___, $™_\u221E_D
Однако, если вы хотите видеть свои скрипты работоспособными во всех
браузерах, не стоит позволять себе вольностей, подобных $™_\u221E_D
в выборе имен переменных. Гораздо безопаснее ограничиться использо-
ванием латиницы и символа подчеркивания.
Ãëàâà 1. Структура и синтаксис языка JavaScript 21
Объявление переменных и констант
Переменные могут быть объявлены двумя способами:
путем присвоения значения переменной;
с использованием ключевого слова var (при этом можно также сразу
присвоить переменной значение).
То как объявляется переменная, может влиять на область ее видимости
(см. далее).
Константы могут определяться только одним способом: при помощи
ключевого слова const. Далее приведены примеры объявления пере-
менных и констант:
x = 10; // объявление переменной путем
// присвоения значения
var y; // объявление переменной с помощью var
var z = 10; // объявление переменной с помощью var
// и присвоение значения
const c = 5; // объявление числовой константы
Область видимости переменных и констант
В JavaScript существуют две области видимости переменных: локальная
и глобальная. Переменные и константы, принадлежащие глобальной
области видимости, могут использоваться в любом месте скрипта. Ло-
кальная область видимости допускает использование принадлежащих ей
констант и переменных только в ее пределах.
В отличие от многих других языков программирования, локальную об-
ласть видимости в JavaScript создает только тело функции. Операторные
блоки не генерируют локальных областей видимости (как, например,
в C++), поэтому при выходе потока вычислений за пределы операторно-
го блока, переменные, объявленные внутри блока, не уничтожаются.
Объявить локальную переменную в теле функции можно, только ис-
пользуя ключевое слово var. Во всех остальных случаях, таких как объ-
явление переменной внутри функции путем присвоения значения, объ-
явление переменной вне функции (как с использованием var, так и без
него), переменные будут относиться к глобальной области видимости.
В следующем примере только переменная L является локальной:
var g0 = 10; g1 = 20;
function f()
22 Часть I. Основы создания Web-страниц и скриптов
{
g2 = 30;
var L = 0;
}
f();
// далее можно использовать переменные g0, g1, g2, но не L
Особо следует подчеркнуть тот факт, что нельзя объявлять переменные
и константы с одинаковыми именами в пределах одной области видимо-
сти. Также их имена не должны совпадать с именами функций.
Глобальная переменная, если при ее определении не использовалось
ключевое слово var, может быть удалена при помощи оператора
delete.
Типы данных
JavaScript является динамически типизированным языком, что означает
отсутствие необходимости определения типа переменной при ее объяв-
лении. Тип переменных определяется из контекста и изменяется в про-
цессе совершения операций с данными (выполняется динамическое
приведение типов). Применительно к JavaScript можно выделить сле-
дующие классы типов данных:
скалярные (строковый, числовой, логический);
составные (массивы, объекты);
специальные (null, undefined).
Здесь стоит отметить тот факт, что в JavaScript переменная любого
(в том числе и скалярного) типа представляется объектом, т. е. имеет
набор свойств и методов, что дает возможность более гибко опериро-
вать данными. Однако JavaScript прозрачно поддерживает семантику
работы с данными конкретных типов, свойственную жестко типизиро-
ванным языкам программирования.
Получить тип данных, который имеет переменная, можно при помощи
оператора typeof. Этот оператор возвращает строку, идентифицирую-
щую соответствующий тип.
Сейчас мы рассмотрим специальные и скалярные типы данных, особен-
ности их конвертирования и способы литерального определения данных
Ãëàâà 1. Структура и синтаксис языка JavaScript 23
этих типов. Массивы и объекты будут рассмотрены далее в соответст-
вующих разделах этой главы.
Специальные типы данных
В JavaScript существуют два специальных типа данных: null и
undefined. С первого взгляда они похожи, однако семантика их приме-
нения существенно различается.
Тип данных null
Переменные этого типа данных могут иметь единственное значение
null. Такие переменные можно охарактеризовать как не содержащие
данных. То есть они не содержат действительных данных любых других
типов.
Изменить тип данных переменной на null можно, присвоив ей специ-
альное литеральное значение null:
var x = null;
Такое действие используют для удаления данных, хранимых в пере-
менной.
Для переменных данного типа оператор typeof возвращает строку
"Object", что может вводить в заблуждение. Для проверки значения
переменной на эквивалентность null следует использовать операции
равенства либо строгого равенства.
Тип данных undefined
Тип переменной считается undefined в случаях, когда:
переменная была декларирована, но ей не было присвоено значение;
используемая переменная является несуществующим свойством объ-
екта.
Для переменных, которые были декларированы, допускается сравнение
со значением undefined:
var x;
if(x === undefined)
alert('x === undefined');
24 Часть I. Основы создания Web-страниц и скриптов
Однако такой подход не может быть использован для проверки сущест-
вования переменной. Для того чтобы выяснить, определена ли перемен-
ная, необходимо использовать оператор typeof:
if(typeof(x) == "undefined")
alert('переменная x определена');
else
alert('переменная x не определена');
Тип данных undefined поддерживается только достаточно новыми
версиями браузеров (Internet Explorer 5.5 и выше, Netscape 6.0 и вы-
ше). Поэтому, если планируется совместимость со старыми браузе-
рами, тип undefined лучше не использовать.
Скалярные типы данных
JavaScript поддерживает три скалярных типа данных: логический, чи-
словой и строковый.
Логический тип данных boolean
Переменные этого типа могут иметь два значения ("истина" или
"ложь"), определяемых, соответственно, литералами true и false. Часто
эти значения являются результатами вычисления выражений и могут
быть либо сохранены как значения переменных, либо непосредственно
использованы в операторах условия, цикла и т. д. Оператор typeof для
переменных данного типа возвращает строку "boolean".
Конвертирование в boolean
При конвертировании в boolean следующие значения считаются равны-
ми false:
null;
undefined;
числовые 0 или NaN;
строковые, если длина строки равна 0.
Все остальные значения конвертируются в boolean как true.
Ãëàâà 1. Структура и синтаксис языка JavaScript 25
Числовой тип данных
Переменные числового типа в JavaScript могут содержать как целые, так
и действительные значения. Все числа представляются в формате
IEEE 754 и могут находиться в диапазоне от –253 до 253 в виде целых
значений либо в диапазонах от –1,7976931348623157 10308 до –5 10–324
и от 5 10–324 до 1,7976931348623157 10308 в виде значений двойной
точности с плавающей точкой. Оператор typeof для переменных число-
вого типа возвращает строку "number".
Числовые литералы
Литерально числовые значения могут задаваться в виде чисел в деся-
тичной, восьмеричной, шестнадцатеричной системе счисления и специ-
альных значений.
Числовые литералы в десятичной системе счисления записываются как
целые числа, либо как дробные числа в простой или экспоненциальной
форме с целой частью или без нее. Далее приводятся корректные при-
меры записи десятичных литералов:
.0001, 0.0001, 1e–4, 1.0e-4, 100, 1e2, 1e+2, 1.0e+2
Запись чисел в восьмеричной системе начинается с ведущего нуля.
Цифры, составляющие восьмеричное число, лежат в диапазоне от 0 до 7.
Примеры восьмеричной записи чисел:
0246, 00, 0777
Стандартом ECMA-262 не специфицируется восьмеричная форма
записи, однако практически все реализации JavaScript в современных
браузерах поддерживают такую возможность (правда, в браузере
Opera она появилась только после пятой версии).
Числовые шестнадцатеричные литералы записываются, начиная с по-
следовательности символов "0x" или "0X". Символами, составляющими
шестнадцатеричное число, являются цифры от 0 до 9 и латинские буквы
от A до F. Примеры записи шестнадцатеричных чисел приведены ниже:
0x0, 0XFF, 0x13579BDF, 0x0000FFFF
Существует несколько специальных значений, которые могут принимать
переменные числового типа. Это Infinity (-Infinity) и NaN. Значение
переменной может стать Infinity или -Infinity при превышении мак-
симально представимых значений числового типа при совершении
арифметических операций (например, деление на 0), либо вследствие
26 Часть I. Основы создания Web-страниц и скриптов
прямого присвоения этого значения переменной. Значение NaN возника-
ет тогда, когда результат вычислений имеет неопределенное значение
(например, деление 0 на 0).
Конвертирование в число
Конвертирование в число происходит в соответствии со следующими
правилами:
конвертирование null в число дает 0;
конвертирование строк дает число, если строка есть символьное
представление числа, либо NaN, если это не так;
boolean-значение конвертируется в 0, если оно false, и в 1, если оно
true;
все остальные значения конвертируются в NaN.
Строки
Как и в других языках, строки в JavaScript предназначены для хранения
данных в виде последовательности символов. Реализации JavaScript со-
временных браузеров поддерживают строки, которые могут содержать
как символы из набора ASCII, так и двухбайтовые Unicode-символы.
Литералы строк
Строковые литералы задаются путем заключения последовательности
символов в одинарные или двойные кавычки, причем вложенные кавыч-
ки другого типа будут являться частью строковых данных. Следующий
код корректен:
var str0 = "текст ' текст ' текст";
var str1 = 'текст " текст " текст';
Но нельзя, например, начинать и заканчивать строку кавычками разного
типа.
Для записи строкового литерала на нескольких строках программного
кода необходимо в конце строки кода добавлять обратный слэш:
var str = "это пример строкового \
литерала записанного \
в несколько строк";
Для представления в строках специальных символов (перевод строки,
табуляция), кавычек, слэшей, Unicode-символов и т. д. служат так назы-
ваемые escape-коды. Их список приведен в табл. 1.1.
Ãëàâà 1. Структура и синтаксис языка JavaScript 27
Таблица 1.1. Escape-коды
Код Значение
\r "Возврат каретки" — переход к началу строки
\n Перевод строки
\t Символ табуляции
\b Символ "backspace" — удаление предыдущего символа
\f Символ перехода к следующей странице
\v Символ вертикальной табуляции
\\ Слэш
\" Двойная кавычка
\' Одинарная кавычка
\xXX ASCII-символ. XX — его шестнадцатеричный код
\XXX ASCII-символ. XXX — его восьмеричный код
\uXXXX Unicode-символ. XXXX — его шестнадцатеричный код
Далее приведен пример записи строкового литерала с использованием
escape-кодов:
var str = "это \"строковый литерал\"\r\nunicode-символ: \u2211";
Unicode не поддерживается в версиях JavaScript ниже 1.3.
Массивы
Массивы в JavaScript представлены в виде объектов, имеющих свойства
упорядоченных карт. При помощи карты производится отображение
ключей в значения. Ключи массива могут быть представлены значения-
ми разных типов (не только целым числом). На основе массивов в
JavaScript достаточно просто можно реализовать различные, даже весь-
ма сложные структуры данных: словарь, стек, дерево.
Объекты массивов могут создаваться путем присвоения переменным
литеральных значений массивов либо при помощи оператора new.
28 Часть I. Основы создания Web-страниц и скриптов
Литерально массив определяется перечислением значений в квадратных
скобках []. При этом значения имеют целочисленный, последовательно
возрастающий от нуля индекс. Тип данных значений может быть лю-
бым. Значения могут быть представлены переменными, константами,
литералами, в том числе допускается использовать литералы массивов,
объектов, функций. Допустимо не указывать некоторые значения (в этом
случае их тип данных будет undefined). Пример литерального опреде-
ления массива:
var arr = [1, , ,'текст', [1, 2, 3],
function(){ alert('функция')}, 0,
100, { prop0: 10, prop1: 2 }, 1000];
Объект массива имеет три варианта конструктора, поэтому существуют
следующие способы создания массива при помощи оператора new:
var arr = new Array();
var arr = new Array(<размер массива>);
var arr = new Array(<значение0>, <значение1>, ...,
<значениеN>);
В первом случае создается пустой массив. Во втором — массив с чис-
лом элементов, равным указанным размером (тип всех элементов
undefined). Третий способ очень похож на определение массива с по-
мощью литерала — аналогично создается массив, заполненный указан-
ными значениями.
Адресация элементов массивов выполняется при помощи все той же
нотации квадратных скобок. Причем, поскольку массивы реализованы
как карты, то при попытке модификации элемента с несуществующим
индексом происходит добавление пары "ключ-значение" в массив:
var arr = [10, 20, 30];
var A = arr[1]; // теперь переменная A
// содержит значение 20
arr['color'] = 'green'; // в массив добавлено значение 'green'
A = arr['color']; // теперь переменная A
// содержит значение 'green'
Элемент массива может быть удален при помощи оператора delete. При
этом удаляется пара "ключ-значение", т. е. оператор typeof для удален-
ного элемента вернет undefined.
Ãëàâà 1. Структура и синтаксис языка JavaScript 29
Объекты
JavaScript является объектно-ориентированным языком. Объектный
подход в нем основан на прототипах. Это означает, что в отличие от
многих объектно-ориентированных языков, таких, например, как C++
или Java, базирующихся на концепции классов и экземпляров (где класс
являет собой абстрактную сущность, определяющую структуру и пове-
дение его экземпляров), в JavaScript существует только одно понятие —
объект, являющийся основой реализации объектно-ориентированного
подхода в этом языке. Далее мы рассмотрим методы использования
объектов в JavaScript — создание, модифицирование, реализацию на-
следования.
Основные понятия
Объект в JavaScript — это структура, позволяющая объединить неупо-
рядоченную совокупность данных и набор методов для работы с этими
данными.
Свойства
Объект содержит данные и ссылки на свои методы как свойства. Полу-
чить доступ к свойствам объекта можно двумя способами:
с помощью оператора . (точка).
с помощью синтаксиса доступа к элементам массива.
В обоих случаях при попытке модификации несуществующего свойства
такое свойство будет создано, и ему будет присвоено соответствующее
значение. Свойство объекта (как, впрочем, и сам объект) можно уда-
лить, используя оператор delete. Таким образом, список свойств и ме-
тодов объектов в JavaScript может изменяться на этапе прогона скрипта.
Далее приведен пример доступа к свойствам объекта myObject (предпо-
лагается, что объект уже создан).
var value_1 = myObject.Property_1; // доступ с помощью
// оператора "."
myObject['Property_2'] = 10; // доступ с помощью
// оператора []
delete myObject.Property_2; // удаление свойства
// Property_2
30 Часть I. Основы создания Web-страниц и скриптов
С помощью синтаксиса доступа к элементам массива можно ссылать-
ся на свойства объекта также по числовому индексу, например
myObject[5]. Обращение по имени к таким свойствам невозможно,
если только объект не представляет собой коллекцию элементов де-
рева документа браузера.
Методы
Методы объекта можно охарактеризовать как ассоциированные с ним
функции. Самый простой способ определить для объекта метод — при-
своить свойству объекта ссылку на некоторую функцию. Методы отли-
чаются от обычных функций тем, что могут ссылаться на объект, кото-
рому они принадлежат, с помощью ключевого слова this. Это делает
возможным доступ к свойствам и другим методам этого объекта. Далее
приведен пример добавления метода Method_0 к объекту myObject. Ме-
тод производит получение значения свойства Property_0 объекта через
ссылку this.
function f()
{
var prop_value = this.Property_0; // получение значения
// свойства
}
myObject.Method_0 = f; // создание метода объекта
myObject.Method_0(); // вызов метода (способ 1)
myObject['Method_0'](); // вызов метода (способ 2)
Создание объектов
В JavaScript объекты можно создавать либо при помощи оператора new,
указывая функцию-конструктор объектов определенного типа, либо с
помощью литеральной нотации, используя инициализаторы объектов.
Создание объектов в литеральной нотации
Создание объекта таким способом принципиально ничем не отличается
от создания переменной любого другого типа. Надо просто присвоить
новой переменной (объекту) соответствующий литерал. Литерал объек-
та представляет собой список пар свойств и соответствующих им значе-
ний, разделенных запятой. Такой список заключен в фигурные скобки, а
Ãëàâà 1. Структура и синтаксис языка JavaScript 31
имена свойств и значения разделены двоеточием. В общем виде это
можно записать так:
{ prop_1 : val_1, prop_2 : val_2, ... ,prop_N : val_N }
Здесь prop_1, prop_2, prop_N — имена свойств объекта, а val_1, val_2,
val_N — значения этих свойств. Значениями свойств могут являться
переменные, константы, литералы (в том числе литералы функций, мас-
сивов, объектов). Если в качестве значения свойства задан литерал
функции либо ее идентификатор, то данное свойство будет являться ме-
тодом объекта. Далее приведен пример создания объекта в литеральной
нотации. Обратите внимание на способы определения методов объекта.
function f()
{ return this.property_1 + this.property_2[1]; }
var myObject =
{
property_1: 10, // свойство с числовым значением
property_2: [1, 2, 3], // свойство со значением "массив"
Method_1 : f, // метод № 1
Method_2 : // метод № 2
function()
{ return this.Method_1() + 10 }
}
Создание объектов оператором new.
Конструкторы объектов. Типы объектов
Второй способ создания объектов заключается в написании функции-
конструктора (она будет определять тип объекта) и использовании опе-
ратора new совместно с этим конструктором. Конструкторам объектов,
так же как и обычным функциям, могут передаваться параметры. Это
обычно используется для присвоения начальных значений свойствам.
Вот простейший пример создания объекта таким образом.
function Point(x, y)
{
this.x = x || 0;
this.y = y || 0;
[Link] =
function(dx, dy)
32 Часть I. Основы создания Web-страниц и скриптов
{
this.x += dx;
this.y += dy;
}
}
var pt = new Point(0, 10);
[Link](5, 6);
Как видно из примера, конструктор Point специфицирует два свойства
объекта — x и y, а также один метод Offset. Затем создается объект pt
типа Point при помощи оператора new и производится вызов метода
Offset.
В конструкторе Point используется специальный синтаксис для при-
своения значений по умолчанию свойствам x и y (например, x || 0).
Здесь 0 — значение по умолчанию. Механизм работы данного метода
заключается в особенности исполнения операции "ИЛИ" (||) в
JavaScript. Эта операция возвращает значение первого аргумента,
если оно конвертируется в true, или второго, если это не так.
Наряду с конструкторами встроенных объектов (таких как String, Date),
ядро JavaScript содержит конструктор объектов, не имеющих свойств
Object. Его удобно использовать для создания "объектов-пустышек", к
которым впоследствии могут быть добавлены необходимые свойства и
методы. Например:
var obj = new Object();
obj.x = 10;
В ряде случаев может возникнуть необходимость добавления свойств,
либо методов к уже специфицированному типу объектов (как добавлять
свойства к отдельно взятому объекту, рассматривалось ранее). Это мо-
жет быть сделано при помощи свойства prototype. При этом добавлен-
ные свойства и методы будут присутствовать как у всех созданных до
этого, так и у всех вновь создаваемых объектов этого типа. Продолжив
предыдущий пример, добавим типу объектов Point метод SetToOrigin,
а затем вызовем этот метод для объекта pt:
[Link] = function()
{
this.x = this.y = 0;
};
[Link]();
Ãëàâà 1. Структура и синтаксис языка JavaScript 33
Здесь особо следует отметить тот факт, что если вы хотите иметь неко-
торое свойство, значение которого может изменяться сразу для всех
объектов определенного типа, то специфицировать и изменять это свой-
ство следует только указанным выше методом — через свойство
prototype конструктора.
Наследование
В JavaScript наследование реализуется путем ассоциирования объектов-
прототипов с функциями-конструкторами, при этом используется свой-
ство prototype конструктора. Другими словами, если требуется создать
тип объектов, наследующий свойства и методы некоторого объекта, не-
обходимо присвоить свойству prototype функции-конструктора этот
объект. Далее приведен пример, демонстрирующий принцип реализации
наследования в JavaScript.
function Object_1(x)
{
this.x = x || 0;
}
function Object_2(x, y)
{
[Link] = Object_1;
[Link](x); // вызов конструктора Object_1
// для инициализации свойств
// объекта-прототипа
this.y = y || 0;
}
Object_2.prototype = new Object_1; // устанавливаем для
// Object_2 объект-прототип
var obj = new Object_2(10, 20); // создаем объект типа
// Object_2, который
// наследует свойство x,
// определяемое в Object_1
В этом примере путем определения функций-конструкторов специфици-
руются два типа объектов: Object_1 и Object_2. Затем создается простая
иерархия — Object_2 наследует свойства Object_1.
В примере существует один немаловажный момент, который необходи-
мо отметить особо. В конструктор Object_1 передается параметр x, зна-
34 Часть I. Основы создания Web-страниц и скриптов
чение которого служит для инициализации свойств объектов типа
Object_1. Скорее всего, нам захочется иметь возможность прозрачной
инициализации свойств, наследуемых Object_2 от Object_1 при созда-
нии объектов типа Object_2. Поэтому логично передавать параметр x в
конструктор Object_2, а затем вызывать конструктор Object_1 с этим
параметром. Обратите внимание на то, как это делается. Сначала неко-
торому свойству объекта (в примере это свойство parent, но имя свой-
ства может быть любым) присваивается ссылка на конструктор
Object_1, а затем производится вызов этого конструктора как метода
Object_2 через значение этого свойства. Зачем? Дело в том, что конст-
руктор Object_1 определяет свойства или методы создаваемого объекта
(в нашем случае это свойство x) через ссылку this на этот объект.
А ссылка this передается в функцию, если она является методом объек-
та. Вызывая конструктор Object_1 как метод Object_2, мы обеспечива-
ем передачу в Object_1 через this-ссылки на тот же объект, с которым
работаем в конструкторе Object_2. Важно не прерывать цепочку вызо-
вов конструкторов, если мы не хотим инициализации некоторых свойств
объектов параметрами по умолчанию.
Операторы
В JavaScript существует достаточно много операторов, позволяющих
осуществлять различные операции с данными. Операторы можно разде-
лить на несколько групп в зависимости от их функционального назначе-
ния. В выражениях операторы выполняются в последовательности, оп-
ределяемой значением их приоритета — сначала выполняются операции
с высшим приоритетом. Далее приведены списки операторов JavaScript,
сгруппированные по категориям (табл. 1.2—1.6). Более высокий при-
оритет здесь обозначен меньшими числовыми значениями.
Таблица 1.2. Арифметические операторы
Количество
Оператор Приоритет Операция
операндов
- 1 1 Унарный минус
- 3 2 Вычитание
+ 3 2 Сложение
* 2 2 Умножение
/ 2 2 Деление
Ãëàâà 1. Структура и синтаксис языка JavaScript 35
Таблица 1.2 (окончание)
Количество
Оператор Приоритет Операция
операндов
% 2 2 Деление по модулю (остаток от
деления)
-- 1 1 Декремент
++ 1 1 Инкремент
Таблица 1.3. Битовые операторы
Количество
Оператор Приоритет Операция
операндов
~ 1 1 Поразрядная инверсия
& 7 2 Поразрядное "И"
| 9 2 Поразрядное "ИЛИ"
^ 8 2 Поразрядное исключающее
"ИЛИ"
<< 4 2 Поразрядный сдвиг влево
с учетом знака
>> 4 2 Поразрядный сдвиг вправо
с учетом знака
>>> 4 2 Поразрядный сдвиг вправо
без учета знака
Таблица 1.4. Операторы присваивания
Количество
Оператор Приоритет Операция
операндов
= 13 2 Присваивание
+=, &=, |=, 13 2 Составные операторы присваи-
^=, /=, вания. Между первым и вторым
<<=, %=, операндом выполняется соот-
*=, >>=, - ветствующая операция, а затем
=, >>>= результат присваивается перво-
му операнду
36 Часть I. Основы создания Web-страниц и скриптов
Таблица 1.5. Логические операторы
Количество
Оператор Приоритет Операция
операндов
! 1 1 Логическое отрицание
< 5 2 Меньше, чем
> 5 2 Больше, чем
<= 5 2 Меньше или равно
>= 5 2 Больше или равно
== 6 2 Равенство
!= 6 2 Неравенство
=== 6 2 Строгое соответствие
!== 6 2 Строгое несоответствие
&& 10 2 Логическое "И"
|| 11 2 Логическое "ИЛИ"
?: 12 3 Условие (единственная
тернарная операция)
Таблица 1.6. Специальные операторы
Количество
Оператор Приоритет Операция
операндов
. 0 1 Доступ к свойству объекта
[ ] 0 1 Доступ к элементу массива,
или свойству объекта
() 0 ~ Группировка операций или
вызов функции
, 14 2 Последовательное вычисле-
ние выражений
new 1 1 Создание объекта
delete 1 1 Удаление переменных,
свойств, элементов массива
in 0 2 Проверка существования
свойства
Ãëàâà 1. Структура и синтаксис языка JavaScript 37
Таблица 1.6 (окончание)
Количество
Оператор Приоритет Операция
операндов
void 1 1 Определяет выражение без
возвращаемого значения
typeof 1 1 Определение типа
instanceof 1 1 Проверка типа объекта
Структуры управления
В JavaScript существует достаточно развитый набор языковых конст-
рукций для управления потоком вычислений, позволяющий реализовы-
вать алгоритмы любой степени сложности.
Конструкции условного исполнения
Оператор if...else
Эта конструкция позволяет выполнять операторы в зависимости от ис-
тинности логического условия. Предложение if...else записывается
следующим образом:
if(<условие>) { <блок операторов 1> }
[ else { <блок операторов 2> } ]
В случае, если выражение, передаваемое оператору if, вычисляется в
true, будет выполнен первый блок операторов, иначе — второй. Пред-
ложение else может быть опущено, если не требуется выполнять каких-
либо операторов при вычислении логического условия в false.
В JavaScript существует сокращенная форма условного оператора:
<условие> ? <выражение 1> : <выражение 2>
Это тернарный оператор, возвращающий результат вычисления одного
из выражений в зависимости от истинности значения, передаваемого в
качестве первого операнда (<условие>). Если условие истинно, то вы-
числяется первое выражение, иначе — второе.
Оператор switch
switch является оператором многоальтернативного выбора, позволяю-
щим выполнять определенный набор инструкций в зависимости от ре-
38 Часть I. Основы создания Web-страниц и скриптов
зультата вычисления некоторого выражения. Оператор switch записы-
вается так:
switch(<выражение>)
{
case <значение1> : <инструкция1>; break;
case <значение2> : <инструкция2>; break;
...
case <значениеN> : <инструкцияN>; break;
default : <инструкция>
}
При выполнении этого оператора ищется значение, указанное в опера-
торе case, совпадающее с результатом вычисления выражения-
аргумента switch. Если такое значение найдено, то выполняется ассо-
циированный с соответствующим case набор инструкций. Если нет, то
производится поиск предложения default в конструкции switch, и если
оно найдено, то выполняются соответствующие ему операторы. Если же
и default не найдено, то выполнение сразу передается на инструкцию,
следующую за оператором switch. Обратите внимание на операторы
break, завершающие наборы инструкций в предложениях case. Они
нужны для обеспечения выполнения набора операторов, ассоциирован-
ных только с одним предложением case.
Циклы
Операторы циклов позволяют производить многократное выполнение
наборов инструкций при условии истинности некоторого утверждения.
В JavaScript определено несколько операторов для реализации цикличе-
ского исполнения.
for
Синтаксис данной конструкции практически ничем не отличается от
синтаксиса for в языках C или PHP.
for(<инициализирующее выражение>; <условие>;
<обновляющее выражение>)
<инструкции тела цикла>
Цикл for выполняется до тех пор, пока выражение, указанное как усло-
вие, вычисляется в true. Инициализирующее выражение вычисляется
Ãëàâà 1. Структура и синтаксис языка JavaScript 39
единственный раз — при начале исполнения оператора for. Оно обычно
используется для присвоения начальных значений переменным, исполь-
зуемым в выражении условия цикла. Выражение условия вычисляется
каждый раз перед выполнением тела цикла. Тело цикла может ни разу
не выполниться, если на первом же проходе выражение условия вычис-
ляется в false. Обновляющее выражение вычисляется после каждого
прохода цикла, т. е. после каждого исполнения инструкций тела цикла.
Для инициализации или изменения сразу нескольких переменных ини-
циализирующее и обновляющее выражения удобно составлять из не-
скольких выражений, комбинируя их с помощью оператора мультипли-
кативного вычисления ,.
for...in
Этот оператор предоставляет простую возможность итерирования по
всем свойствам объекта. Синтаксис оператора таков:
for(<переменная> in <объект>)
<инструкции тела цикла>
Тело цикла будет выполнено столько раз, сколько свойств имеет объект,
указанный после оператора in, при этом переменной, указанной перед
in, будет последовательно присваиваться строковое значение, содержа-
щее имя текущего свойства. Это может быть использовано, например,
для итерирования по массиву, содержащему нечисловой или непоследо-
вательный индекс, в отладочных целях и т. д.
while
Это самый простой из операторов цикла. Тело цикла выполняется, пока
значением выражения-условия является true. Синтаксис этой конструк-
ции таков:
while(<условие>)
<инструкции тела цикла>
do...while
В противоположность предыдущему оператору, тело do...while выпол-
няется, пока выражение условия цикла вычисляется в false. Синтаксис
do...while таков:
do <инструкции тела цикла> while(<условие>)
40 Часть I. Основы создания Web-страниц и скриптов
Метки, операторы break и continue
Метки
JavaScript поддерживает использование меток в программном коде.
Синтаксис определения меток идентичен соответствующему синтаксису
в языке C.
<имя метки> : <инструкция>
Метки обеспечивают возможность идентификации инструкций для по-
следующей ссылки на них из операторов break и continue по имени
меток. Правила определения имен для меток аналогичны правилам име-
нования переменных и констант.
Оператор break
Оператор break применяется для завершения выполнения циклов, кода в
конструкции switch, а также операторов, идентифицируемых при по-
мощи определенной метки. Таким образом, этот оператор может приме-
няться в двух контекстах:
без ссылки на конкретный оператор. В этом случае break прерывает
выполнение цикла либо инструкции switch, в теле которой он со-
держится;
ссылаясь на инструкцию с помощью имени метки. В этом случае
прерывается выполнение указанной инструкции. Это удобно исполь-
зовать для выхода из вложенных циклов (в других языках для этого
иногда используется оператор goto).
Далее приведен пример, демонстрирующий синтаксис и применение
оператора break.
m1: for(var i = 0; i < 10; i++)
for(var j = 0; j < 10; j++)
if(i == 1)
break m1;
else
break;
Оператор continue
Оператор continue позволяет прервать исполнение тела цикла, начав
новую итерацию. Использование continue очень похоже на применение
break с той лишь разницей, что выполнение цикла не прерывается пол-
ностью. Вместо этого производится исполнение кода в той последова-
Ãëàâà 1. Структура и синтаксис языка JavaScript 41
тельности, как если бы тело цикла было исполнено (в цикле while про-
изводится проверка условия, в цикле for — сначала выполняется обнов-
ляющее выражение, затем проверяется условие). Оператор continue
аналогично break может использоваться как в сочетании с меткой, так и
без нее. Если после continue указывается метка, идентифицирующая
цикл, то производится переход на новую итерацию этого цикла. Если же
continue указывается без метки, то начинается новая итерация цикла,
тело которого непосредственно содержит данный оператор. Далее при-
веден пример использования оператора continue как с меткой, так и без.
m1: for(var i = 0; i < 10; i++)
for(var j = 0; j < 10; j++)
if(j < 5)
continue;
else
continue m1;
Генерация и обработка исключений
Для обработки исключений в JavaScript существует конструкция
try...catch...finally. Для возбуждения исключений используется
оператор throw. Это сравнительно новые структуры языка. Они специ-
фицируются в третьей версии стандарта ECMAScript и поддерживаются,
начиная с JavaScript 1.4 и JScript 5.0.
Оператор throw
Этот оператор служит для программного "вбрасывания" исключения.
Данная возможность может быть использована, например, для прерыва-
ния исполнения большого количества операторов, для выхода из многих
вложенных циклов и т. д. Синтаксис оператора throw прост.
throw <выражение>;
Выражение, являющееся аргументом оператора, будет передано в блок
catch при обработке исключения. Его значением может быть строка,
число, либо любой другой объект. Посредством этого значения удобно
передавать в блок catch информацию о причине вбрасывания исклю-
чения.
Конструкция try...catch...finally
Эти операторы предоставляют возможность обрабатывать ошибки, воз-
никающие на этапе прогона, и продолжать дальнейшее выполнение
скрипта.
42 Часть I. Основы создания Web-страниц и скриптов
Синтаксис операторов следующий:
try { <инструкции блока try> }
catch(<идентификатор исключения>) {<инструкции блока catch>}
[ finally { <инструкции блока finally> } ]
Конструкция работает следующим образом. Сначала исполняются инст-
рукции в блоке try. Если на этом этапе возникает ошибка (или выполня-
ется оператор throw), управление передается в блок catch. Предложение
finally не является обязательным, но если оно присутствует, то блок
операторов finally выполняется в любом случае, вне зависимости от
того, возникла ли ошибка в блоке try. Блок операторов finally выпол-
няется только после исполнения блоков try и/или catch, что делает его
удобным местом для расположения кода освобождения данных, создан-
ных в блоке try.
Как видно из приведенного примера синтаксиса, в операторе catch спе-
цифицируется некий идентификатор. Этот идентификатор действителен
только во время исполнения блока catch и определяет значение, пере-
данное оператору throw, возбудившему исключение, либо специальный
объект, созданный браузером (если исключение возникло не по причине
применения throw) и позволяющий получить информацию о причине
ошибки.
Далее приведен пример использования конструкции try...catch...
finally для предотвращения возникновения ошибки времени исполне-
ния скрипта. В блоке try производится попытка создать объект несуще-
ствующего типа DummyObject, в блоке catch выводится сообщение об
ошибке, а в блоке finally очищаются данные, созданные в try.
try
{
var arr = new Array(10000);
var obj = new DummyObject();
}
catch(e)
{
alert('Ошибка времени исполнения: ' + e);
}
finally
{
delete arr;
}
alert('Выполнение скрипта продолжается...');
Ãëàâà 1. Структура и синтаксис языка JavaScript 43
Функции
Традиционно функции используются для реализации структурного под-
хода в программировании. Ранее было показано, как ассоциировать
функции в качестве методов объектов при программировании с исполь-
зованием объектно-ориентированной парадигмы. Сейчас мы рассмот-
рим более подробно возможности специфицирования и использования
функций в JavaScript.
Специфицирование функций
Функции в JavaScript могут быть специфицированы несколькими спосо-
бами:
декларирование с помощью ключевого слова function;
создание с помощью литерала функции;
создание в виде объекта с помощью оператора new.
Декларирование с помощью ключевого слова function
Это классический метод определения функции. Пример определения
функции с именем f0 данным способом таков:
function f0(x, y) { return x + y; }
Создание с помощью литерала функции
Литерал функции состоит из ключевого слова function, списка аргу-
ментов, заключенных в круглые скобки, и операторного блока, состав-
ляющего тело функции. Вот пример специфицирования функции f1
в литеральной нотации:
var f1 = function(x, y) { return x + y; }
Создание в виде объекта с помощью оператора new
Ядро JavaScript содержит конструктор объектов-функций с именем
Function, позволяющий специфицировать функции при помощи синтак-
сиса создания объектов.
Конструктор Function принимает переменное число параметров строко-
вого типа. Последним параметром передается строка, содержащая код
тела функции (без фигурных скобок). Все остальные параметры пред-
44 Часть I. Основы создания Web-страниц и скриптов
ставляют собой названия аргументов. Если функция не имеет аргумен-
тов, то в конструктор передается только один параметр — строка тела
функции. Пример создания функции f2 следующий:
var f2 = new Function("x", "y", "return x + y;");
Вызов функций, аргументы функций
Вызов функции осуществляется при помощи оператора () — круглых
скобок. В скобках указывается список параметров, передаваемых в
функцию. Например:
f0(10, 14);
JavaScript поддерживает рекурсивные вызовы, т. е. функция может вы-
зывать сама себя. Классический пример функции вычисления факториа-
ла может выглядеть так:
function f(n)
{ return ((n == 0) || (n == 1)) ? 1 : (n * f(n-1)); }
В JavaScript возможна передача в функцию большего количества пара-
метров, чем определено ее прототипом, а в теле доступен массив
arguments, с помощью которого можно адресовать параметры функции
по их порядковому номеру. Это делает возможным реализацию функ-
ций, принимающих переменное число аргументов. В качестве примера
приведу функцию, осуществляющую конкатенацию строковых пред-
ставлений значений переменного количества аргументов.
function concat(n)
{
var str = "";
for(var i = 0; i < [Link]; i++)
str += arguments[i];
return str;
}
alert('1 ', '2 ', '3 ', '4 ', '5 '); // вызов функции
Обратите внимание на то, что в прототипе функции определен всего
один параметр, в то время как при вызове параметров передается пять.
ГЛАВА 2
Язык HTML
Должно быть, сегодня не найти человека, который не был бы знаком с
понятием "Интернет", обозначающим глобальную мировую сеть ин-
формационных ресурсов. Безусловно, Интернет в настоящее время яв-
ляется крупнейшим распределенным медианосителем и проводником
информации. Часть содержимого глобальной сети представлено в виде
гипертекстовых документов. Гипертекст дает возможность удобного и
быстрого доступа к сервисам и ресурсам сети, предоставляющим муль-
тимедийное содержимое различного типа. Данные гипертекстовых до-
кументов чаще всего представлены в формате, определяемом языком
разметки HTML (HyperText Markup Language), являющимся на данный
момент самым популярным языком структурной разметки, используе-
мым в Web.
Объем данной главы очень мал. К тому же, в задачи книги не входит
подробное рассмотрение всех аспектов HTML — с этим гораздо лучше
справится спецификация языка. Цель этой главы — дать читателю по-
нимание того, чем является HTML, и какие средства он предоставляет
разработчику для создания Web-страниц.
Чем является и чем не является HTML
Сразу следует сказать, что HTML является приложением SGML (Stan-
dardized Generalized Markup Language) — обобщенного языка разметки,
т. е. одной из его реализаций. SGML — язык структурной разметки.
Точнее, это система определения языков разметки. Поэтому HTML,
прежде всего, — язык структурной разметки. Действительно, специфи-
46 Часть I. Основы создания Web-страниц и скриптов
кация не определяет многих аспектов визуального представления доку-
ментов в формате HTML. В настоящее время при помощи разметки в
большей мере задается логическая структура документа, в то время, как
представление определяется таблицами стилей (см. главу 3).
HTML применяется для создания электронных документов с возмож-
ностью использования в них различных элементов оформления и струк-
турирования (шрифтов, связанных изображений, таблиц, списков). До-
кументы могут содержать формы, с помощью которых возможен ввод
данных и их отправка на удаленный сервер, а также включать такие
элементы, как апплеты и компоненты ActiveX. Таким образом, HTML
является одним из общепринятых универсальных средств формирования
структурированных, интерактивных документов, публикуемых в Web.
Чем точно не является HTML, так это языком программирования.
HTML вообще не имеет отношения к программированию. Даже скрип-
ты, непосредственно внедренные в документ, не интерпретируются раз-
борщиком, а передаются ядру исполнения сценариев.
Немного истории
HTML начал активно распространяться в 1990-х годах, благодаря его
использованию в браузере Mosaic и росту популярности Web. Разработ-
чиком HTML является Тим Бернерс-Ли. В тех же 1990-х годах, вследст-
вие внесения в HTML различных расширений, встала проблема обеспе-
чения корректного отображения существующих документов всеми брау-
зерами, что подразумевало необходимость выработки стандарта. В 1995 го-
ду были разработаны спецификации HTML 2.0 и HTML 3.0. Работа по
стандартизации велась группой консорциума W3C и в начале 1997 года
создается версия HTML 3.2, а в конце 1997 года публикуется специфи-
кация HTML 4.0. Позже, в декабре 1999 года становится доступной по-
следняя на данный момент версия спецификации HTML 4.01.
Ссылки на документацию
Официальными нормативными версиями спецификаций HTML являют-
ся только документы консорциума W3C на английском языке. Они рас-
положены по адресам:
[Link] (HTML 4, последняя версия);
[Link] (HTML 4.01, последняя версия);
[Link] (спецификация последней версии HTML).
Ãëàâà 2. Язык HTML 47
Однако спецификации переводились на многие языки мира (в том числе
и на русский). Список ссылок на переводы можно найти по адресу:
[Link] Также, на при-
лагаемом к книге компакт-диске в каталоге w3c\html находятся версии 4
и 4.01 спецификаций HTML. В частности, в каталоге w3c\html\4.01\rus\
находится русский перевод спецификации HTML 4.01, выполненный
Александром Пирамидиным.
Языковые конструкции HTML
Предоставляя авторам достаточно обширный набор выразительных
средств, HTML является очень простым языком разметки.
Элементы
Документ в формате HTML представляется набором элементов разного
типа. Структура набора определяет визуальное представление доку-
мента.
Элементы вводятся в документ при помощи тегов. Обычно элемент со-
стоит из трех частей: начального тега, содержимого и конечного тега.
Тип элемента определяется по его имени, указываемому в начальном и
конечном теге. Теги ограничиваются знаками < и >. Конечный тег опре-
деляется наличием знака / (прямой слэш) перед именем тега. Таким об-
разом:
<H1> — начальный тег;
</H1> — конечный тег.
Использование некоторых элементов (таких как LI, DD, P) допускается
без указания конечных тегов. Для небольшого количества элементов
есть возможность опускать и начальные теги (например, BODY, HEAD).
Существуют также элементы, не имеющие содержимого (BR, IMG). Для
них использование конечных тегов запрещено.
Примечание
Иногда возникает путаница с употреблением терминов тег и эле-
мент — элементы называют тегами. Это две разные сущности. Тег
является конструкцией, при помощи которой элемент вводится в со-
став документа. Однако, как указано выше, у элемента могут отсутст-
вовать теги, в то время как сам элемент присутствует (например,
BODY).
48 Часть I. Основы создания Web-страниц и скриптов
Атрибуты
Элементы часто имеют ассоциированные с ними свойства, называемые
атрибутами. Чаще всего атрибутам также сопоставлено некоторое зна-
чение.
Атрибуты элементов HTML указываются в начальных тегах (через про-
бел после имени элемента). Значения атрибутов записываются через
знак = после их имени. Пары вида "атрибут=значение" записываются в
произвольном порядке через пробел, их количество не ограничено.
Обычно значение атрибута должно заключаться в одинарные либо
двойные кавычки. Кавычки одного типа могут входить в значение атри-
бута, но при этом атрибут необходимо заключать в кавычки другого
типа. Можно также указывать значение атрибута без кавычек, но при
условии, что оно содержит только латинские буквы, цифры, знак - (ми-
нус) и точку.
Существуют также атрибуты логического типа. Факт указания такого
атрибута устанавливает соответствующее свойство элемента в значение
"истина", факт отсутствия — в "ложь". Для атрибутов данного типа до-
пустимо сопоставление единственного значения, равного их собствен-
ному имени. Допускается также употребление этих атрибутов в миними-
зированной форме — в начальном теге элемента указывается только имя
атрибута (кстати, именно такая форма записи более широко распростра-
нена).
В приведенном далее фрагменте HTML-кода для элементов INPUT опре-
деляются атрибуты type и checked, причем второй из них — логическо-
го типа. Обратите внимание, как он употребляется в полной и сокра-
щенной форме.
<FORM>
<INPUT type="CHECKBOX" checked>Флажок 1</INPUT><br>
<INPUT type="CHECKBOX" checked="checked">Флажок 2</INPUT>
</FORM>
Комментарии
Комментарии в HTML не делятся на однострочные и многострочные —
любой комментарий может быть записан в несколько строк. Признаком
начала комментария является последовательность символов <!--, при-
знаком завершения — -->. Значит, комментарий может выглядеть сле-
дующим образом:
<!-- первая строка комментария
вторая строка комментария -->
Ãëàâà 2. Язык HTML 49
Структура документа HTML
Следуя спецификации HTML 4.0, в документе выделяют три части:
строка объявления типа документа;
раздел заголовков (задается элементом HEAD);
тело документа (задается элементом BODY или FRAMESET).
Далее приведен пример простейшего документа в формате HTML
с явным определением всех, указанных выше частей.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"
"[Link]
<HTML>
<HEAD> <!-- раздел заголовка --> <TITLE>Заголовок<TITLE>
</HEAD>
<BODY> <!-- тело документа --> </BODY>
</HTML>
Обратите внимание, что раздел заголовка и тело документа должны со-
держаться в элементе HTML. Также очень желательно, чтобы в раздел
HEAD был включен элемент TITLE, задающий название документа. Назва-
ние позволяет пользователям легко идентифицировать документ, а так-
же обычно используется браузерами при отображении журнала посе-
щенных страниц, каталога закладок и т. д.
В приведенном примере присутствует строка объявления типа докумен-
та. Тип документа объявляется путем указания DTD (Document Type
Definition, определение типа документа). Спецификация HTML 4.0 оп-
ределяет три DTD:
строгое DTD. Это определение включает описание элементов и ат-
рибутов, не помеченных в спецификации как нежелательные и не ис-
пользующихся в документах с фреймами;
промежуточное DTD. Оно включает все определения строгого DTD,
а также определения элементов и атрибутов, помеченных в специфи-
кации как нежелательные;
DTD для документов, использующих фреймы. Это DTD включает
определения промежуточного DTD, а также специфицирует элемен-
ты и атрибуты, относящиеся к фреймам.
В документах, соответствующих тому или иному DTD, нужно использо-
вать следующие строки объявления типа документа.
50 Часть I. Основы создания Web-страниц и скриптов
строгое DTD:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"
"[Link]
промежуточное DTD:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"[Link]
DTD для документов, использующих фреймы:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN"
"[Link]
Основные элементы Web-страниц
Как уже было отмечено, HTML предоставляет разработчикам весьма
обширный набор средств для создания сложных гипертекстовых доку-
ментов, позволяя использовать элементы структурирования (заголовки,
абзацы, списки, таблицы), внедренные объекты, кадры (фреймы) и т. д.
В данном разделе мы рассмотрим основные типы элементов, из которых
состоят Web-страницы, и методы их формирования с помощью размет-
ки HTML.
Текст
Далее кратко описаны элементы структурирования и визуального фор-
матирования текста.
Набор символов документа HTML
По причине того, что ASCII является крайне ограниченным набором
символов, не включающим символы национальных алфавитов и многие
специальные знаки, он не пригоден для широкого представления ин-
формации в Web. HTML использует набор UCS (Universal Character Set,
универсальный набор символов), фактически идентичный набору
Unicode 2.0.
Ссылки-мнемоники на символы
Поскольку ввод и отображение в текстовых редакторах многих специ-
альных знаков из набора символов документа может быть затруднен или
невозможен, в HTML предусмотрен универсальный способ записи таких
Ãëàâà 2. Язык HTML 51
символов в виде их мнемонических обозначений, или ссылок на симво-
лы. Запись ссылки на символ (мнемоники) начинается знаком & и закан-
чивается точкой с запятой ;. Мнемоники могут быть двух видов:
числовые ссылки на символы;
символические мнемоники.
Ссылки в числовой форме включают в себя код Unicode-символа в деся-
тичном либо шестнадцатеричном представлении. Перед кодом символа
должен присутствовать знак #. Признаком использования шестнадцате-
ричного представления является префикс x (или X) в коде символа. На-
пример, ссылка ∑ (шестнадцатеричное представление) представ-
ляет символ , а ссылка © (десятичное представление) знак .
Символическая мнемоника представляется в виде имени, обычно ассо-
циирующимся с обозначаемым символом. Например, знак может
быть обозначен ссылкой ©, а знак & — ссылкой &. В HTML
определено много ссылок на символы. Их список вы можете найти
в соответствующих разделах спецификаций (например, в файле
w3c\html\4.01\rus\[Link]).
Элементы форматирования текста
Простейшее форматирование текста может осуществляться путем выде-
ления в нем частей, например, при помощи определения абзацев, либо
разделения блоков текста при помощи перевода строки.
Для указания в тексте абзаца служит элемент P. Он должен иметь на-
чальный тег, но может не иметь конечного. Абзац не должен иметь вло-
женных элементов уровня блока (подробнее см. главу 3). Для элемента P
может быть задан атрибут align, определяющий выравнивание текста в
абзаце.
Принудительный переход на новую строку осуществляется при помощи
элемента BR (для него запрещен конечный тег). Имеется также возмож-
ность запрета переноса. Так весь текст, помещенный в элемент NOBR,
будет выводиться непрерывно в одну строку, если только в него не
включены элементы BR. Можно запретить возможность переноса на но-
вую строку между двумя конкретными словами, если в качестве разде-
лителей использовать только символы неразрывного пробела, представ-
ляющиеся с помощью ссылок на символы ,  ,  .
В HTML также определен элемент PRE, позволяющий вводить в доку-
мент блоки текста, отформатированного "стандартным образом" — при
52 Часть I. Основы создания Web-страниц и скриптов
помощи пробельных символов, табуляции и символов перевода строки.
Для PRE обязательны начальный и конечный теги. При выводе текста,
содержащегося в этом элементе, браузеры учитывают большинство не-
печатных символов, т. е., например, выводят несколько идущих подряд
пробелов, делают переход на новую строку, если встречается символ
возврата каретки, и т. д. Кроме того, для отображения таких блоков
обычно используется шрифт с символами одинаковой ширины.
Элементы структурирования текста
Следующие элементы позволяют определять структуру текста:
элементы обозначения цитат (BLOCKQUOTE, Q);
фразовые элементы (ABBR, ACRONYM, CITE, CODE, DFN, EM, KBD, STRONG,
SAMP, VAR);
элементы обозначения верхних и нижних индексов (SUB, SUP).
Одни из них порождают структурные, другие строковые блоки, и обыч-
но текст, содержащийся в этих элементах, отображается браузерами
специальным образом (например, текст, заключенный в BLOCKQUOTE,
имеет отступ от левого края, а текст в STRONG выделен жирным начерта-
нием). За более подробной информацией об этих элементах и их атрибу-
тах также отсылаю к спецификации.
Шрифты
В соответствии с принципом разделения структуры и представления,
в настоящее время разработчики обычно выносят определения стиля, цве-
та шрифтов документа в каскадные таблицы стилей CSS, однако доста-
точно часто встречается и употребление приведенных далее элементов.
Элементы определения стиля шрифта
Для того чтобы задавать стиль начертания шрифта, в HTML существуют
следующие элементы: B, BIG, I, S, SMALL, STRIKE, TT, U. Далее приведено
их описание:
B — текст элемента выводится полужирным шрифтом;
BIG — "крупный" шрифт;
I — курсивный шрифт;
S, STRIKE — перечеркнутый шрифт;
SMALL — уменьшенный шрифт;
Ãëàâà 2. Язык HTML 53
TT — текст элемента выводится шрифтом с одинаковой шириной
символов;
U — подчеркнутый шрифт.
Для всех этих элементов обязательны начальный и конечный теги. Эле-
менты могут быть вложены друг в друга, при этом стиль шрифта текста
определяется областью действия текущих элементов — сочетаемые сти-
ли могут комбинироваться.
Элементы определения параметров шрифта
В HTML существуют два элемента, позволяющие определять гарнитуру,
размер и цвет шрифта. Это элементы FONT и BASEFONT. Оба элемента
помечены в спецификации как нежелательные (вместо них предпочти-
тельнее использовать таблицы стилей). Для этих элементов допустимо
задавать значения следующих атрибутов:
size — устанавливает размер шрифта. Размер может задаваться:
в абсолютных (целые числа от 1 до 7) или относительных (значения
вида "-2", "+3") единицах. Относительные значения задают величину
изменения размера шрифта относительно использовавшегося ранее.
Единицы измерения размера шрифта — условные, представление
шрифта зависит от браузера;
color — задает цвет шрифта;
face — задает список разделенных запятыми имен гарнитур шриф-
тов, которые могут быть использованы для отображения текста. По-
рядок их следования определяет приоритет использования.
При помощи элемента BASEFONT обычно устанавливается базовый раз-
мер шрифта (атрибут size). Если далее для изменения размера шрифта
используется элемент FONT, то изменения происходят относительно это-
го размера. Базовый размер шрифта для документа по умолчанию ра-
вен 3. Элемент FONT изменяет размер, цвет и гарнитуру шрифта для
включаемого в него текста.
Ссылки
Безусловно, именно ссылки (или гиперссылки) являются основой попу-
лярности гипертекстовых документов, их основной и неотъемлемой
конструкцией. Ссылки связывают гипертекстовые документы и другие
ресурсы, доступные в Web.
54 Часть I. Основы создания Web-страниц и скриптов
Понятие "ссылка" тесно связано с понятием "якорь". Обычно ссылку
определяют как два связанных якоря — исходный (попросту источник)
и целевой. При активизации якоря-источника обычно происходит пере-
ход к целевому якорю, который может являться любым ресурсом (в том
числе, гипертекстовым документом или элементом в том же либо дру-
гом документе). В исходном якоре обычно указывается адрес целевого
якоря при помощи URI.
Создание ссылок и якорей
Атрибуты многих элементов HTML (таких как IFRAME, IMG) могут со-
держать ссылки на другие ресурсы. Далее описан только элемент A, по-
скольку с помощью него создаются ссылки, визуально представленные в
документе и доступные для активизации пользователем, а также якоря,
которые могут являться целевыми для ссылок.
Для элемента A доступна установка таких атрибутов, как href, id, name.
Если у элемента A определен атрибут href, то такой элемент является
ссылкой, и значением href должен быть URI целевого якоря (понятие
URI рассмотрено далее). Если у этого элемента установлены атрибуты
id либо name, то такой элемент будет являться якорем, на который мож-
но сослаться при помощи указания его URI. Стоит заметить, что атрибу-
ты href, name, id могут быть одновременно определены для элемента A.
В этом случае элемент является одновременно и ссылкой, и якорем.
Пример ссылки и якоря таков:
<A href="[Link] ссылка</A>
<A name="top">а это якорь</A>
URI
Термин URI (Universal Resource Identifier) переводится как универсаль-
ный идентификатор ресурсов. URI представляет собой строку, иденти-
фицирующую информационные ресурсы различных типов.
В структуре URI можно выделить несколько частей:
обозначение протокола доступа к данным ресурса;
адрес машины, содержащей ресурс;
имя самого ресурса.
Имя ресурса часто представлено в виде пути к нему на целевой машине.
Вследствие специфики, в гипертекстовых документах, публикуемых в
Web, чаще всего применяются ссылки, включающие URI, идентифици-
Ãëàâà 2. Язык HTML 55
рующие ресурсы, доступные по протоколам HTTP, FTP, а также URI
адресов электронной почты. Например, [Link]
/[Link] или [Link]
В ссылках из гипертекстовых документов часто используют URI, яв-
ляющиеся адресами якорей в этом же или другом гипертекстовом доку-
менте. В таких URI указывается значение атрибута id или name целевого
якоря через знак # после имени документа (имя документа может не
указываться, если ссылка ведет на тот же документ). Пример таких
ссылок:
<a href="[Link] № 1</a>
<a href="#top">ссылка № 2</a>
В документах, объединенных логически в одну группу и находящихся на
одной машине (если в Web, то и на одном домене), часто используются
относительные URI. В таких URI не указывается адрес машины, а путь
может быть относительным:
<a href="../[Link]">ссылка</a>
Списки
Одним из элементов структурированного представления информации
в HTML являются списки. Списки должны содержать элементы списка
и могут быть нескольких видов:
неупорядоченный список — создается элементом UL, элементы спи-
ска создаются элементом LI;
упорядоченный (нумерованный) список — создается элементом OL,
элементы списка создаются элементом LI;
список определений — создается элементом DL, элементы списка
создаются элементами DT и DD.
Отличие списков приведенных типов состоит в отображении их элемен-
тов. Элементы неупорядоченного и упорядоченного списков помечают-
ся маркерами (их тип зависит от значения атрибута type элемента UL или
OL), причем маркеры неупорядоченного списка представлены изображе-
ниями, а упорядоченного — буквами, арабскими или римскими цифра-
ми. Список определений содержит элементы двух типов: элементы тер-
минов (определяемые с помощью DT) и элементы определений (созда-
ются с помощью DD). Браузеры отображают эти элементы с разным
отступом и разным шрифтом.
56 Часть I. Основы создания Web-страниц и скриптов
Списки могут быть вложенными. Вкладывать друг в друга разрешается
любые типы списков.
Таблицы
Таблицы в HTML являются главным средством структурированного
представления данных. Долгое время таблицы оставались также основ-
ным средством разметки Web-страниц. Тема применения таблиц в
HTML очень обширна, поэтому для ее изучения я отсылаю вас к доку-
ментации. В табл. 2.1 приведен список элементов, использующихся для
формирования таблиц.
Таблица 2.1. Элементы для формирования таблиц
Элемент Назначение
TABLE Корневой элемент таблицы
THEAD Определяет группу строк верхнего заголовка
TFOOT Определяет группу строк нижнего заголовка
TBODY Определяет группу строк тела таблицы
COLGROUP Создает структурную группу столбцов таблицы
COL Определяет один или несколько столбцов в группе
TR Определяет строку таблицы
TH Определяет ячейку-заголовок
TD Определяет ячейку с данными
В настоящее время таблицы все еще очень широко используются как
основной элемент разметки Web-страниц, однако более корректным
механизмом считается использование таких элементов структуризации,
как DIV или SPAN в сочетании с интенсивным применением таблиц
стилей.
Изображения, объекты, апплеты
Как уже отмечалось, HTML предоставляет широкие возможности фор-
мирования "составных" документов, включающих содержимое различ-
ного типа. HTML позволяет разработчикам внедрять в документ изо-
бражения, апплеты, другие документы, компоненты ActiveX и т. д.
Ãëàâà 2. Язык HTML 57
В ранних версиях HTML (до 4.0) существовали три элемента для обес-
печения описанных выше возможностей: IMG — для внедрения графиче-
ских изображений, IFRAME — для включения в документ других доку-
ментов и APPLET — для использования в документе апплетов на языке
Java. Традиционно, наиболее используемыми в гипертекстовых доку-
ментах мультимедийными элементами являются изображения, поэтому
стоит привести здесь пример использования элемента IMG.
<IMG src="[Link]
Для этого элемента запрещен конечный тег. Источник (URI) изображе-
ния указывается значением атрибута src.
Приведенные элементы выполняют только конкретные задачи, поэтому
в HTML 4.0 был введен элемент OBJECT, обеспечивающий универсаль-
ное решение для включения в документ объектов различных (возможно,
неизвестных на данный момент) типов, поскольку дает возможность
предоставлять браузеру информацию, необходимую для отображения
содержимого. Этот элемент можно использовать вместо IMG, IFRAME,
APPLET. Например, изображение можно включить так:
<OBJECT data="[Link]
type="image/png">
Работа с изображениями и использование объекта IMG описывается
в главе 12.
Формы
Формы, находящиеся в документах HTML, служат, в основном, средст-
вом взаимодействия пользователя с удаленным сервером. Формы со-
держат элементы управления (кнопки, списки, поля ввода и т. д.), дан-
ные которых передаются серверу посредством HTTP-запроса. Кроме
того, формы могут обрабатываться клиентскими сценариями, а данные
элементов управления использоваться, например, для динамического
изменения содержимого документа.
Форма определяется при помощи элемента FORM. Атрибут action этого
элемента задает агента обработки данных формы, которым может яв-
ляться, например, скрипт на удаленном сервере, почтовая система поль-
зователя или клиентский сценарий. Если значение атрибута action оп-
ределяет URI серверного скрипта, то можно задать тип HTTP-запроса,
который будет использоваться для отправки данных формы при помощи
значения атрибута method (значением может быть "get" либо "post").
58 Часть I. Основы создания Web-страниц и скриптов
Элементы управления форм вводятся в основном при помощи элемента
INPUT. Тип элемента задается посредством атрибута type. Кроме того,
кнопки могут определяться при помощи элемента BUTTON и быть не-
скольких типов: обычные, кнопка отправки формы, кнопка очистки
формы.
Работа с формами средствами сценариев JavaScript подробно описана
в главе 11.
Фреймы
Фреймы (или кадры) позволяют создавать Web-страницы, состоящие из
нескольких независимых документов. Основой таких страниц является
специальная структура документа, в которой элемент BODY заменяется
элементом FRAMESET, определяющим расположение фреймов. Атрибуты
cols и rows элемента FRAMESET определяют количество и параметры
строк и столбцов набора фреймов, создаваемого элементом. Каждый
отдельный фрейм набора определяется при помощи элемента FRAME. Его
атрибут src задает URI документа, загружаемого во фрейм. Элементы
FRAMESET могут быть вложены друг в друга (являться наряду с FRAME
ячейкой набора фреймов), при этом могут создаваться фреймовые
структуры со сложной топологией.
Существует также элемент IFRAME (он уже упоминался ранее), позво-
ляющий создавать встроенные фреймы, внедряя один независимый до-
кумент в другой. Кроме того, его можно использовать для динамической
загрузки данных скриптом.
В общем случае фреймы крайне не рекомендуются к применению при
создании Web-сайтов (возникают проблемы с индексированием поиско-
выми системами, навигацией и т. д.), но если они используются, доку-
мент-контейнер набора фреймов должен включать элемент NOFRAMES
с альтернативным содержимым для браузеров, не поддерживающих
фреймы.
ГЛАВА 3
Каскадные
таблицы стилей CSS2
В предыдущей главе мы рассмотрели язык HTML — его основные осо-
бенности и средства, которые он предоставляет для создания гипертек-
стовых документов. Как уже отмечалось, HTML является языком струк-
турной разметки. Он создает "каркас", жесткую "основу", определяю-
щую отношения элементов в документе. Но, предоставляя широкие
средства форматирования, HTML не способен обеспечить однозначного
представления документа. Например, пользуясь только средствами
HTML, нельзя осуществить позиционирование структурных блоков с
точностью до пиксела. Также спецификация языка не определяет точно-
го размера шрифтов элементов (например, заголовков), значений отсту-
пов и т. д.
Таблицы стилей предоставляют разработчикам удобный механизм оп-
ределения параметров визуального представления документа, отделен-
ный от его структуры. Использование принципа разделения структуры и
представления гипертекстовых документов при создании современных
Web-ориентированных информационных систем позволяет снизить
стоимость разработки и дальнейшего обслуживания, а также делает их
более легко модифицируемыми и доступными. В этой главе мы рас-
смотрим каскадные таблицы стилей второго уровня (CSS2).
Ссылки на документацию
Целью данной главы является ознакомление читателя с основными по-
нятиями и терминами спецификации CSS2, принципами построения
каскадных таблиц стилей, способами их применения и предоставляе-
60 Часть I. Основы создания Web-страниц и скриптов
мыми ими возможностями. Материал главы не является справочным и
не описывает весь перечень свойств и их значений, используемых в
CSS2. Для изучения всех аспектов использования каскадных таблиц
стилей второго уровня следует обратиться к нормативной докумен-
тации.
Единственной нормативной версией спецификации CSS2 является ее
английская версия, находящаяся по адресу: [Link]
/REC-CSS2-19980512. Существуют переводы спецификации на другие
языки. Список ссылок на переводы можно найти на странице:
[Link] На прилагаемом
к книге компакт-диске в каталоге w3c\css находятся доступные на мо-
мент публикации книги версии спецификаций CSS. В каталоге
w3c\css\css2\rus размещен русский перевод спецификации CSS2, выпол-
ненный Александром Пирамидиным.
Основные сведения о CSS2
CSS2 представляет собой язык описания таблиц стилей, позволяющий
разрабатывать и применять таблицы стилей в структурированных доку-
ментах, созданных при помощи языков разметок, таких как XML
или HTML. Язык CSS2 основан на CSS1 ([Link]
/[Link]) — языке описания каскадных таблиц стилей
первого уровня, поэтому таблицы стилей, допустимые в CSS1, возмож-
ны и в CSS2.
Возможности CSS2
Использование каскадных таблиц стилей второго уровня предоставляет
разработчику гипертекстовых документов массу возможностей, дающих
полную свободу управления представлением документа. Наиболее зна-
чимые из них приведены далее.
Возможность создания таблиц стилей для различных устройств
представления. Это позволяет определять специфическую схему ото-
бражения документа для конкретных устройств (графических брау-
зеров, принтеров, устройств чтения по системе Брайля, мониторов
портативных компьютеров и т. д.).
Возможность применения к документу стилей из нескольких источ-
ников. К документу могут применяться стили, определяемые разра-
Ãëàâà 3. Каскадные таблицы стилей CSS2 61
ботчиком в виде встроенной информации о стиле, внедренных в до-
кумент либо подключаемых таблиц стилей. Также на документ могут
влиять стили, задаваемые браузером и пользователем. Области дей-
ствия стилевых правил могут пересекаться. В этом случае их исполь-
зование будет производиться в соответствии с правилами каскада
(см. разд. "Каскады таблиц стилей. Наследование" далее в этой главе).
Возможность гибкого определения стилевых правил.
Для элементов в зависимости от:
их положения в документе (см. разд. "Псевдоэлементы и
псевдоклассы" далее в этой главе);
связей между ними (см. разд. "Селекторы" далее в этой главе);
значений атрибутов (см. разд. "Селекторы атрибутов" далее
в этой главе);
состояния элементов (см. разд. "Псевдоэлементы и псевдо-
классы" далее в этой главе).
Для некоторых частей содержимого элементов (см. описание
псевдоэлементов first-letter и first-line в разд. "Псевдоэле-
менты и псевдоклассы" далее в этой главе).
Развитые возможности управления топологией представления доку-
мента, описываемые моделью визуального форматирования, а также
включающие средства форматирования таблиц.
Возможности управления форматированием текста (выравниванием,
отступами, интервалами, элементами декорирования).
Широкие возможности описания используемых в документе шриф-
тов с возможностью их загрузки из Web.
Возможность генерирования содержимого и поддержка автоматиче-
ской нумерации фрагментов документа.
Возможность тесной интеграции с пользовательским интерфей-
сом — использование цветов, курсоров, шрифтов, определяемых на-
стройками графической среды пользователя.
Возможность создания "звуковых" таблиц стилей, позволяющих
управлять параметрами звука и речи при воспроизведении документа
"говорящими" браузерами.
62 Часть I. Основы создания Web-страниц и скриптов
Использование CSS2
в HTML-документах
Спецификация языка HTML определяет несколько методов использова-
ния стилевых правил CSS2 в документах HTML. Наборы правил CSS2
могут представляться либо в форме встроенной информации о стиле
(для конкретного элемента), либо в виде таблиц стилей (непосредст-
венно внедренных в документ, либо внешних). Далее мы рассмотрим
способы использования стилей CSS2 в документах HTML.
Язык таблиц стилей по умолчанию
Язык описания таблиц стилей CSS не является единственным средством
подобного рода, поэтому корректной является установка для документа
языка таблиц стилей по умолчанию. С этой целью может быть исполь-
зован элемент META. Например, для установки по умолчанию языка CSS
в раздел заголовка документа (в элемент HEAD) необходимо поместить
следующее объявление:
<META http-equiv="Content-Style-Type" content="text/css">
Существует также возможность установки языка таблиц стилей по
умолчанию с помощью заголовка ответа сервера при загрузке документа
по протоколу HTTP. Например, следующий заголовок также установит
CSS языком таблиц стилей по умолчанию:
Content-Style-Type: text/css
Использование HTTP-заголовка может оказаться нежелательным, по-
скольку произведет необходимый эффект лишь при получении доку-
мента с сервера, в то время, как пользователь может сохранить доку-
мент на своей машине и впоследствии использовать его локальную
копию. Таким образом, предпочтительным является включение META-
объявления в документ.
Встроенная информация о стиле
Для применения некоторого набора стилей к отдельно взятому элементу
HTML может использоваться его атрибут style. Значением данного
атрибута является строка, содержащая корректное определение стиля на
Ãëàâà 3. Каскадные таблицы стилей CSS2 63
языке таблиц стилей, заданном для документа по умолчанию. Далее
приведен пример, иллюстрирующий применение данного метода.
<DIV style="text-align: justify; color: green;">
Текст текст текст</DIV>
Очевидно, что повсеместное использование подобного подхода крайне
нерационально, т. к. комбинации стилей для разных элементов могут
дублироваться. К тому же, данный метод не следует принципу разделе-
ния структуры и представления документа. Несравнимо большую гиб-
кость обеспечивают таблицы стилей.
С точки зрения спецификации HTML, документы, для которых не оп-
ределен язык таблиц стилей по умолчанию и которые используют ус-
тановку стилей через значение атрибута style, являются некоррект-
ными. Однако все браузеры в этом случае используют синтаксис CSS
для интерпретации данных стиля.
Таблицы стилей, внедренные в документ
Для внедрения таблиц стилей непосредственно в HTML-документ необ-
ходимо использовать элемент STYLE. Спецификация HTML предписыва-
ет располагать таблицы стилей в разделе заголовка, однако, как показы-
вает практика, браузеры интерпретируют содержимое элементов STYLE и
в случае, если они находятся в теле документа. Далее приведен листинг
HTML-документа, содержащего внедренную таблицу стилей.
<HTML>
<HEAD><TITLE>Документ, содержащий таблицу стилей</TITLE>
<STYLE type="text/css">
<!--
DIV { color: green; text-align: justify; }
-->
</STYLE>
</HEAD>
<BODY>
<DIV>Тело документа</DIV>
</BODY>
</HTML>
Для элемента STYLE обязательны начальный и конечный теги. Для него
также допустимо определение значений атрибутов: type, определяюще-
64 Часть I. Основы создания Web-страниц и скриптов
го язык таблиц стилей данного элемента, и media, задающего дескрип-
тор целевого устройства для информации о стиле.
CSS2 поддерживает синтаксис, позволяющий скрывать содержимое
элемента STYLE от устаревших браузеров, предотвращая тем самым ото-
бражение этого содержимого в виде текста. Как видно из приведенного
примера, для этого используются комментарии HTML.
Внешние таблицы стилей
Часто при разработке гипертекстовых документов оптимальным вариан-
том является применение внешних таблиц стилей. При этом в докумен-
тах не содержится стилевой информации, вместо этого в них распола-
гаются ссылки на источники, предоставляющие данные таблиц стилей.
По сравнению с вышеприведенными способами использования таблиц
CSS в HTML-документах применение внешних таблиц стилей обладает
следующими преимуществами:
возможность совместного использования таблиц в нескольких доку-
ментах;
возможность разработки и модификации таблиц отдельно от доку-
мента HTML;
возможность создания отдельных внешних таблиц стилей для раз-
личных типов устройств отображения, что позволяет пользователь-
ским агентам производить их выборочную загрузку, снижая тем са-
мым общее время загрузки документа.
Предпочитаемые, альтернативные
и постоянные таблицы стилей
При разработке HTML-документа разработчик может связать с ним не-
ограниченное число внешних таблиц стилей. Спецификация HTML вы-
деляет несколько типов таких таблиц, вводя понятия альтернативных,
предпочитаемых и постоянных таблиц стилей.
Альтернативные таблицы стилей разрабатываются обычно в целях соз-
дания нескольких вариантов представления документа для одного типа
устройств отображения. Пользовательские агенты могут предоставлять
пользователю механизм выбора одной из таких таблиц. Кроме того, раз-
работчик документа может указать предпочитаемую таблицу стилей
среди альтернативных. Альтернативные таблицы стилей могут группи-
роваться. Пользовательский агент может предоставить пользователю
Ãëàâà 3. Каскадные таблицы стилей CSS2 65
возможность выбрать одну из таких групп, и затем применить к доку-
менту правила всех таблиц группы.
Среди связанных с документом внешних таблиц можно указать посто-
янные таблицы, стилевые правила которых будут применяться всегда,
комбинируясь с правилами альтернативных таблиц стилей.
Связывание документа с внешними таблицами стилей
Ссылки на внешние таблицы стилей, используемые документом, опре-
деляются при помощи элемента LINK. Этот элемент может присутство-
вать только в разделе HEAD, однако его можно указывать неограниченное
число раз. С помощью значений атрибутов элемента LINK определяются
все параметры использования внешней таблицы. Атрибуты имеют зна-
чения, перечисленные в табл. 3.1.
Таблица 3.1. Атрибуты элемента LINK
Атрибут Значение
href URI источника данных таблицы стилей
type Язык таблицы стилей (в случае CSS это значение должно быть
"text/css")
title Имя таблицы стилей
rel Тип прямой связи; для таблиц стилей: "stylesheet" либо
"alternate stylesheet"
media Дескриптор целевого устройства таблицы стилей
Тип таблицы стилей (альтернативная, предпочитаемая, постоянная) оп-
ределяется комбинацией атрибутов rel и title следующим образом.
Элемент LINK ссылается на постоянную таблицу стилей, если его
атрибут rel имеет значение "stylesheet", а атрибут title не уста-
новлен.
Предпочитаемая таблица указывается элементом LINK, со значением
атрибута rel равным "stylesheet" и установленным значением ат-
рибута title.
Для того чтобы ссылаться на альтернативную таблицу стилей, необ-
ходимо установить значение атрибута rel элемента LINK равным
"alternate stylesheet", а также определить название таблицы
с помощью атрибута title.
66 Часть I. Основы создания Web-страниц и скриптов
Далее приведен листинг раздела заголовка документа, использующего
внешние таблицы стилей.
<HEAD>
<TITLE>Документ, ссылающийся на внешнюю таблицу стилей
</TITLE>
<LINK rel="stylesheet" media="screen" href="[Link]"
type="text/css">
<LINK rel="stylesheet" media="print" href="[Link]"
type="text/css">
</HEAD>
В приведенном примере документ связывается с двумя таблицами сти-
лей, одна из которых будет использоваться при отображении документа
браузером на дисплее (media="screen"), а другая — при печати доку-
мента (media="print").
Разработка таблиц стилей CSS2
В этом разделе мы кратко рассмотрим основные аспекты создания кас-
кадных таблиц стилей второго уровня (CSS2).
Основные синтаксические конструкции CSS
В терминах спецификации CSS2 таблица стилей представляет собой
список выражений. Все выражения делятся на два класса: правила "at" и
наборы правил. Выражения могут разделяться с помощью пробельных
символов.
Правила "at"
Запись правил данного типа состоит из символа "коммерческое at" (@) и
непосредственно следующего за ним идентификатора. Правила "at"
включают в себя все, что находится до первой, следующей после иден-
тификатора, точки с запятой (;) либо до окончания первого, следующего
за ним, структурного блока (см. далее). Вот пример записи правил "at":
@import "[Link]";
@media print { BODY { color: green; }}
Наборы правил, селекторы, объявления, свойства
Набор правил (или просто правило) состоит из селектора и следующего
за ним блока объявлений.
Ãëàâà 3. Каскадные таблицы стилей CSS2 67
Блок объявлений (далее — "{}-блок") представляет собой список разде-
ленных точкой с запятой (;) объявлений, заключенный в фигурные
скобки ({ и }). Блок объявлений может быть пустым.
Селектор (подробнее см. разд. "Селекторы" далее в этой главе) включа-
ет все, что находится до начала следующего за ним блока объявлений.
Объявление состоит из свойства и его значения, разделенных двоето-
чием (:).
Свойство представляет собой идентификатор. Спецификация CSS каж-
дого уровня определяет конкретные наборы свойств.
Синтаксис значений свойств определяется отдельно для каждого свой-
ства. Значение может состоять из идентификаторов и данных допусти-
мых типов. Возможные значения свойств указываются в спецификации.
Вот пример корректного правила CSS:
[Link], [Link]
{
color : blue;
font-weight : normal;
font-face : "Times New Roman";
}
В этом примере "[Link], [Link]" является селектором, color,
font-weight, font-face — свойства, а blue, normal, "Times New Ro-
man" — значения свойств.
Комментарии
Комментарии CSS1 и CSS2 начинаются последовательностью символов
/* и заканчиваются */ (таким образом, CSS предусматривает только
многострочные комментарии). Комментарий может включаться в любое
место таблицы стилей, его содержимое полностью игнорируется браузе-
ром.
CSS допускает использование в таблицах стилей, внедренных в доку-
мент с помощью элемента STYLE, комментариев HTML (<!-- и -->). Од-
нако они не влияют на структуру таблицы стилей и используются ис-
ключительно для сокрытия стилевых правил от устаревших пользова-
тельских агентов.
Селекторы
Как уже было сказано, таблицы стилей CSS представляют собой набор
правил. Стилистические правила содержат блоки объявлений, в которых
68 Часть I. Основы создания Web-страниц и скриптов
перечисляются свойства и соответствующие им значения. Когда таблица
стилей применяется к гипертекстовому документу, некоторые стилисти-
ческие правила сопоставляются определенным элементам в дереве до-
кумента. В процессе такого сопоставления значения свойств, указанные
в блоке объявлений правила присваиваются соответствующим свойст-
вам элемента. Для определения множества элементов документа, кото-
рым может быть сопоставлено конкретное правило, служит селектор
этого правила.
Типы селекторов
Фактически селекторы являются шаблонами, определяющими порядок
сопоставления стилистических правил. Язык CSS2 предоставляет син-
таксис описания селекторов, с помощью которого возможно создание
как простых, так и сложных шаблонов, задающих всевозможные аспек-
ты использования правил.
Простые селекторы
Следуя правилам синтаксиса описания селекторов, их можно отнести к
той или иной группе в зависимости от типов задаваемых ими критериев
сопоставления стилистических правил. В табл. 3.2 приведено описание
некоторых типов селекторов.
Таблица 3.2. Типы селекторов
Тип
Шаблон Пример Сопоставление
селектора
Универ- * * Любому элементу
сальный
Типа E DIV Элементу типа E
Классов [Link] [Link] Элементу типа E,
значение атрибута
class которого рав-
но name
Псевдо- E:name A:hover Элементу типа E
классов в определенном
состоянии
(см. разд. "Псевдо-
элементы и псевдо-
классы" далее
в этой главе)
Ãëàâà 3. Каскадные таблицы стилей CSS2 69
Таблица 3.2 (окончание)
Тип
Шаблон Пример Сопоставление
селектора
Атрибутов E[attr] A[href] Элементу типа E
с атрибутами, соот-
E[attr=val] A[id="some_id"]
ветствующими опре-
E[attr~=val] A[class~="c0"] деленным условиям
(см. разд. "Селек-
E[attr|=val] A[lang|="en"] торы атрибутов"
далее в этой главе)
ID-селектор E#val DIV#some_id Элементу типа E,
атрибут id которого
равен val
Селектор, представляющий собой селектор типа либо универсальный
селектор, непосредственно за которым в произвольном порядке могут
следовать селекторы атрибутов, псевдоклассов или ID-селекторов, на-
зывается простым селектором. Сопоставление простых селекторов
производится в случае, если сопоставимы все его составляющие. Все
приведенные далее селекторы являются простыми:
SPAN, [Link], H1#head, A[href]#link_0:hover
Селекторы потомков, дочерних и сестринских элементов
Понятие селектора не ограничено только простыми селекторами. В об-
щем смысле, это один простой селектор либо несколько простых селек-
торов, разделенных комбинаторами. Комбинаторами являются: символ
пробела (" "), символы > и +. Среди селекторов, составленных при по-
мощи комбинаторов, выделяют следующие типы:
селекторы потомков;
селекторы дочерних элементов;
селекторы сестринских элементов.
Селектор потомков состоит из двух и более простых селекторов, разде-
ленных пробелом. Такой селектор, вида "A B" сопоставим, если у эле-
мента B существует предок, элемент A. Например, следующее правило
будет применяться только к элементам SPAN, у которых есть предки эле-
менты DIV:
DIV SPAN { color : green; }
70 Часть I. Основы создания Web-страниц и скриптов
Селектор дочерних элементов состоит из двух и более селекторов, раз-
деленных символом >, и сопоставим, если элемент является дочерним по
отношению к некоторому другому элементу. Элемент A является дочер-
ним по отношению к B, если B непосредственно предшествует A в дереве
документа.
Селекторы сестринских элементов состоят из двух простых селекто-
ров, объединенных с помощью комбинатора +, т. е. имеют вид: A + B.
Такой селектор сопоставляется, если элементы A и B являются дочерни-
ми элементами одного и того же родительского элемента, и A непосред-
ственно предшествует B.
Селекторы атрибутов
В разделе о простых селекторах было указано четыре шаблона селекто-
ров атрибутов. Такие селекторы сопоставляются, если набор их атри-
бутов соответствует определенным условиям. Рассмотрим их более под-
робно.
Селекторы вида E[attr] сопоставляются тогда, когда у элемента имеет-
ся атрибут attr. Значение атрибута не учитывается.
Селекторы E[attr = val] сопоставимы, если значение атрибута attr
данного элемента в точности равно val.
Селекторы E[attr ~= val] сопоставляются, если значением атрибута
attr данного элемента является строка, содержащая набор фрагментов
текста, разделенных пробелами, один из которых в точности равен val.
Селекторы E[attr |= val] сопоставляются в тех случаях, если значени-
ем атрибута attr данного элемента является строка, содержащая набор
фрагментов текста, разделенных дефисом, начинающийся со слова val.
Псевдоэлементы и псевдоклассы
Селекторы типов, классов, атрибутов и ID-селекторы позволяют произ-
водить сопоставление стилистических правил элементам в зависимости
от таких характеристик элемента, как его положение в дереве докумен-
та, наличие и значения определенных атрибутов. В CSS существует ме-
ханизм, позволяющий адресовать элементы, находящиеся в определен-
ном состоянии, т. е. на основе информации, которую нельзя получить из
дерева документа. Эти возможности реализуют псевдоэлементы и псев-
доклассы.
Используя псевдоэлементы, разработчики могут задавать стиль содер-
жимому, которое не представлено как объект дерева документа, но мо-
жет быть выделено по некоторому признаку (например, первая строка,
Ãëàâà 3. Каскадные таблицы стилей CSS2 71
первая буква). В табл. 3.3 приведено назначение псевдоэлементов, опре-
деляемых CSS2.
Таблица 3.3. Псевдоэлементы
Псевдоэлемент Назначение
:first-line Определяет первую форматируемую строку абзаца
:first-letter Позволяет определить стиль первой буквы блока
текста
:before Генерирует содержимое перед содержимым ука-
занного элемента
:after Генерирует содержимое после содержимого ука-
занного элемента
Псевдоклассы позволяют адресовать элементы, содержащиеся в дереве
документа по характеристикам, которые не могут быть получены из де-
рева документа. Такие свойства могут появляться у элементов во время
работы пользователя с документом (например, фокус или активность
у ссылки). В табл. 3.4 представлен перечень псевдоклассов.
Таблица 3.4. Псевдоклассы
Псевдокласс Сопоставляемые элементы
:first-child Первые дочерние элементы других элементов
:link Непросмотренные пользователем ссылки
:visited Просмотренные пользователем ссылки
:hover Выделенный, но неактивизированный пользователем
элемент
:active Активизированный пользователем элемент
:focus Элементы, находящиеся в фокусе ввода
:lang(L) Элементам, использующим язык L (L — код языка, на-
пример, "ru")
Каскады таблиц стилей. Наследование
Как уже упоминалось, CSS позволяет использовать таблицы стилей из
нескольких источников. Это могут быть, например, несколько внешних
72 Часть I. Основы создания Web-страниц и скриптов
таблиц стилей, связанных с документом посредством элемента LINK в
сочетании с таблицами, внедренными в документ с помощью элемента
STYLE. В этом случае возникает каскад таблиц стилей. Вот пример тако-
го каскада:
<LINK rel="stylesheet" href="style_1.css" type="text/css">
<LINK rel="stylesheet" href="style_2.css" type="text/css">
<STYLE type="text/css">
A:hover { text-decoration: underline; }
</STYLE>
Стоит отметить, что таблицы стилей, составляющие каскад, могут быть
определены для различных устройств (элементы LINK и STYLE использу-
ются с атрибутом media). В этом случае при выводе документа на кон-
кретное устройство будут отобраны только необходимые таблицы.
В процессе отображения документа пользовательский агент должен оп-
ределить набор и значения свойств, применяемых к каждому элементу.
Сначала для этого используются правила каскада. В соответствии с ни-
ми, все сопоставляемые элементу правила ранжируются по приоритету,
источнику, специфичности, селектору и порядку следования (более под-
робную информацию о правилах каскада вы можете получить в соответ-
ствующем разделе спецификации CSS, в частности, на прилагаемом к
книге компакт-диске, этот раздел спецификации CSS2 находится в
w3c\css\css2\rus\[Link]), что позволяет определить необходимые
для применения правила.
Если правило для определения свойства не найдено, то может приме-
няться механизм наследования. Наследование значений допустимо не
для всех свойств (для конкретных свойств эта возможность указана в
спецификации). Если свойство может наследоваться, то поиск его зна-
чения производится среди значений этого же свойства, установленного
для элементов-предшественников вниз по иерархии дерева документа.
Такой механизм позволяет использовать наследуемые свойства для мно-
гих элементов документа, не определяя соответствующие правила,
обеспечивая тем самым возможность наиболее компактного описания
таблиц стилей.
Если необходимое значение не определено после применения механиз-
мов каскада и наследования, то свойству будет присвоено значение по
умолчанию (значения по умолчанию для каждого свойства также указы-
ваются в спецификации языка CSS2).
Ãëàâà 3. Каскадные таблицы стилей CSS2 73
Модель представления документа
в виде блоков
Для формализованного описания схемы взаимодействия элементов при
отображении документа, использующего каскадные таблицы стилей,
в CSS принята модель представления документа в виде блоков. Эта мо-
дель определяет понятие блоков, генерируемых для элементов дерева
документа. Блоки имеют прямоугольную форму, принципы их отобра-
жения описываются моделью визуального форматирования (см.
разд. "Модель визуального форматирования" далее в этой главе).
В соответствии с моделью представления документа в виде блоков, бло-
ки имеют информационную область, представляющую содержимое по-
родившего его элемента, а также области отступов, границ, полей. На
рис. 3.1 схематично изображен блок с указанием перечисленных выше
областей.
Область полей блока
Бордюр
Область отступов
Область содержимого
Рис. 3.1. Структура блока
При помощи таблиц стилей можно определять различные параметры
блока: ширину, высоту, параметры каждой из областей (например, тол-
щину бордюра блока). Можно также задавать параметры отдельных
сегментов каждой области (например, значение верхнего отступа или
правого поля блока). Подробную информацию о модели представления
документа в виде блоков можно найти в спецификации CSS2 на ком-
пакт-диске (w3c\css\css2\rus\[Link]). Там же описан перечень атрибу-
тов и их возможных значений для управления параметрами блоков.
Модель визуального форматирования
Модель представления документа в виде блоков вводит понятие блока,
определяет его структуру и свойства CSS для управления параметрами
74 Часть I. Основы создания Web-страниц и скриптов
блоков. Модель визуального форматирования описывает методы и схе-
мы размещения таких блоков при отображении документа пользова-
тельскими агентами на различных графических устройствах.
При помощи каскадных таблиц стилей CSS2 можно создавать блоки
нескольких типов (при этом каждый элемент дерева документа может не
порождать, либо порождать один или несколько блоков). Тип блока,
создаваемого элементом, устанавливается при помощи свойства display
и во многом влияет на его отображение в документе. Различают струк-
турные, строковые, компактные и инициальные блоки. При отображе-
нии документа все типы блоков форматируются как структурные или
строковые (компактные и инициальные преобразуются в структурные
или строковые в зависимости от определенных условий).
Размещение блоков при форматировании осуществляется исходя из:
типа и размера блока;
размера области просмотра;
схемы позиционирования блока, устанавливаемой свойством position;
взаимодействием элементов дерева документа.
В CSS2 предусмотрены три схемы позиционирования блоков: нормаль-
ный поток, перемещаемые объекты и абсолютное позиционирование,
предоставляющие разработчикам широкие возможности форматирова-
ния гипертекстовых документов без применения "жестких" элементов
разметки (таких как, например, прозрачные изображения). Подробную
информацию о модели визуального форматирования можно найти в
спецификации CSS2 на компакт-диске (w3c\css\css2\rus\[Link]).
Визуальные эффекты
Визуальные эффекты позволяют определять дополнительные аспекты
отображения блоков при форматировании документа. К ним относятся:
переполнение, отсечение и видимость.
Иногда при отображении документа некоторые структурные блоки мо-
гут оказаться переполненными, т. е. часть содержимого блоков окажется
за их пределами. Содержимое, выходящее за пределы блока, может усе-
каться. Усекается ли содержимое при переполнении блока, и если да, то
каким образом, определяется свойством overflow. При помощи этого
свойства в CSS2 возможно указать следующие способы отображения
содержимого при переполнении блока:
Ãëàâà 3. Каскадные таблицы стилей CSS2 75
содержимое не может усекаться и должно отображаться за предела-
ми блока (значение overflow равно visible);
содержимое должно усекаться без предоставления пользователю ме-
ханизма прокрутки для просмотра отсеченной части (значение
overflow равно hidden);
содержимое блока может усекаться, и блок должен всегда отобра-
жать механизм прокрутки (значение overflow равно scroll);
содержимое блока может усекаться, механизм прокрутки должен
быть предусмотрен, но может отображаться не всегда (значение
overflow равно auto).
В CSS2 возможно также задать область усечения, определяющую види-
мую часть содержимого блока. Область усечения может быть указана
при помощи свойства clip. На данный момент возможна единственная
форма области усечения — прямоугольная.
Еще одним свойством, определяющим визуальное отображение блока,
является свойство видимости блока (visibility). Его значение опреде-
ляет, будет ли осуществляться отображение блоков, создаваемых эле-
ментом, к которому применяется стилистическое правило. Допустимы-
ми значениями являются: visible (блок является видимым), hidden
(блок влияет на форматирование документа, но является невидимым),
collapse (используется в основном для управления отображением стро-
ками и столбцами таблиц, а для блоков остальных типов это значение
аналогично hidden).
Цвет
При помощи цветового оформления разработчики могут создавать удо-
бочитаемые и выразительные гипертекстовые документы. Таблицы сти-
лей позволяют гибко управлять цветами различных частей порождаемых
элементами блоков: текста, бордюра, фона. Это делается с помощью
следующих свойств:
color — задает цвет текстового содержимого элемента;
background-color — определяет цвет фона блока;
border-top-color, border-right-color, border-bottom-color, border-
left-color, border-color — определяют цвета верхнего, правого,
нижнего, левого сегментов и всего бордюра соответственно.
76 Часть I. Основы создания Web-страниц и скриптов
Значения всех перечисленных выше свойств могут являться либо клю-
чевыми словами, либо численными значениями цветов в модели RGB.
Используемые для обозначения цвета ключевые слова могут быть двух
типов: обозначающие предопределенные цвета, такие как black, blue,
purple (их перечень, в частности, определяется в спецификации
HTML 4.0), а также обозначающие цвета элементов пользовательского
интерфейса графической среды пользователя (например, ActiveBorder,
ButtonFace, ThreeDDarkShadow).
Числовое описание цвета также может быть представлено в двух фор-
мах: в шестнадцатеричном и в функциональном представлении.
В шестнадцатеричном представлении значение цвета записывается в
формате #rgb либо #RrGgBb, где R, r, G, g, B, b — шестнадцатеричные
цифры. Причем запись вида #rgb является сокращенной и всегда расши-
ряется до #rrggbb (путем дублирования цифр), т. е. значение #4BA будет
интерпретировано как #44BBAA.
В функциональном представлении цветовые значения выглядят как:
rgb(r, g, b), где r, g, b — целые числа от 0 до 255, либо как rgb(r%,
g%, b%), где r, g, b — процентные значения, числа от 0 до 100. Причем
в этих формах записи процентное значение 100% эквивалентно цело-
му 255.
Далее приведен пример правила CSS, использующий все формы обозна-
чения цвета:
DIV
{
color : #369;
background : #3399FF;
border-top-color : rgb(0, 0, 255);
border-bottom-color : rgb(0%, 50%, 100%);
}
В приложении addons\[Link] на компакт-диске представлены табли-
цы цветов и соответствующие им значения свойств CSS.
Шрифты
Безусловно, возможность управления шрифтами — одна из важнейших
при создании гипертекстовых документов. По сравнению с HTML, кас-
кадные таблицы стилей CSS2 предоставляют гораздо более широкий
Ãëàâà 3. Каскадные таблицы стилей CSS2 77
набор средств определения шрифтов документа. Следующие параметры
используются для описания шрифтов в CSS2.
Гарнитура (свойство font-family). Это группа шрифтов, обладаю-
щих сходным начертанием и разработанных для совместного ис-
пользования. Шрифты одной гарнитуры могут различаться стилем,
весом и другими параметрами.
Стиль (свойство font-style). Определяет тип начертания шрифта
(обычный, курсивный наклонный).
Вариант (свойство font-variant). Указывает, метод отображения
строчных символов — с использованием обычных глифов или гли-
фов капители.
Вес (свойство font-weight). Определяет, насколько глифы, исполь-
зуемые для отображения текста, темнее или светлее глифов других
шрифтов той же гарнитуры.
Масштабирование (свойство font-stretch). Определяет величину
сжатия или растяжения глифов шрифта, по сравнению с глифами
других шрифтов той же гарнитуры.
Размер (свойство font-size). Размер шрифта определяет величину
шрифта от одной базовой линии до другой при наборе без шпонов.
Тема применения шрифтов в CSS2 настолько обширна, что для ее рас-
крытия не хватит объема всей этой главы, поэтому настоятельно реко-
мендуется ознакомиться с соответствующим разделом спецификации
(представленным также на компакт-диске: w3c\css\css2\rus\[Link]).
Текст
Как же упоминалось в разд. "Возможности CSS2" ранее в этой главе,
при помощи свойств каскадных таблиц стилей CSS разработчик может
управлять такими параметрами форматирования текста, как, например,
выравнивание, отступы, межсимвольные интервалы, элементы декори-
рования. В табл. 3.5 приведен перечень и описание свойств, используе-
мых для управления параметрами форматирования текста.
Таблица 3.5. Свойства, управляющие форматированием текста
Свойство Описание
text-indent Определяет отступ первой строки текста в блоке
text-align Определяет способ выравнивания содержимого блока
78 Часть I. Основы создания Web-страниц и скриптов
Таблица 3.5 (окончание)
Свойство Описание
text-decoration Задает элементы декорирования текста, такие как:
подчеркивание, надстрочное подчеркивание, пере-
черкивание, мигание
letter-spacing Задает расстояние между буквами в тексте
word-spacing Задает расстояние между словами в тексте
text-transform Управляет преобразованием регистра букв текстового
содержимого
white-space Определяет способ обработки последовательностей
пробельных символов в текстовом содержимом эле-
мента. С помощью этого свойства можно достигать
эффекта, аналогичного использованию элементов
PRE и NOBR в разметке
Курсоры
Последнее, о чем хотелось бы упомянуть в этой главе, — возможность
определения с помощью CSS вида курсора мыши, отображаемого над
конкретным блоком во время работы пользователя с документом. Вид
курсора задается с помощью свойства cursor. Его значением может яв-
ляться ключевое слово, описывающее тип стандартного курсора, опре-
деляемого настройками графической среды пользователя, либо список
URI, задающий местоположение ресурсов, из которых курсор может
быть загружен. После списка URI должен быть указан стандартный кур-
сор, который будет отображен, если все курсоры, указываемые с по-
мощью URI, не смогут быть загружены. Вот пример использования
свойства cursor.
[Link] { cursor : crosshair }
[Link] { cursor : url("cursor_1.cur"),
url("cursor_1.cur"), default; }
Приложение addons\[Link] позволяет видеть форму всех стандарт-
ных курсоров.
ГЛАВА 4
Объектные модели браузера
и документа
В двух предыдущих главах нами были рассмотрены средства создания
статических гипертекстовых документов: язык структурной разметки
HTML и язык описания каскадных таблиц стилей CSS2, позволяющий
определять представление документа на логическом уровне. Однако
наша задача — создание динамических Web-страниц, структуру и пред-
ставление которых можно было бы изменять программно. В главе 1, где
рассматривался язык JavaScript, не упоминались какие-либо средства,
позволяющие управлять браузером или загруженными в него докумен-
тами. В той же главе говорилось, что JavaScript является кроссплатфор-
менным средством. А это значит, что при работе JavaScript-сценария в
конкретной исполняющей среде средства манипулирования целевой
платформой предоставляются сценарию самой средой. Web-браузеры
являются одной из платформ, реализующей поддержку JavaScript. И они
также предоставляют скрипту соответствующие средства управления.
Эти средства представлены в виде методов и свойств иерархии объек-
тов, расширяющих набор объектов ядра языка.
Понятия объектных моделей браузера,
документа
Итак, цель поддержки браузерами скриптовых языков — обеспечить
возможность изменения состояния браузера или документа, загруженно-
го в браузер с помощью клиентских сценариев. Для того чтобы скрипт
мог взаимодействовать с браузером, ему должен быть предоставлен не-
кий программный интерфейс. Наиболее удобно такой интерфейс реали-
80 Часть I. Основы создания Web-страниц и скриптов
зуется в виде совокупности объектов, экспортируемых ядром исполне-
ния сценариев в глобальную область имен скрипта. Традиционно объек-
ты этого набора объединяются в иерархию с древовидной структурой.
В этой иерархии одни объекты являются свойствами других. Структура
такой иерархии называется объектной моделью браузера.
Объектные модели различных браузеров, конечно же, различаются. Од-
нако в процессе их эволюции образовалась некоторая устоявшаяся
структура. Она представлена на рис. 4.1.
window
document frames history location navigator
Иерархия объектов в модели документа (DOM)
Рис. 4.1. Схема объектной модели браузера
Как видно из рисунка, в качестве корня иерархии объектной модели вы-
ступает объект window. С помощью некоторых свойств и методов этого
объекта можно манипулировать окном документа. Среди свойств window
есть такие полезные объекты, как, например, navigator, frames,
history. Особого внимания среди них заслуживает объект document.
Объект document является корнем объектной иерархии дерева докумен-
та. Эта иерархия формируется во время загрузки документа и может
быть изменена программно клиентским скриптом. Структура иерархии
фактически повторяет структуру исходного документа — она содержит
объекты, соответствующие элементам в HTML-разметке, атрибуты эле-
ментов и их значения, атрибуты, соответствующие свойствам CSS.
Структура иерархии объектов дерева документа носит название объект-
ной модели документа, или DOM (Document Object Model).
Введение в объектную модель
документа (DOM)
Для того чтобы начать практическое использование объектной модели
документа при программировании JavaScript-сценариев, нам необходи-
Ãëàâà 4. Объектные модели браузера и документа 81
мо рассмотреть несколько аспектов, касающихся основ ее применения, а
именно: конкретизировать понятие объектной модели документа, обо-
значить связь объектной иерархии со структурой гипертекстового доку-
мента и рассмотреть саму структуру объектной иерархии.
W3C DOM и DHTML Object Model
Эволюция объектной модели на ранних стадиях развития браузеров но-
сила несколько спонтанный характер. Производители браузеров один за
другим заявили в своих продуктах поддержку динамического HTML или
DHTML (Dynamic HTML), что означало применение скриптов и наличие
объектной модели. Однако объектная модель браузера (и основная ее
часть — объектная модель документа) развивались исключительно в
соответствии с нуждами и понятиями конкретного производителя. Это,
конечно же, не могло не привести к проблеме эффективного написания
кроссбраузерного кода.
Исходя их того, что объектная модель представляет собой программный
интерфейс, реализация в скрипте конкретных функций обуславливается
наличием тех или иных объектов, коллекций, методов, свойств. Но по-
скольку DHTML-ориентированные объектные модели разных браузеров
весьма различаются, то разработчикам клиентских сценариев приходит-
ся писать дополнительный код, разрабатывая "уровень абстракции" —
собственный унифицированный программный интерфейс, взаимодейст-
вующий с объектной моделью способами, зависящими от особенностей
конкретного браузера.
В результате консорциумом W3C была представлена стандартизирован-
ная объектная модель, рассчитанная на использование с HTML- и XML-
документами. Главным преимуществом W3C DOM (или просто DOM)
перед объектными моделями DHTML различных браузеров является
именно наличие четкого стандарта и спецификаций — того, чего долго
не хватало Web-разработчикам. Существенны также структурные и
идеологические отличия этих моделей. В то время как DHTML предпо-
лагает использование специфических коллекций объектов и методов для
манипулирования данными документа, DOM определяет стандартный
API (Application Programming Interface, интерфейс прикладного про-
граммирования), ориентированный на работу с деревом документа в
явном виде: создание, изменение, копирование узлов дерева, установка
узлам атрибутов и т. д.
Консорциумом W3C были опубликованы спецификации объектных мо-
делей нескольких уровней.
82 Часть I. Основы создания Web-страниц и скриптов
DOM уровня 0. Фактически это не формальная спецификация, а тер-
мин, указывающий на объектные модели документов устаревших
браузеров Microsoft Internet Explorer 3.0 и Netscape Navigator 3.0.
DOM уровня 1. Данная объектная модель предоставляет широкий
набор средств, позволяющих получить доступ к каждому элементу
дерева документа и легко манипулировать структурой документа и
текстовым содержимым элементов.
DOM уровня 2. Во многом объединяя возможности DOM уровней 1
и 2, данная модель добавляет требования поддержки различных до-
полнительных средств, таких, например, как: расширенный механизм
обработки событий, механизм доступа к таблицам стилей, объекты
итерации по дереву документа, работа с диапазонами выделения.
DOM уровня 3. Эта версия вводит новые интерфейсы, типы данных,
а также один дополнительный объект. Также описываются измене-
ния некоторых методов интерфейсов, определенных в спецификации
DOM уровня 2.
К сожалению, в настоящее время можно говорить о полной поддержке
браузерами DOM первого и, лишь частично, второго уровня. При про-
граммировании клиентских сценариев имеется возможность определить
параметры совместимости браузера (как это сделать, см. в главе 5), что
позволяет реализовать выборочное использование необходимых средств
и избежать ошибок времени исполнения скрипта.
Главная страница раздела, посвященного DOM, на сайте консорциума
W3C находится по адресу [Link] Ссылки на все
существующие спецификации DOM можно найти на странице
[Link] Также, в каталоге w3c\dom на прила-
гаемом к книге компакт-диске содержатся доступные на момент опуб-
ликования книги версии спецификаций DOM.
Объекты модели документа,
их связь с HTML
Как упоминалось в одном из предыдущих разделов, документ, загру-
женный в браузер, доступен JavaScript-сценарию в виде иерархии объек-
тов, структура которой тесно связана со структурой исходного докумен-
та. Связь объектной иерархии и документа HTML удобно рассматривать
в терминах спецификации объектной модели документа W3C DOM. Для
Ãëàâà 4. Объектные модели браузера и документа 83
рассмотрения этой связи необходимо сначала пояснить, как модель
W3C DOM соотносится с иерархией объектов, представляющей доку-
мент.
Итак, уже говорилось о том, что W3C DOM определяет API для доступа
к HTML- и XML-документам. Важным моментом является то, что спе-
цификация DOM не требует от приложений какой-то конкретной струк-
туры организации данных документа. Вместо этого она определяет API
как набор интерфейсов. В данном контексте интерфейс представляет
собой абстракцию, описывающую схему доступа и взаимодействия со
специфическим для приложения внутренним представлением докумен-
та. Таким образом, W3C DOM на логическом уровне определяет прин-
ципы управления структурой и данными документа.
Работа с реализациями интерфейсов в широком смысле и, в частности,
с реализациями интерфейсов DOM-модели в различных языках про-
граммирования осуществляется по-разному. Применительно к JavaScript,
ядро исполнения сценариев DOM-совместимых браузеров отображает
атрибуты и методы интерфейсов объектной модели в соответствую-
щие свойства и методы объектов иерархии дерева документа (хотя для
других языков, например, атрибуты могут представляться парой функ-
ций getValue/setValue).
Заглянув в спецификацию DOM любого уровня, можно видеть, что она
состоит из нескольких разделов. Всегда присутствует раздел "DOM
Core", описывающий ядро объектной модели — общий набор интерфей-
сов, программного манипулирования содержимым и структурой доку-
мента. В других разделах описываются расширения DOM, позволяющие
решать определенный круг задач, такие, например, как: "DOM CSS"
(интерфейсы программного управления правилами таблиц стилей CSS),
"DOM Events" (обработка событий модели документа) и т. д. Одно из
таких расширений — "DOM HTML" представляет для нас особый инте-
рес, т. к. определяет набор интерфейсов DOM, специфичных для доку-
ментов HTML.
Раздел спецификации объектной модели документа HTML (Document
Object Model HTML) вводит несколько интерфейсов общего назначения
(таких как HTMLCollection, HTMLDocument) и большое количество интер-
фейсов, соответствующих элементам языка HTML. Один интерфейс —
HTMLElement следует упомянуть особо. От него наследуются все осталь-
ные интерфейсы объектов, соответствующих HTML-элементам. Он оп-
ределяет следующие атрибуты: id, title, lang, dir, className. Некото-
рые объекты дерева документа реализуют интерфейс HTMLElement непо-
84 Часть I. Основы создания Web-страниц и скриптов
средственно, не определяя дополнительных свойств и методов. Таким
объектам соответствуют следующие элементы HTML:
abbr big dfn noframes span tt
acronym center dt noscript strike u
address cite em s strong var
b code i samp sub
bdo dd kbd small sup
Всем остальным HTML-элементам в дереве документа соответствуют
объекты, предоставляющие специфические интерфейсы. Так, например,
элементу BODY соответствует интерфейс HTMLBodyElement, а элементу
A — HTMLAnchorElement. Таким образом, используя спецификацию CSS1
(на компакт-диске находится в каталоге w3c\dom\dom1), можно легко
получать информацию о свойствах и методах конкретных типов объек-
тов дерева документа, заведомо поддерживаемых современными брау-
зерами.
На компакт-диске (в файле addons\[Link]) находится таблица
соответствия элементов HTML интерфейсам DOM с указанием списков
атрибутов и методов для каждого типа объекта.
Иерархия объектов модели документа
браузера
Важным преимуществом объектной модели W3C DOM, обеспечиваю-
щим простоту ее применения является представление документа в виде
дерева. Древовидное представление объектной иерархии позволяет ис-
пользовать унифицированные методы доступа к информации документа,
к тому же, оно наибольшим образом соответствует структуре HTML- и
XML-документов. Рассмотрим простой HTML-документ следующего
содержания:
<html>
<head><title>Заголовок</title></head>
<body>
<a href="[Link]
<p>текст <i>курсив <u>подчеркнутый курсив</u> курсив</i>
текст</p>
</body>
</html>
Ãëàâà 4. Объектные модели браузера и документа 85
HTML
HEAD
TITLE
"Заголовок"
BODY
"codegu"
"ru"
".ru"
""
"текст"
"курсив"
"подчеркнутый курсив"
"курсив"
"текст"
Рис. 4.2. Иерархия объектов HTML-документа
86 Часть I. Основы создания Web-страниц и скриптов
При загрузке этого документа браузер может представить его в виде
иерархии объектов со структурой, представленной на рис. 4.2.
Блоками с темным фоном на рисунке изображены объекты, соответст-
вующие элементам HTML. Все остальные узлы дерева являются объек-
тами, представляющими текстовые фрагменты документа. Стоит заме-
тить, что это лишь один из возможных вариантов иерархии объектов
модели документа. Структура иерархии может различаться при обра-
ботке документа различными браузерами. Например, по-разному могут
представляться последовательности пробельных символов между
HTML-элементами в разметке.
Способы доступа
к объектам модели документа
В данном разделе мы рассмотрим ключевой аспект создания динамиче-
ских Web-страниц — способы доступа к объектам модели гипертексто-
вого документа. Получив возможность выборочно ссылаться на эти объ-
екты из скрипта, мы сможем изменять содержимое и структуру доку-
мента с помощью наших сценариев. Существует несколько стандартных
(т. е. описываемых спецификациями W3C DOM и поддерживаемых
всеми современными браузерами) способов доступа к объектам мо-
дели документа. Рассмотрение этих способов будет проводиться на
примере гипертекстового документа следующего содержания (см. файл
examples\04\ex_4_01.htm на компакт-диске):
<HTML><BODY>
<FORM id="f0"><BUTTON type="button"
name="el0">Button</BUTTON></FORM>
<A href="#">Ссылка 0</A>
<A href="#" name="el0">Ссылка 1</A>
<SCRIPT src="ex_4_01.js"></SCRIPT>
</BODY></HTML>
Фрагменты кода, приведенные далее, содержатся в файле внешнего
скрипта ex_4_01.js, подключаемого к документу. Результатом работы
будет являться список имен HTML-элементов, соответствующих объек-
там дерева документа, ссылки на которые будут получены описанными
далее способами. Все результаты сохраняются в строку str (см. код
примера), а затем выводятся в тот же документ. Имя элемента определя-
ется из значения атрибута nodeName объекта.
Ãëàâà 4. Объектные модели браузера и документа 87
Доступ через методы объекта document
Одним из наиболее простых в использовании способов получения ссы-
лок на объекты дерева документа является использование нескольких
методов объекта document.
Метод getElementById
Этот метод возвращает ссылку на объект дерева документа, соответст-
вующего элементу HTML, для которого установлено определенное зна-
чение атрибута id. В листинге документа, приведенном ранее, атрибут
id установлен для элемента form. Таким образом, зная id, легко полу-
чим ссылку на соответствующий объект, а затем определим имя HTML-
элемента:
obj = [Link]("f0");
str += [Link];
При использовании необходимо учитывать семантику работы данного
метода — он всегда возвращает ссылку только на один объект. Если в
документе содержится несколько элементов, атрибуты id которых име-
ют одинаковые значения, то getElementById вернет ссылку либо на пер-
вый, либо на последний из них. Такое поведение стандартно, и опреде-
лено для того, чтобы побудить авторов создавать корректные документы
HTML, в которых значения id различных элементов уникальны.
Метод getElementsByName
Данный метод производит поиск элементов в дереве документа с опре-
деленным значением атрибута name. Он принимает в качестве параметра
строку, содержащую необходимое значение. В отличие от метода
getElementById, метод getElementsByName может возвращать коллекцию
(список) объектов, т. к. в документе может содержаться несколько эле-
ментов с одинаковыми значениями атрибута name (например, элементы
input, относящиеся к разным формам). Для нахождения количества
объектов списка можно использовать свойство length коллекции, а для
перечисления всех найденных объектов — цикл с параметром и синтак-
сис доступа к элементам массива:
obj = [Link]("el0");
for(var i = 0; i < [Link]; i++)
str += obj[i].nodeName + "<br>";
Метод getElementsByTagName
Также, как и предыдущий метод, getElementsByTagName возвращает
список объектов. Однако поиск производится по имени тега элемента
88 Часть I. Основы создания Web-страниц и скриптов
(он передается методу в качестве строкового параметра). Для просмотра
объектов списка также воспользуемся циклом:
obj = [Link]("A");
for(var i = 0; i < [Link]; i++)
str += obj[i].nodeName + "<br>";
В этом примере находятся все объекты модели документа, соответст-
вующие элементам A.
Доступ через предопределенные коллекции объектов
Начиная с самой первой версии (DOM уровня 0), объектная модель до-
кумента определяет несколько коллекций, являющихся свойствами объ-
екта document, которые содержат ссылки на объекты часто используе-
мых HTML-элементов. В табл. 4.1 приведен список идентификаторов и
описание этих коллекций.
Таблица 4.1. Коллекции объекта document
Коллекция Описание
anchors Содержит ссылки на объекты, соответствующие якорям
в документе (элементам вида <A name="...">...</A>)
applets Содержит ссылки на объекты, соответствующие элемен-
там Java-апплетов
forms Коллекция объектов форм (элементы FORM)
images Коллекция объектов, соответствующих элементам изо-
бражений (IMG)
links Коллекция объектов ссылок (элементы вида <A href=
"..." >...</A>)
Для доступа к элементам коллекций используется все тот же синтаксис
обращения к элементам массива — по числовому (порядковый номер
элемента в документе), либо строковому (значение атрибута id или
name) индексу. Далее приведен пример перечисления ссылок в докумен-
те с помощью коллекции links.
for(var i = 0; i < [Link]; i++)
str += [Link][i].nodeName + "<br>";
Ãëàâà 4. Объектные модели браузера и документа 89
Доступ путем прохождения дерева документа
Каждый объект, представляющий узел дерева документа в модели DOM,
имеет набор свойств, позволяющих получить информацию об этом узле,
а также ссылки на предыдущий, последующий и дочерние объекты от-
носительно данного узла. Наличие этих свойств позволяет рекурсивно
просматривать иерархию объектов дерева документа. И благодаря тому,
что DOM — стандартизированная модель, можно полагаться на одина-
ковое поведение во всех DOM-совместимых браузерах. В табл. 4.2 при-
ведено описание соответствующих свойств.
Таблица 4.2. Свойства DOM-объектов
для перемещения по дереву документа
Свойство Описание
nodeName Имя узла. Если узел представляет элемент, то
значение атрибута содержит имя элемента
nodeType Содержит числовое значение, обозначающее тип
узла (типы узлов указаны в спецификации W3C
DOM в определении интерфейса Node; всего опи-
сано 12 типов узлов, но здесь приведены только
наиболее значимые):
1. Элемент.
2. Атрибут.
3. Текст.
8. Комментарий.
9. Документ (объект document).
10. Тип документа. Данное значение атрибут
nodeType будет иметь у элемента определения
типа документа <!DOCTYPE...>
nodeValue "Значение" узла. Для текстовых узлов содержит
строку текста
childNodes Объект-коллекция ссылок на дочерние узлы (если
такие имеются)
parentNode Ссылка на объект родительского узла (если такой
существует)
firstChild Ссылка на первый дочерний узел (если такой
существует)
lastChild Ссылка на последний дочерний узел (если такой
существует)
90 Часть I. Основы создания Web-страниц и скриптов
Таблица 4.2 (окончание)
Свойство Описание
previousSibling Ссылка на предыдущий соседний узел (если та-
кой существует)
nextSibling Ссылка на следующий соседний узел (если такой
существует)
Для того чтобы начать "перемещаться" по иерархии объектов дерева
документа, необходимо иметь ссылку хотя бы на один из этих объектов.
Если вам известна структура документа, с которым работает скрипт, то
удобным может быть получение такой ссылки путем использования ме-
тода getElementById или getElementsByName. Но, даже если это не так,
существует несколько всегда доступных "точек входа" в дерево объек-
тов модели документа:
объект document;
свойство [Link] — ссылка на объект, представ-
ляющий корневой элемент HTML-документа (обычно это элемент
HTML);
свойство [Link] — ссылка на объект, представляющий эле-
мент тела HTML-документа (элемент BODY).
Далее приведен пример рекурсивного обхода дерева документа с выво-
дом в строку информации обо всех объектах дерева. В примере исполь-
зуется коллекция childNodes для перебора всех дочерних узлов объекта,
ссылка на который передается в функцию ListNodes первым парамет-
ром. Обход дерева начинается с объекта document.
function ListNodes(refNode, nLevel)
{
var strResult = "";
for(var i = 0; i < nLevel; i++)
strResult += " ";
strResult += [Link] +
" (nodeType=" + [Link] +
"; nodeValue=" + [Link] + ")<br>";
if([Link]())
{
Ãëàâà 4. Объектные модели браузера и документа 91
for(i = 0; i < [Link]; i++)
strResult += ListNodes([Link][i],
nLevel + 1);
}
return strResult;
};
str += ListNodes(document, 0);
Этот код также находится в файле examples\04\ex_4_01.js на компакт-
диске.
Основы использования
объектной модели
Далее мы рассмотрим несколько важных объектов, свойств, методов,
с которыми часто будем работать при решении типовых задач создания
скриптов.
Объект window.
Методы ввода/вывода информации
Являясь корнем объектной иерархии и предоставляя доступ к свойствам
окна браузера или фрейма, объект window, безусловно, заслуживает осо-
бого внимания и изучения. Подробное описание этого объекта, его
свойств, методов, а также способов создания, перемещения, изменения
параметров окон браузера будет приведено в главе 10. Здесь же мы рас-
смотрим всего три метода объекта window, обеспечивающие возмож-
ность ввода и вывода информации посредством диалоговых окон. Эти
методы хороши своей простотой и далее будут часто использоваться в
простых примерах.
Метод alert
Вызов этого метода приводит к отображению модального диалогового
окна, содержащего строку сообщения, передаваемую в качестве пара-
метра. Обычно такое окно является стандартным окном сообщений гра-
фической среды и содержит также кнопку OK и пиктограмму, символи-
зирующую знак предупреждения.
92 Часть I. Основы создания Web-страниц и скриптов
Вот примеры использования метода alert:
[Link]("Сообщение выведено методом alert");
alert("Еще одно сообщение");
Данный метод удобно использовать для вывода диагностической ин-
формации при отладке небольших скриптов.
Метод confirm
Данный метод служит для вывода диалогового окна подтверждения.
Оно обычно содержит кнопки OK и Cancel (возможны также локализо-
ванные версии надписей, если браузер использует механизм графиче-
ской среды для вывода окон сообщений), а также пиктограмму, свойст-
венную окнам запроса.
Аналогично методу alert, confirm принимает один параметр — строку,
которая будет выведена в диалоговом окне в качестве сообщения. Одна-
ко, в отличие от alert, этот метод возвращает логическое значение, ука-
зывающее на действие, совершенное пользователем. Если окно было
закрыто вследствие нажатия кнопки OK, будет возвращено значение
true, иначе — false. Далее приведен пример вызова метода confirm:
var bResult = [Link]("Вы подтверждаете
выполнение операции?");
Данный метод часто используют для подтверждения каких-либо дейст-
вий, производимых пользователем перед посылкой запроса для обра-
ботки сервером.
Метод prompt
Этот метод объекта window позволяет запросить у пользователя ввод
текстовых данных небольшого объема. Вызов метода приводит к появ-
лению модального диалогового окна, содержащего элемент управления
для ввода текста. Такие окна не являются стандартными для различных
графических сред, и их вид зависит от конкретного браузера. Обычно
окно запроса данных включает кнопки подтверждения ввода и отмены
(например, OK и Cancel), также оно может содержать пиктограмму,
аналогичную пиктограмме окна, отображаемого вследствие вызова ме-
тода confirm.
Метод prompt принимает два строковых параметра. Первый — строка,
которая будет отображена в диалоговом окне (обычно она используется
Ãëàâà 4. Объектные модели браузера и документа 93
для пояснения значения вводимой информации). Второй — начальное
значение, которое будет выведено в поле ввода текста. Тип возвращае-
мого значения метода зависит от действий, произведенных пользовате-
лем: если пользователь закрыл окно либо нажал кнопку Cancel, то воз-
вращается null, если же была нажата кнопка OK, то возвращаемое зна-
чение — строка, содержащая текст, введенный пользователем. Далее
приведен пример вызова метода prompt:
var strResult = prompt("Какое Ваше любимое время года?",
"весна");
При помощи данного метода удобно запрашивать у пользователя ин-
формацию в виде коротких произвольных значений (числа, имена, на-
звания и т. д.) в тех случаях, когда ее необходимо немедленно обрабо-
тать (например, осуществить проверку на соответствие диапазону зна-
чений, либо определенным образом отформатировать и тут же вставить
в документ).
Объекты document и body
Объект document интересен тем, что является корнем объектной иерар-
хии дерева документа. В спецификации W3C DOM уровня 1 описан ин-
терфейс HTMLDocument (см. файл w3c\dom\dom1\[Link] на
компакт-диске), определяющий минимальный набор свойств, методов и
коллекций этого объекта, доступных скрипту при работе под управлени-
ем DOM-совместимых браузеров.
Коллекции, являющиеся свойствами объекта document (images, applets,
links, forms, anchors), были описаны в предыдущем разделе, поэтому
перечислим здесь только его свойства, не являющиеся коллекциями, и
методы. Перечень и описание свойств (согласно определению интер-
фейса HTMLDocument CSS уровня 1) приведен в табл. 4.3.
Таблица 4.3. Свойства объекта document
Свойство Описание
title Заголовок документа. Идентично содержимому элемента
TITLE
cookie Содержимое cookie-набора, связанного с документом
referrer URI страницы, с которой был совершен переход в данный
документ
94 Часть I. Основы создания Web-страниц и скриптов
Таблица 4.3 (окончание)
Свойство Описание
domain Изначально содержит имя домена, из которого загружен до-
кумент (или null, если домен не определен). Значение свой-
ства может быть задано для обеспечения взаимодействия
скриптов в документах, загруженных в разные фреймы с раз-
ных поддоменов одного домена
URL Содержит полный URI документа
body Объект HTML-элемента тела документа (BODY или FRAMESET)
Применение большинства из этих свойств будет подробно обсуждаться
в следующих главах, поэтому здесь останавливаться на этом не будем.
Также объект document имеет несколько методов. Два из них
(getElementById, getElementsByName) были описаны ранее. Описание
остальных приведено в табл. 4.4.
Таблица 4.4. Методы объекта document
Метод Описание
open Открывает поток данных документа для записи. Если доку-
мент уже имеет какое-либо содержимое — оно очищается
close Закрывает поток данных, открытый методом open, и вызыва-
ет перерисовку
write Записывает строку текста в поток данных, открытый методом
open. Этот текст интерпретируется как HTML-код и обраба-
тывается разборщиком HTML. На его основе генерируется
часть объектной структуры модели документа
writeln Полностью аналогичен write, но текст дополняется симво-
лом конца строки
Эти методы интересны для нас тем, что предоставляют простой и есте-
ственный способ генерирования фрагментов (или даже всего) содержи-
мого документа скриптом. С помощью них мы можем, например, созда-
вать некоторые части страницы "на лету", во время загрузки документа
в браузер, или генерировать их в зависимости от определенных условий.
Данными методами удобно пользоваться для вывода больших объемов
диагностической информации при отладке скриптов. Для иллюстриро-
вания возможностей использования методов write и writeln далее при-
Ãëàâà 4. Объектные модели браузера и документа 95
веден фрагмент JavaScript-кода (его также можно найти в файле
examples\04\ex_4_02.js на компакт-диске, а увидеть результат его работы
можно, открыв в браузере файл примера examples\04\ex_4_02.htm), ге-
нерирующий достаточно большую (32 строки на 32 столбца) таблицу
с изменяющимся цветом фона ячеек, эмулирующую область с градиент-
ной заливкой.
var d = document;
[Link]("<table cellpadding=0 cellspacing=0 border=0" +
"style='font-size: 0px;'>");
for(var y = 0; y < 0xFF; y += 0x08)
{
[Link]("<tr>");
for(var x = 0; x < 0xFF; x += 0x08)
{
var clr = 0xFF - ((x > y) ? x : y);
var strColor = [Link](16);
strColor = "#" + strColor + strColor + strColor;
[Link]("<td width='4' height='4' " +
"bgColor='" + strColor + "'> </td>");
}
[Link]("</tr>");
}
[Link]("</table>");
Объект body
Среди свойств объекта document было указано свойство body, которое
представляет собой объект DOM-дерева, соответствующий элементу
BODY или FRAMESET HTML-разметки. Этот объект является реализацией
интерфейса HTMLBodyElement (см. спецификацию DOM) и в соответст-
вии с его описанием имеет несколько свойств. Стоит отметить, что
практически всем свойствам объекта body (кроме background) соответ-
ствуют аналогичные свойства объекта document (они не определяются
спецификацией DOM и поддерживаются браузерами только для обеспе-
чения обратной совместимости). В табл. 4.5 приведен перечень и описа-
96 Часть I. Основы создания Web-страниц и скриптов
ние свойств объекта body, соответствующих им свойств объекта
document и атрибутов HTML.
Таблица 4.5. Свойства объектов body и document
body document Атрибут HTML Описание
link linkColor link Цвет текста
непосещенных ссылок
aLink alinkColor alink Цвет текста активных
ссылок
vLink vlinkColor vlink Цвет текста посещенных
ссылок
background — background URI фонового
изображения документа
bgColor bgColor bgcolor Цвет фона документа
text fgColor text Цвет текста документа
Изменяя значения этих свойств, можно легко управлять некоторыми
характеристиками визуального представления документа (например,
менять цвет фона или фоновое изображение). Однако принципиально
более правильным и более универсальным способом, с помощью кото-
рого можно достичь аналогичного эффекта, является манипулирование
стилистической информацией документа.
DOM и CSS
Как подчеркивалось в главе 3, использование каскадных таблиц стилей
CSS в соответствии с принципом разделения структуры и представления
дает разработчикам массу преимуществ при создании и поддержке
гипертекстовых документов. Очевидно, возможность управления табли-
цами стилей (динамического подключения/отключения, доступа к от-
дельным правилам, их модификация) и встроенной информацией о сти-
ле каждого элемента имеет большое значение для создания современ-
ных клиентских сценариев.
Управление таблицами стилей
Начать данный подраздел, пожалуй, следует с двух утверждений. Утверж-
дение первое: DOM специфицирует мощные средства работы с табли-
Ãëàâà 4. Объектные модели браузера и документа 97
цами стилей. Утверждение второе: этими средствами нельзя воспользо-
ваться в полной мере. Дело в том, что все интерфейсы для работы с таб-
лицами стилей (и, в частности, для работы с таблицами стилей CSS)
вводятся только в спецификации DOM уровня 2 (раздел спецификации
DOM уровня 2, посвященный управлению стилями, находится в катало-
ге w3c\dom\dom2\style). К сожалению, на момент написания данного
материала ни один браузер не поддерживает полностью все требования
спецификации DOM второго уровня. Также имеются некоторые расхо-
ждения со стандартом в реализации DOM CSS браузера Microsoft
Internet Explorer (что будет отмечаться по мере необходимости). Именно
поэтому здесь будет приведен лишь небольшой обзор основных воз-
можностей работы с таблицами стилей.
Итак, самым простым способом, позволяющим получить доступ ко всем
внедренным (содержащимся в элементах STYLE) и связанным (с по-
мощью элемента LINK) таблицам стилей документа, является использо-
вание коллекции styleSheets объекта document. Следует упомянуть, что
в терминах спецификации DOM уровня 2 styleSheets является атрибу-
том интерфейса DocumentStyle, а не Document (или производных от не-
го), однако спецификация рекомендует предусматривать получение эк-
земпляра интерфейса DocumentStyle через специфическое для языка
приведение типа из экземпляра интерфейса Document, поэтому в
JavaScript коллекция styleSheets представлена свойством объекта
document. Таким образом, доступ к объекту, соответствующему первой
таблице стилей в документе, может быть произведен следующим спо-
собом:
var oStyleSheet = [Link](0);
Каждый элемент коллекции [Link] соответствует одной
внедренной либо связанной с документом таблице стилей. При исполь-
зовании таблиц стилей CSS элементы коллекции представлены объекта-
ми, являющимися реализацией интерфейса CSSStyleSheet. Интерфейс
CSSStyleSheet в свою очередь наследуется от интерфейса StyleSheet,
являющегося абстрактным базовым интерфейсом для работы с таблица-
ми стилей любого типа. Таким образом, элементы коллекции
styleSheets имеют следующие свойства (табл. 4.6), определяемые ин-
терфейсом StyleSheet.
Особый интерес здесь представляют свойства disabled и media, т. к.
позволяют влиять на применение таблиц стилей к документу, а следова-
тельно, и на его визуальное представление. Так, изменяя значение свой-
98 Часть I. Основы создания Web-страниц и скриптов
ства disabled, можно выборочно "отключать" и заново задействовать
таблицы стилей. А работая с объектом media — изменять список деск-
рипторов устройств, для которых предназначена таблица, что также мо-
жет приводить к изменению отображения документа.
Таблица 4.6. Свойства элементов коллекции styleSheets
Свойство Описание
disabled Логическое значение, определяющее, должна ли
таблица стилей применяться при отображении до-
кумента
href Указывает URI связанной таблицы стилей (содер-
жит значение атрибута href элемента LINK, либо
null, если таблица стилей является внедренной)
media Представляет собой объект, содержащий инфор-
мацию о media-дескрипторах устройств, при ото-
бражении документа на которых должна приме-
няться данная таблица
ownerNode Ссылка на объект дерева документа, соответст-
вующий элементу разметки HTML, задающему таб-
лицу стилей (STYLE или LINK)
parentStyleSheet Применим только к таблицам стилей, определен-
ным на языках описания, допускающих вложен-
ность таблиц. Определяет "родительскую" таблицу
title Содержит значение атрибута title элемента, оп-
ределяющего таблицу стилей
type Указывает тип таблицы стилей (например,
"text/css")
Интерфейс CSSStyleSheet определяет два дополнительных метода и два
атрибута, необходимых для управления таблицами стилей CSS. С по-
мощью методов (insertRule и deleteRule) можно производить добав-
ление и удаление стилистических правил в таблице. Атрибут ownerRule
логически соответствует правилу @import CSS. На втором атрибуте
cssRules следует остановиться чуть подробнее.
Атрибут cssRules интерфейса CSSStyleSheet определяет список всех
правил, содержащихся в таблице стилей. В JavaScript реализацией этого
интерфейса является объект, представляющий собой коллекцию. С по-
Ãëàâà 4. Объектные модели браузера и документа 99
мощью него возможен доступ к отдельным правилам таблицы стилей и
их изменение. Стоит отметить, что в большинстве браузеров, четко сле-
дующих стандартам, эта коллекция называется cssRules. Однако в
Microsoft Internet Explorer именем аналогичного объекта является rules.
Таким образом, доступ к объекту, соответствующему первому правилу
первой таблицы стилей документа, может производиться следующим
образом:
var oRule = [Link](0).[Link](0);
Однако при работе скрипта в среде Internet Explorer это должно выгля-
деть так:
var oRule = [Link](0).[Link](0);
Теперь необходимо сказать несколько слов об объектах, соответствую-
щих правилам таблиц стилей CSS (в примерах, приведенных ранее, пе-
ременной oRule присваивалась ссылка на такой объект). Для представ-
ления CSS-правил всех видов (как наборов правил, так и правил "at"),
в спецификации DOM уровня 2 определена группа интерфейсов
(табл. 4.7).
Таблица 4.7. Интерфейсы для представления CSS-правил
Интерфейс Описание
CSSUnknownRule Соответствует "at"-правилу, не поддерживаемому
текущим агентом пользователя
CSSStyleRule Соответствует одному набору правил таблицы
стилей CSS
CSSMediaRule Соответствует правилу @media
CSSFontFaceRule Соответствует правилу @font-face
CSSPageRule Соответствует правилу @page
CSSImportRule Соответствует правилу @import
CSSCharsetRule Соответствует правилу @charset
Каждый из этих интерфейсов определяет особые атрибуты и методы для
управления конкретным типом правил таблиц стилей. Однако все они
наследуются от одного интерфейса — CSSRule, являющегося абстракт-
ным интерфейсом для представления как наборов правил, так и правил
"at". Интерфейс CSSRule имеет несколько свойств (табл. 4.8).
100 Часть I. Основы создания Web-страниц и скриптов
Таблица 4.8. Свойства интерфейса CSSRule
Свойство Описание
cssText Текущее текстовое представление правила (нена-
чальное значение)
parentRule Если правило является вложенным в другое прави-
ло (например, стилистическое правило, содержа-
щееся в блоке @media), то этот атрибут указывает
"родительское" правило, иначе — null
parentStyleSheet Ссылка на таблицу стилей, содержащую данное
правило
type Числовое значение, указывающее тип правила
Свойство type представляет большой интерес, т. к., используя его, мож-
но узнать действительный тип объекта (интерфейс), представляющего
элемент коллекции cssRules, и, следовательно, при необходимости вы-
полнять специфическую обработку правила таблицы стилей. Возможные
значения этого свойства описаны в виде констант в декларации интер-
фейса CSSRule. Константы, их числовые значения и названия соответст-
вующих интерфейсов приведены в табл. 4.9.
Таблица 4.9. Константы, определяющие действительный тип
объекта, реализующего интерфейс CSSRule
Константа Значение Интерфейс
UNKNOWN_RULE 0 CSSUnknownRule
STYLE_RULE 1 CSSStyleRule
CHARSET_RULE 2 CSSCharsetRule
IMPORT_RULE 3 CSSImportRule
MEDIA_RULE 4 CSSMediaRule
FONT_FACE_RULE 5 CSSFontFaceRule
PAGE_RULE 6 CSSPageRule
Еще одним интересным интерфейсом, о котором стоит сказать особо,
является CSSStyleRule. Объекты, реализующие этот интерфейс, соответ-
ствуют обычным стилистическим правилам таблиц стилей. То есть, гру-
бо говоря, любое правило, не являющееся правилом "at", представляется
Ãëàâà 4. Объектные модели браузера и документа 101
в JavaScript объектом DOM, прототип которого определяется деклара-
цией интерфейса CSSStyleRule.
Интерфейс CSSStyleRule имеет всего два атрибута: selectorText —
текстовое представление селектора правила, и style — объект, реали-
зующий интерфейс CSSStyleDeclaration (интерфейс CSSStyleDeclaration
и объект style будут подробно описаны далее), предоставляющий сред-
ства для работы с отдельными свойствами (и их значениями) пра-
вил CSS.
Исходя из всего сказанного, можно полагать, что алгоритмы обработки
таблиц стилей должны выглядеть чрезвычайно просто. Например, поиск
нужного стилистического правила (получения ссылки на соответствую-
щий объект) в таблице стилей сводится к последовательному перебору
всех объектов коллекции cssRules (или rules для Microsoft Internet
Explorer), анализу свойства type и, если его значение равно STYLE_RULE,
анализу свойства selectorText этого объекта. Однако, к сожалению,
здесь также имеются расхождения с теорией, обусловленные все теми
же отступлениями от стандартов в браузере Microsoft Internet Explorer.
Есть два значимых отличия реализации коллекции rules объекта табли-
цы стилей от реализации, предписываемой спецификацией DOM:
коллекция содержит только объекты стилистических правил (прави-
ла "at", видимо, просто не включаются в иерархию объектной модели);
элементы коллекции (объекты правил) реализуют интерфейс
CSSStyleDeclaration не полностью. Объекты имеют только свойства
selectorText и style, но не имеют свойств (например, того же свой-
ства type), определяемых интерфейсом CSSRule, от которого, соглас-
но спецификации, наследуется CSSStyleDeclaration.
Таким образом, при манипулировании объектами правил таблиц стилей
CSS абсолютно не стоит полагаться на документированное поведение.
Это, как минимум, означает, что необходимо проверять существование
коллекции rules (что указывает на факт работы с объектной моделью
Internet Explorer). А также, при определении типа правила, если свойст-
во type не существует, можно анализировать существование свойства
style для выделения стилистических правил (однако проверку type
осуществлять следует — возможно, браузер от Microsoft еще будет под-
держивать это свойство).
Прилагаемый к книге компакт-диск содержит пример (в файле
ex_4_03.htm, код скрипта находится в файле ex_4_03.js, в каталоге
examples\04), иллюстрирующий принципы работы с таблицами стилей,
102 Часть I. Основы создания Web-страниц и скриптов
используя коллекцию [Link]. Он выводит информацию
обо всех внедренных и внешних таблицах стилей и правилах, содержа-
щихся в них. Далее приведен немного сокращенный фрагмент кода из
этого скрипта, осуществляющий последовательный перебор всех объек-
тов таблиц стилей и для каждого такого объекта перебор всех объектов
правил.
var nCount = [Link];
for(var nSheet = 0; nSheet < nCount; nSheet++)
{
var oSheet = [Link](nSheet);
DumpSheet(nSheet, oSheet);
if([Link])
[Link] = [Link];
var nRulesCount = [Link];
for(var nRule = 0; nRule < nRulesCount; nRule++)
{
var oRule = [Link](nRule);
DumpRule(nRule, oRule);
}
}
Важным моментом, на который стоит обратить внимание, здесь являет-
ся способ работы с коллекцией правил таблиц стилей (cssRules или
rules). Как можно видеть из примера, в случае существования свойства-
коллекции rules у объекта таблицы стилей просто создается еще одно
свойство с именем cssRules, которому присваивается ссылка на коллек-
цию rules. И далее работа с коллекцией правил происходит единооб-
разно — через это свойство. Следует заметить, что данный прием (соз-
дание у объекта нового свойства, являющегося ссылкой на существую-
щее) гораздо рациональнее, чем осуществление проверок доступности
необходимого свойства в разных местах скрипта.
На этом мы закончим обзор принципов управления встроенными и вне-
дренными таблицами стилей. Напоследок хотелось бы еще раз отметить
обстоятельство, уже упоминавшееся в начале главы: не все (даже совре-
менные) браузеры полностью поддерживают требования спецификации
DOM уровня 2. Поэтому стоит с большой осторожностью использовать
описанные возможности, обязательно анализируя существование необ-
Ãëàâà 4. Объектные модели браузера и документа 103
ходимых объектов, либо ориентироваться на версию и тип браузера.
Особо хочется подчеркнуть, что в некоторых браузерах средства мани-
пулирования таблицами стилей могут отсутствовать вообще. Например,
браузер Opera версии 8.02, установленный у автора на момент написа-
ния книги, не поддерживал коллекцию [Link], поэтому
пример, рассмотренный ранее, при работе в нем будет выводить просто
сообщение о невозможности доступа к таблицам стилей. К сожалению,
Opera не поддерживает и свойство sheet (см. атрибут sheet интерфейса
LinkStyle) объектов модели документа, соответствующих HTML-эле-
ментам STYLE и LINK (ссылающимся на таблицы стилей), которое пред-
ставляет собой ссылку на объект таблицы стилей, определяемой элемен-
том. Таким образом, похоже, в этом браузере невозможно программно
модифицировать таблицы стилей. Учитывая подобные обстоятельства,
гораздо более надежными способами управления отображением доку-
мента на основе стилистической информации представляется опериро-
вание встроенной информацией о стиле и модификация свойства
className DOM-объектов элементов.
Управление встроенной информацией о стиле
Как указывалось в главе 3, с помощью атрибута style каждому элементу
в документе может быть сопоставлен индивидуальный набор правил
CSS. Такая стилистическая информация называется встроенной (inline).
В рамках DOM-модели доступ к встроенной стилистической информа-
ции возможен через свойство style соответствующего объекта.
Свойство style представляет собой объект, реализующий интерфейс
CSSStyleDeclaration, уже упоминавшийся ранее. Согласно декларации
этого интерфейса, объект style должен иметь следующие свойства
(табл. 4.10).
Таблица 4.10. Свойства объекта style
Свойство Описание
cssText Текстовое представление блока CSS-правил, сопостав-
ленного элементу. Изменение значения этого свойства
приводит к установке нового набора правил для элемента
length Количество CSS-свойств, задаваемых встроенным описа-
нием
parentRule Ссылка на объект правила CSS (интерфейс CSSRule).
Установлено только, если блок описаний является частью
правила таблицы стилей. В случае встроенного описания
равно null
104 Часть I. Основы создания Web-страниц и скриптов
Также спецификацией определяются следующие методы объекта style
(табл. 4.11).
Таблица 4.11. Методы объекта style
Метод Описание
item Возвращает имя одного из CSS-свойств, явно
установленных встроенным описанием. Пара-
метром метода является числовой индекс свой-
ства. Количество CSS-свойств определяется
свойством length
getPropertyValue Возвращает значение CSS-свойства, установ-
ленного встроенным описанием, имя которого
передается в качестве параметра метода
getPropertyCSSValue Возвращает значение свойства CSS, имя кото-
рого передано как параметр, в виде объекта,
реализующего интерфейс CSSValue
removeProperty Удаляет свойство, установленное встроенным
CSS-описанием
getPropertyPriority Возвращает приоритет указанного свойства
setProperty Устанавливает значение и приоритет свойства с
указанным именем
Пользуясь вышеприведенными свойствами и методами можно легко
получать информацию обо всех свойствах и их значениях во встроенном
CSS-описании элемента, а также добавлять, удалять свойства и модифи-
цировать их значения. Например, установить зеленый цвет фона для
всей области отображения документа (для элемента BODY) можно так:
[Link]("background-color",
"green", "");
Однако этот код также не является кроссбраузерным — он не будет ра-
ботать в Microsoft Internet Explorer. В отношении реализации объекта
style этот браузер также сильно отступает от стандартов. Из перечис-
ленных свойств он поддерживает только cssText, использование кото-
рого удобно далеко не во всех случаях. А для получения, установки зна-
чений и удаления свойств встроенного CSS-описания у объекта style
в Internet Explorer будут присутствовать методы getExpression,
setExpression и removeExpression соответственно. Из этого следует,
Ãëàâà 4. Объектные модели браузера и документа 105
что при использовании методов объекта style для работы со свойствами
CSS необходимо предусматривать дополнительные проверки, что озна-
чает написание лишнего кода. Обычно гораздо рациональнее и проще
изменять значения свойств встроенного CSS-описания элемента через
соответствующие свойства объекта style.
Спецификация DOM уровня 2 определяет интерфейс CSS2Properties,
специально предназначенный для удобного получения и установки зна-
чений свойств встроенного CSS-описания элемента, представляемого
объектом, реализующим интерфейс CSSStyleDeclaration (в JavaScript
это объект style). Интерфейс CSS2Properties не содержит методов, но
содержит большое количество атрибутов. Каждый атрибут этого интер-
фейса соответствует определенному свойству CSS и обычно имя ат-
рибута схоже по написанию с именем свойства (например, атрибут
backgroundColor соответствует свойству background-color, а
listStyleType — свойству list-style-type).
Важным моментом, который хотелось бы подчеркнуть здесь, является
то, что спецификация предполагает обеспечение средствами языков
программирования возможности получения интерфейса CSS2Properties
из экземпляра CSSStyleDeclaration. Применительно к JavaScript это
означает, что объект style кроме свойств, соответствующих атрибутам
интерфейса CSSStyleDeclaration, содержит также свойства, соответст-
вующие атрибутам CSS2Properties. Таким образом, для объекта style
допустима установка и получение значений большого количества
свойств, соответствующих свойствам встроенного CSS-правила элемен-
та. Получение значения таких свойств равноценно вызову метода
getPropertyValue, а установка — setProperty. Например, программно
задать зеленый цвет фона для элемента BODY при помощи установки
CSS-свойства background-color можно следующим образом:
[Link]="green";
Данный способ хорош своей простотой. К тому же, он поддерживается
всеми современными браузерами, поэтому при его применении не надо
заботиться об обеспечении совместимости. Далее, при написании
скриптов, мы будем широко использовать именно его.
Как уже отмечалось, имена свойств объекта style и соответствующих
им свойств правил CSS весьма схожи. В основном, отображение имен
CSS-свойств в имена свойств объектной модели следует некоторому
простому правилу: из имени CSS-свойства убираются дефисы, и каждая
часть имени, следующая после дефиса, начинается со строчного симво-
ла. Исключением является лишь свойство float. Ему соответствует
106 Часть I. Основы создания Web-страниц и скриптов
свойство объекта style cssFloat, поскольку float является ключевым
словом JavaScript. Несмотря на то, что имя необходимого свойства объ-
екта style практически всегда можно "угадать", хочется заметить, что
перечень всех этих свойств и их соответствие свойствам CSS всегда
можно увидеть на странице описания интерфейса CSS2Properties в спе-
цификации DOM уровня 2. Также соответствие свойств в более удобном
для просмотра виде можно найти в документе addons\[Link] на
компакт-диске.
Управление стилем на основе
значения атрибута class элемента
Этот метод динамического изменения отображения элементов не ис-
пользует манипулирование стилистической информацией в прямом
смысле, однако в некоторых случаях может оказаться весьма полезным
и эффективным. Он основан на использовании различных наборов CSS-
правил с селекторами атрибута class, которые могут быть применены к
одному и тому же элементу. Изменяя значение свойства className объ-
екта, соответствующего некоторому элементу в документе, можно обес-
печить применение к нему различных наборов правил CSS, что будет
влиять на визуальное отображение элемента.
Далее приведен немного сокращенный пример, содержащийся на ком-
пакт-диске (файл examples\04\ex_4_04.htm), который иллюстрирует
применение данного принципа для создания динамических визуальных
эффектов. В нем осуществляется смена цвета фона блока с текстом при
щелчке на нем мышью.
<head>
<style type="text/css">
DIV.c0 { background : green; }
DIV.c1 { background : blue; }
</style>
</head>
<body>
<DIV
class="c0"
onclick="[Link]='c'+(([Link]=='c0')?'1':'0');">
Щелкните здесь мышью
</DIV>
</body>
В примере использовано назначение обработчика события щелчка мы-
ши для элемента DIV. В обработчике события onclick производится из-
Ãëàâà 4. Объектные модели браузера и документа 107
менение значения свойства className, что приводит к изменению ото-
бражения элемента. События и их обработка будут подробно рассматри-
ваться в следующем разделе.
События в объектной модели
Используя сценарии, манипулирующие различными составляющими
объектной модели, можно создавать динамические документы различ-
ной степени сложности. Однако возможность динамического изменения
содержимого или визуального отображения документа не обеспечивает
его интерактивности, т. е. способности активного взаимодействия с
пользователем. Для создания интерактивного документа необходим ме-
ханизм, позволяющий определять программную реакцию на действия
пользователя либо изменения в операционном окружении. Такие изме-
нения либо действия пользователя называют событиями. Специфиче-
ский код, исполняющийся при возникновении в программной системе
событий определенного типа, называют обработчиком события (как
правило, он оформляется в виде специальной процедуры или функции).
Исполнение обработчика при возникновении определенного события и
выполнение специальных действий, связанных с этим событием, назы-
вается обработкой события. Совокупность механизмов возникновения,
передачи, способов назначения обработчиков и обработки событий на-
зывается событийной моделью (или моделью обработки событий).
В данном разделе мы рассмотрим основные модели обработки событий,
возникающих в объектной модели документа, поддерживаемых совре-
менными браузерами.
Несколько слов
о моделях обработки событий
К сожалению, необходимо констатировать, что формирование моделей
обработки событий в процессе совершенствования разных типов брау-
зеров происходило аналогично развитию объектных моделей, т. е. инди-
видуально для каждого браузера. В результате, на данный момент, мож-
но выделить несколько таких моделей, существование которых обуслов-
лено наличием некоторой общей части (набора событий, атрибутов
поддерживаемых всеми современными браузерами) и индивидуальных
расширений объектной модели, несовместимых между собой. Таким
образом, можно говорить о существовании следующих моделей обра-
ботки событий.
108 Часть I. Основы создания Web-страниц и скриптов
Базовая модель. Определяет минимальное подмножество средств
обработки событий, поддерживаемых как старыми, так и новыми
версиями практически всех браузеров.
Модель обработки событий DOM уровня 2. Стандартизированная,
описанная в отдельной части спецификации DOM уровня 2, модель
обработки событий. Поддерживается многими новыми версиями
браузеров.
Специфические модели обработки событий. Прежде всего, к данной
категории относится событийная модель Microsoft Internet Explorer
(версии 4 и выше). Также сюда можно отнести модель обработки со-
бытий старых версий браузеров Netscape (версии выше третьей, но
ниже шестой).
Поскольку с базовой моделью обработки событий совместимы все ос-
тальные модели, базовая модель поддерживается всеми типами браузе-
ров, предоставляет значительную гибкость и возможности, достаточные
для решения большинства задач создания интерактивных документов,
в данном разделе основное внимание будет уделено именно ей. Осталь-
ные модели будут рассмотрены менее подробно.
Базовая модель обработки событий
Как следует из названия, эта модель обработки событий определяет не-
которую основу, принципы, поэтому и начать данный раздел будет уме-
стно с изложения самих принципов.
Принципы обработки событий
Как уже было сказано, для обработки события необходимо создать об-
работчик. Также события могут быть разных типов. В моделях обработ-
ки событий браузеров обработчик события сопоставляется определен-
ному элементу в документе и определенному событию. Это значит, что
код конкретного обработчика исполняется в случае возникновения со-
бытия только определенного типа, связанного с конкретным элементом.
Базовая модель обработки событий предполагает два способа сопостав-
ления обработчиков событий элементам:
использование атрибутов обработчиков событий в HTML-разметке
(см. также разд. "Включение кода в обработчики событий элемен-
тов Web-страниц" главы 1);
назначение обработчиков событий программно, путем установки
значений свойств объектов DOM, соответствующих атрибутам обра-
ботчиков событий.
Ãëàâà 4. Объектные модели браузера и документа 109
Так, например, назначить обработчик события щелчка мышью для эле-
мента при помощи HTML-разметки можно следующим образом:
<div onclick="alert('Click !');">Щелкните здесь мышью</div>
При щелчке по такому элементу будет исполнен ассоциированный
JavaScript-код:
alert('Click !');
Значениями HTML-атрибутов обработчиков событий могут являться
любые (даже весьма сложные) корректные фрагменты программного
кода.
Для того чтобы программно назначить элементу обработчик событий
(способ 2), необходимо получить ссылку на объект в дереве объектной
иерархии документа, соответствующий этому элементу, и присвоить
необходимому атрибуту ссылку на функцию-обработчик, уже опреде-
ленную в коде скрипта. Например, для элемента, задаваемого следую-
щей HTML-разметкой:
<div id="Div0">Щелкните здесь мышью</div>
обработчик события щелчка мышью можно назначить так (код этого
примера находится в файле examples\04\ex_4_05.htm на компакт-диске):
<script type="text/javascript">
<!--
function OnClickDiv0()
{
alert('Обработчик OnClickDiv0');
}
[Link]("Div0").onclick = OnClickDiv0;
//-->
</script>
Следует отметить тот факт, что первый способ назначения обработчика
событий принципиально не отличается от второго. В примерах, приве-
денных ранее, в обоих случаях свойству onclick объекта, соответст-
вующего элементу DIV, присваивалась ссылка на объект функции. Толь-
ко во втором случае функция была определена явно, а в первом — сге-
нерирована браузером на основе значения атрибута onclick в
HTML-разметке. В этом достаточно просто убедиться, если отобразить
значение свойства, определяющего обработчик события (например, того
же onclick) для объекта, соответствующего элементу, у которого дан-
ный обработчик задан в HTML-разметке. Это делается в примере, нахо-
110 Часть I. Основы создания Web-страниц и скриптов
дящимся в файле examples\04\ ex_4_06.htm на компакт-диске. Документ
содержит следующий фрагмент разметки:
<div id="Div0" onclick="alert('Click !'); /* onclick */"></div>
Далее, во внедренном скрипте, производится вывод информации об об-
работчике:
var oElement = [Link]("Div0");
[Link]("Значение атрибута onclick:\r\n" +
[Link]);
[Link]("\r\nЗначение typeof(onclick): " +
typeof([Link]));
Вывод, генерируемый скриптом, в Internet Explorer выглядит так (по-
добные результаты получаются и в других браузерах):
Значение атрибута onclick:
function anonymous()
{
alert('Click !'); /* onclick */
}
Значение typeof(onclick): function
Отсюда явно видно, что свойство onclick содержит ссылку на объект
функции, а тело функции представляет собой значение атрибута onclick
элемента DIV.
Основные события DOM
В предыдущем разделе мы рассмотрели способы создания обработчиков
событий, связанных с определенным элементом гипертекстового доку-
мента. В примерах создавались обработчики только одного события —
щелчок мышью. Но, конечно же, современные браузеры поддерживают
возможность обработки достаточно большого набора событий. Далее
приводится описание событий (и имена соответствующих атрибутов
элементов HTML, используемых для назначения обработчиков этих со-
бытий), которые можно отнести к базовой модели обработки событий,
т. е. поддерживаемых всеми современными браузерами. Описания
сгруппированы по типам событий.
Для начала рассмотрим события пользовательского ввода, к которым
относятся события, возникающие в результате действий пользователя,
производимых при помощи манипулятора "мышь" (табл. 4.12), а также
работе с клавиатурой (табл. 4.13).
Ãëàâà 4. Объектные модели браузера и документа 111
Таблица 4.12. Атрибуты для назначения обработчиков
событий мыши
Атрибут Описание обрабатываемого события
onclick Щелчок мышью
ondblclick Двойной щелчок мышью
onmousedown Нажатие кнопки мыши (если курсор мыши находится
над элементом)
onmouseup Отпускание кнопки мыши (если курсор мыши находится
над элементом)
onmouseover Перемещение курсора мыши в область, занимаемую
элементом
onmousemove Движение курсора мыши над элементом
onmouseout Выход курсора мыши за границы элемента
Перечисленные атрибуты, задающие обработчики событий мыши, могут
использоваться практически со всеми элементами, которые визуально
представляются в документе. Для наглядного ознакомления с последо-
вательностью и зависимостями возникновения событий мыши при раз-
личных действиях, производимых пользователем, можно воспользовать-
ся примером examples\04\ ex_4_07.htm на компакт-диске.
Таблица 4.13. Атрибуты для назначения обработчиков
событий клавиатуры
Атрибут Описание обрабатываемого события
onkeydown Нажатие клавиши
onkeyup Отпускание клавиши
onkeypress Клавиша была нажата, а затем отпущена
Эти свойства также применимы к большинству визуально-представимых
элементов. События клавиатуры возникают для элемента, находящегося
в фокусе ввода, т. е. активизированного мышью, либо выделенного при
помощи перевода фокуса клавишей <Tab>.
При получении либо потере фокуса элементом также возникают собы-
тия (табл. 4.14).
112 Часть I. Основы создания Web-страниц и скриптов
Таблица 4.14. Атрибуты для назначения обработчиков событий
получения и потери фокуса
Атрибут Описание обрабатываемого события
onfocus Получение элементом фокуса ввода (активизация его при
помощи мыши либо вследствие перехода к нему при помощи
клавиши <Tab>)
onblur Потеря элементом фокуса ввода (обычно вследствие получе-
ния фокуса другим элементом)
Обработчики событий получения/потери фокуса обычно устанавлива-
ются для интерактивных элементов — элементов форм (TEXTAREA, INPUT,
BUTTON и т. д.), ссылок (A, AREA). Иногда также и для других элементов:
OBJECT, EMBED, DIV, SPAN, IMG, TABLE.
Существуют события, возникающие только для форм и элементов форм
(табл. 4.15).
Таблица 4.15. Атрибуты для назначения обработчиков
событий форм и их элементов
Атрибут Описание обрабатываемого события
onchange Событие возникает для элементов INPUT, SELECT,
TEXTAREA при потере ими фокуса, если в промежутке вре-
мени между получением фокуса и его потерей содержимое
элемента было изменено пользователем
onselect Событие возникает при выделении текста в элементах
INPUT и TEXTAREA
onreset Событие очистки формы. Обычно возникает после нажатия
кнопки reset
onsubmit Событие отправки формы. Возникает после нажатия кнопки
submit
Обработка событий форм и их элементов будет подробно рассмотрена в
главе 11.
Два последних, описываемых здесь, события обычно используются для
совершения некоторых действий сразу после загрузки документа в брау-
зер и непосредственно перед его закрытием, а также позволяют в неко-
торой степени контролировать состояние загружаемых в браузер объек-
тов (табл. 4.16).
Ãëàâà 4. Объектные модели браузера и документа 113
Таблица 4.16. Атрибуты для назначения обработчиков событий
загрузки и выгрузки документа
Атрибут Описание обрабатываемого события
onload Событие, уведомляющее, что объект загружен в браузер.
Обработчик удобно устанавливать для элемента BODY или
FRAMESET для исполнения некоторого кода сразу после
загрузки документа. Новые версии браузеров поддержива-
ют обработку этого события для элементов IMG, SCRIPT,
STYLE, LINK, APPLET, EMBED
onunload Возникает для элементов BODY и FRAMESET при "выгрузке"
текущего документа — закрытии окна браузера или загруз-
ке в то же окно другого документа
Безусловно, в этой главе описан достаточно малый набор событий, од-
нако этот тот минимум, на который в настоящее время можно полагать-
ся при программировании скриптов для любого браузера.
Программный запуск и отмена обработки событий
Помимо того, что события в объектной модели могут возникать вслед-
ствие действий пользователя, существует возможность программно
инициировать аналогичный процесс, приводящий к вызову установлен-
ных обработчиков и, возможно, к исполнению предопределенного дей-
ствия, связанного с событием. Для этого у объектов DOM, соответст-
вующих некоторым элементам HTML, существуют специальные мето-
ды. Эти методы оказываются весьма полезными во многих случаях:
например, для установки фокуса на нужные элементы, программной
отправки форм и т. д. В табл. 4.17 приведен минимальный, поддержи-
ваемый всеми браузерами, набор таких методов и их описание.
Таблица 4.17. Методы объектов DOM, инициирующие
возникновение событий
Метод Описание
blur Вызывает потерю фокуса ввода элементом, способным при-
нимать пользовательский ввод (INPUT, TEXTAREA, SELECT, A,
AREA, BUTTON)
focus Вызывает перемещение фокуса ввода на элементы, способ-
ные принимать пользовательский ввод (INPUT, TEXTAREA,
SELECT, A, AREA, BUTTON)
114 Часть I. Основы создания Web-страниц и скриптов
Таблица 4.17 (окончание)
Метод Описание
click Для ссылок, элемента BUTTON и элемента INPUT с атрибутом
type, равным button, submit, reset, checkbox, radio. Вызы-
вает действие, аналогичное щелчку мышью
reset Только для объекта элемента FORM. Устанавливает полям
формы значения по умолчанию. Действие аналогично нажа-
тию кнопки reset (определяемой тегом <INPUT
type="reset">)
submit Только для объекта элемента FORM. Отправляет форму. Дей-
ствие аналогично нажатию кнопки submit (определяемой те-
гом <INPUT type="submit">)
select Вызов метода приводит к выделению всего текста в элемен-
тах INPUT (с атрибутом type, равным text, password, file) и
TEXTAREA
Таким образом, имея в документе ссылку, определяемую HTML-раз-
меткой:
<a href="[Link]" id="ref_0">ссылка</a>
можно программно инициировать переход браузера по этой ссылке при
помощи следующего JavaScript-кода:
[Link]('ref_0').click();
Следует сделать особое замечание по поводу вызова обработчиков со-
бытий вследствие использования данных методов. Вызов соответст-
вующих обработчиков происходит после вызова всех методов, кроме
submit (вследствие вызова метода click вызывается обработчик, зада-
ваемый атрибутом onclick, вследствие вызова focus — атрибутом
onfocus, и т. д., но после вызова submit обработчик, определяемый ат-
рибутом onsubmit, не вызывается). Это, в частности, означает, что ини-
циированную программно отправку формы нельзя отменить. Таким об-
разом, если обработчик отправки формы должен вызываться в любом
случае, при использовании метода submit необходимо сделать это явно.
Специфические модели обработки событий
Как уже было сказано, к специфическим моделям обработки событий
относятся: модель Microsoft Internet Explorer версии 4 и выше и модель
обработки событий браузеров Netscape версий выше 4-й, но ниже 6-й.
Ãëàâà 4. Объектные модели браузера и документа 115
Они имеют некоторое сходство в плане нововведений относительно ба-
зовой модели, но абсолютно несовместимы между собой в отношении
процесса обработки событий. Стоит отметить, что модель Netscape
практически ушла в прошлое (начиная с версии 6, браузеры Netscape
базируются на ядре Mozilla, которое использует модель обработки со-
бытий DOM уровня 2), а модель Internet Explorer поддерживается только
в этом типе браузеров. Тем не менее их рассмотрение имеет смысл. Ос-
новная причина состоит в том, что, видимо, обе эти модели, имея неко-
торые общие черты, оказали влияние на формирование модели обработ-
ки событий DOM уровня 2 (что и будет показано далее). Также, по-
скольку Internet Explorer — один из самых популярных браузеров (а до
появления Firefox — самый популярный), при разработке кроссбраузер-
ного скрипта, в котором не обойтись только базовой моделью обработки
событий, придется учитывать и его модель, а значит, понимать принци-
пы ее работы.
Есть два основных отличия DOM уровня 2 и специфических моделей
обработки событий от базовой.
Наличие информации о контексте возникновения события (коорди-
наты курсора мыши, коды нажатых клавиш, тип события и т. д.). Эта
информация представлена в виде значений свойств специального
объекта (Event в моделях DOM уровня 2, Netscape, и event в модели
MSIE). Перечень свойств и способ передачи этого объекта обработ-
чику событий также различаются в разных моделях.
Распространение событий по иерархии объектов модели документа.
Как вы помните, в базовой модели обработчик событий некоторого
типа назначается для определенного элемента, и он обрабатывает
только события, связанные с этим элементом. Все события, для ко-
торых не назначен обработчик, вызывают действия, определенные
браузером по умолчанию. В специфических и DOM-моделях событие
передается от объекта к объекту в дереве документа, имея шанс по-
пасть в обработчик одного из элементов, стоящих ближе к корню
иерархии. Направление и точка начала распространения события
также определяются конкретной моделью.
Модель обработки событий Netscape
Это первая модель, достаточно сильно отличающаяся от базовой, пре-
доставляющая разработчику гораздо больше возможностей и обладаю-
щая большей гибкостью обработки событий.
116 Часть I. Основы создания Web-страниц и скриптов
Специфической особенностью модели является то, что возникающие
события распространяются через иерархию объектов DOM по направле-
нию от корневого объекта window к целевому объекту (для которого воз-
никло событие). Модель предусматривает механизм "захвата" событий.
Он служит для обработки событий одного типа от разных элементов
одним и тем же обработчиком конкретного объекта. События могут за-
хватываться объектами window, document, layer при помощи метода
captureEvents. Методу передается числовое значение, которое является
битовой маской, определяющей типы событий, захватываемых объек-
том. Например:
[Link]([Link] | [Link]);
Исполнение этого кода приведет к тому, что обработка всех событий
одинарного и двойного щелчка мышью на любом элементе документа
будет производиться обработчиками объекта document. Поскольку собы-
тия распространяются вглубь DOM-дерева, то из объектов, производя-
щих захват событий, больший приоритет в обработке имеют те, которые
стоят ближе к корню иерархии. Существует возможность инициировать
дальнейшее распространение события, после его обработки некоторым
обработчиком. Для этого служит метод routeEvent.
Модель позволяет получать доступ к контекстной информации события,
используя свойства специального объекта Event. Он создается браузе-
ром при возникновении события, и ссылка на него передается в обра-
ботчик. Среди свойств этого объекта есть такие, как type (строка, ука-
зывающая тип события, например "click"), target (ссылка на объект
DOM, соответствующий элементу, для которого возникло событие),
pageX, pageY, screenX, screenY (координаты курсора мыши относитель-
но документа и экрана, соответственно, в момент возникновения собы-
тия), which (код нажатой клавиши в кодировке Unicode для событий
клавиатуры или цифровое обозначение кнопки для событий мыши)
и т. д. Имея такую информацию, можно создавать гораздо более функ-
циональные скрипты, чем при использовании только базовой модели.
Модель обработки событий
Microsoft Internet Explorer
Данная модель, как и модель Netscape, обладает рядом преимуществ по
сравнению с базовой моделью. В ней также доступна контекстная ин-
формация о событии и также имеет место распространение событий
в дереве документа. Однако существуют сильные отличия этой модели
от модели обработки событий Netscape.
Ãëàâà 4. Объектные модели браузера и документа 117
Аналогично модели Netscape, контекстная информация о событии в мо-
дели Internet Explorer представлена объектом типа Event. Однако ссылка
на этот объект не передается обработчику событий в качестве аргумен-
та. Вместо этого объект доступен в глобальной области видимости
скрипта через идентификатор event. Перечень свойств объекта event в
MSIE практически аналогичен перечню свойств объекта Event в
Netscape, однако их имена отличаются. К примеру, свойствам pageX,
pageY, screenX, screenY в Netscape соответствуют свойства clientX,
clientY, screenX, screenY в Internet Explorer.
Распространение событий в DOM-дереве MSIE происходит по направ-
лению от элемента, для которого возникло событие, к корню объектной
иерархии — объекту document (т. е. в обратном направлении относи-
тельно модели Netscape). По умолчанию, при этом производится после-
довательный вызов всех установленных обработчиков событий данного
типа для каждого объекта, следующего в иерархии. Распространение
события можно остановить, присвоив свойству [Link] зна-
чение true в одном из обработчиков. Однако прекращение распростра-
нения возможно не для всех типов событий, также не все события име-
ют способность к распространению. К примеру, событие щелчка
мышью (click) распространяется, и его распространение может быть
отменено, событие отпускания клавиши (keyup) распространяется, но
его распространение не может быть отменено, а событие отправки фор-
мы (submit) не распространяется.
Модель обработки событий DOM уровня 2
Подробное описание этой модели обработки событий можно найти в
соответствующем разделе спецификации DOM уровня 2 на сайте кон-
сорциума W3C по адресу [Link] или на
прилагаемом к книге компакт-диске в каталоге w3c\dom\dom2\events. На
данный момент это наиболее гибкая и функциональная модель. Она реа-
лизует важнейшие концепции базовой и специфических моделей, а так-
же добавляет несколько весьма интересных возможностей. К сожале-
нию, эта модель обработки событий поддерживается не всеми типами
браузеров и, в основном, только достаточно новыми их версиями. Брау-
зеры Mozilla, Netscape 6, Opera 7.50, Firefox поддерживают эту модель.
Opera 7.0 поддерживает ее лишь частично. Microsoft Internet Explorer не
поддерживает вообще.
Первой особенностью, выгодно отличающей модель событий DOM
уровня 2 от предыдущих моделей, является принцип распространения
118 Часть I. Основы создания Web-страниц и скриптов
событий в DOM-дереве. Практически, здесь сочетаются специфические
модели Internet Explorer и Netscape. Процесс распространения события
в объектной модели делится на две фазы:
1. Фаза "погружения". После возникновения события происходит его
продвижение по направлению от корня (объекта document) вглубь
объектной иерархии — к объекту-цели, т. е. объекту, соответствую-
щему HTML-элементу, в контексте которого оно возникло.
2. Фаза "всплытия". Достигнув обработчика целевого элемента, собы-
тие может начать движение в обратном направлении — к корню де-
рева документа, объекту document.
Как на первом, так и на втором этапе продвижения события через дере-
во документа происходит последовательный вызов обработчиков, соот-
ветствующих типу события и фазе обработки (об этом чуть позже),
установленных для объектов-узлов иерархии. В обработчики в качестве
параметра передается ссылка на объект Event, содержащий контекстную
информацию события. Каждый вызываемый обработчик может пред-
отвратить дальнейшее распространение события, вызвав метод
stopPropagation объекта Event, отменить предопределенное действие,
соответствующее событию, вернув false, либо вызвав метод
preventDefault объекта Event, а также делегировать обработку события
другому объекту дерева документа, вызвав метод dispatchEvent этого
объекта и передав ему в качестве параметра ссылку на существующий
объект Event.
Вторая особенность модели событий DOM уровня 2, обеспечивающая
большую гибкость по сравнению с остальными моделями, заключается в
стандартном для нее способе назначения обработчиков событий. Кроме
традиционно поддерживаемых браузерами способов: при помощи
HTML-атрибутов в разметке и присвоения свойствам объектов, опреде-
ляющих обработчики, ссылок на функции-обработчики, существует еще
один. Он заключается в использовании метода addEventListener объек-
тов, являющихся узлами дерева документа. Его синтаксис таков:
[Link](strEventName, refHandler,
bPlungePhase);
Параметры имеют следующее значение:
strEventName — строка, идентифицирующая событие;
refHandler — ссылка на функцию-обработчик события;
bPlungePhase — логическое значение, указывающее фазу продвиже-
ния события в дереве документа, для которой устанавливается обра-
Ãëàâà 4. Объектные модели браузера и документа 119
ботчик. Значение этого параметра должно быть true, если необхо-
димо установить обработчик для фазы "погружения", и false, если
для фазы "всплытия".
Таким образом, следующий код устанавливает обработчик с идентифи-
катором OnAllClick для всех событий щелчка мышью в документе:
[Link]("click", OnAllClick, true);
У данного метода установки обработчиков есть несколько явных пре-
имуществ:
он позволяет назначать отдельные обработчики для каждой фазы
прохождения события через дерево документа. Это дает возмож-
ность обрабатывать событие как до, так и после его попадания в це-
левой обработчик;
данный метод позволяет сопоставить больше одного обработчика
конкретного события конкретному DOM-объекту (в этом случае та-
кие обработчики в нужный момент вызываются все подряд, однако
порядок их вызова ничем не определяется);
самое главное, должно быть, преимущество данного метода, прин-
ципиально отличающее его от методов назначения обработчиков ба-
зовой и специфических моделей, состоит в том, что он позволяет со-
поставить обработчик любому текстовому узлу дерева документа
(например, соответствующему элементу U).
Каждый установленный при помощи addEventListener обработчик
можно легко аннулировать, вызвав метод removeEventListener для того
же объекта и с теми же параметрами, с которыми обработчик устанав-
ливался. Таким образом, следующий код удалит обработчик события
click, установленного в примере выше:
[Link]("click", OnAllClick, true);
Еще одна особенность модели событий DOM уровня 2, которую хочется
здесь подчеркнуть, заключается в предоставлении обработчику контек-
стной информации о событии. Как и в других моделях, эта информация
представляется в виде свойств объекта Event. Он передается в обработ-
чик по ссылке. Отличие от других моделей состоит в том, что набор его
свойств и методов не фиксирован. Это обусловлено тем, что объект
Event реализует один из DOM2-интерфейсов, соответствующих событи-
ям определенного типа. Спецификация DOM уровня 2 определяет не-
сколько типов событий (пример определения поддержки браузером со-
бытий конкретного типа будет приведен в главе 5): события мыши
120 Часть I. Основы создания Web-страниц и скриптов
(click, mouseover), изменений в структуре документа (DOMNodeInserted,
DOMNodeRemoved), пользовательского интерфейса (DOMFocusIn,
DOMFocusOut), традиционные события HTML и старых браузеров ( load,
submit, resize). Каждому типу событий соответствует свой интерфейс
для объекта Event. То есть, для каждого конкретного типа событий объ-
ект Event будет содержать определенное, специфическое подмножество
свойств и методов.
Подробнее узнать об обработке событий в DOM уровня 2 можно из со-
ответствующего раздела спецификации (на компакт-диске: w3c\dom
\dom2\events\[Link]). К сожалению, использование этой модели со-
бытий резко ограничивается отсутствием ее поддержки в Microsoft
Internet Explorer. И ее применение пока видится лишь в сочетании с ба-
зовой и специфическими моделями событий.
* * *
На этом мы заканчиваем обзор объектных моделей и переходим к сле-
дующей части книги. Более подробно мы рассмотрим многие аспекты
работы с DOM далее, на практических примерах.
ЧАСТЬ II
Решение типовых задач
программирования скриптов
Глава 5. Определение параметров
операционного окружения
Глава 6. Дата и время
Глава 7. Работа с cookie
Глава 8. Регулярные выражения
Глава 9. Протоколы mailto и javascript
Мы переходим к следующей, практической части книги. Преимуще-
ственно, далее будут рассматриваться конкретные задачи создания
скриптов и их решения. Данная часть посвящена типовым задачам
программирования сценариев для Web-страниц. Скрипты, обсуждае-
мые здесь, будут входить составной частью в большинство примеров,
рассматриваемых в других частях этой книги.
ГЛАВА 5
Определение параметров
операционного окружения
Постоянное совершенствование браузеров, улучшение поддержки ими
различных стандартов, а также общий прогресс в области Web-техно-
логий делают возможным создание все более функциональных и удоб-
ных в использовании сетевых информационных систем. Действительно,
все меньше встречается сайтов, не использующих генерацию содержи-
мого страниц на стороне сервера, и редкий сайт сегодня обходится без
применения клиентских сценариев. Использование клиентских скриптов
различными сетевыми сервисами (Web-интерфейсами почтовых служб,
системами формирования заказов интернет-магазинов, биллинг-пане-
лями) давно уже стало нормой.
Возможности, открывающиеся вследствие улучшенной поддержки стан-
дартов новыми браузерами, а также наличия у них собственных расши-
рений DOM побуждают Web-мастеров разрабатывать более мощные
сценарии. Однако, как и раньше, актуальной остается проблема совмес-
тимости, т. е. обеспечения работоспособности скриптов в исполняющей
среде различных браузеров. Учитывая быструю эволюцию браузеров
(выпускаются новые версии, и даже появляются новые линейки про-
грамм, как, например, Mozilla Firefox), а также существование их "об-
легченных" версий (для портативных устройств), можно говорить, что
решение этой проблемы отнюдь не тривиально.
Существуют две крайности, которым нередко следуют Web-мастера,
в отношении использования сценариев. Первая — полный отказ от ис-
пользования исполняемого кода на стороне клиента. Такие сайты, без-
условно, более просты в разработке и более надежны в плане представ-
124 Часть II. Решение типовых задач программирования скриптов
ления браузером, однако, во многих случаях, менее удобны в использо-
вании и более "тяжеловесны" (HTML-код занимает больший объем, что
приводит к увеличению трафика и времени загрузки страницы). К при-
меру, при отображении большой древовидной структуры (каталога рас-
сылок, книг или карты сайта) с возможностью "разворачивания" и "сво-
рачивания" ветвей дерева можно использовать генерацию страниц на
сервере. При этом, после выбора пользователем очередного уровня де-
рева для просмотра будет происходить загрузка страницы с новым со-
держимым. Однако, используя клиентский сценарий, можно генериро-
вать части дерева без перезагрузки, запрашивая с сервера только необ-
ходимые данные. Вторая крайность, свойственная в основном новичкам
в разработке сайтов — ориентация на определенный тип и определен-
ную версию браузера. Возможно, вы посещали сайты, страницы кото-
рых содержат предупреждения типа: "Этот сайт предназначен для про-
смотра в Microsoft Internet Explorer 6.0". Часто, при просмотре таких
страниц в других браузерах, на них возникают ошибки исполнения
скриптов.
Учитывая весь спектр сочетаний поддерживаемых браузерами возмож-
ностей, а также тот факт, что исполнение скриптов может быть просто
отключено, логичной представляется организация сайта, при которой
обеспечивается его нормальное функционирование без использования
клиентских сценариев вообще. Однако, там где их использование воз-
можно и целесообразно, сценарии должны присутствовать, загружаясь и
повышая интерактивность страниц, но только при гарантированной их
работоспособности, т. е. соответствии браузера определенным требова-
ниям. Таким образом, весьма часто может возникать задача определения
различных параметров совместимости браузера.
Определение версии JavaScript
Одной из наиболее общих характеристик исполняющей среды, дающей,
однако, некоторое представление о поддерживаемых возможностях,
является информация об используемой версии JavaScript. Простой метод
определения версии языка, реализуемой ядром исполнения сценариев
браузера, основан на применении атрибута language в теге <SCRIPT>.
Значением этого атрибута является строка, идентифицирующая язык
скрипта, содержащегося в элементе SCRIPT. Для идентификации
JavaScript может использоваться строка "JavaScript", либо строка вида
"JavaScriptX.Y", где X.Y — номер версии языка. Данный метод исполь-
Ãëàâà 5. Определение параметров операционного окружения 125
зует тот факт, что содержимое элементов SCRIPT, для которых указан
неподдерживаемый браузером язык скриптов либо его версия, пол-
ностью игнорируется. Таким образом, включив в страницу несколько
элементов SCRIPT с указанием возрастающих значений версий JavaScript
в атрибуте language, сможем определить ее максимальное значение.
<script language="JavaScript"><!--
var strJSVer = "1.0"; //--></script>
<script language="JavaScript1.1"><!--
strJSVer = "1.1"; //--></script>
<script language="JavaScript1.2"><!--
strJSVer = "1.2"; //--></script>
<script language="JavaScript1.3"><!--
strJSVer = "1.3"; //--></script>
<script language="JavaScript1.4"><!--
strJSVer = "1.4"; //--></script>
<script language="JavaScript1.5"><!--
strJSVer = "1.5"; //--></script>
Данный код в виде примера определения версии JavaScript можно найти
в файле examples\05\ex_5_01.htm на компакт-диске.
Возможно, в некоторых случаях полезными могут оказаться функции
браузера Microsoft Internet Explorer (табл. 5.1).
Таблица 5.1. Функции получения сведений о ядре исполнения
скриптов в Internet Explorer
Функция Описание
ScriptEngine Возвращает строку, указывающую исполь-
зуемый язык скриптов (например,
"JScript")
ScriptEngineMajorVersion Старшее число версии ядра исполнения
скриптов
ScriptEngineMinorVersion Младшее число версии ядра исполнения
скриптов
ScriptEngineBuildVersion Номер сборки ядра исполнения скриптов
Код, иллюстрирующий их использование, также включен в файл
ex_5_01.htm:
126 Часть II. Решение типовых задач программирования скриптов
function GetScriptEngineInfo()
{
return ScriptEngine() + " Версия " +
ScriptEngineMajorVersion() + "." +
ScriptEngineMinorVersion() + " Сборка " +
ScriptEngineBuildVersion();
}
Поэкспериментировав с примером, вы можете убедиться в том, что даже
современные браузеры реализуют поддержку JavaScript на разном уров-
не. Например, мои эксперименты привели к следующему: Microsoft
Internet Explorer 6.0 поддерживает JavaScript версии 1.3, Opera 7.0 — 1.4,
Mozilla 1.2b, Netscape 7.0, Opera 8.02 — 1.5.
Определение параметров
совместимости браузера
Информация о версии JavaScript, реализуемой ядром исполнения
сценариев, позволяет сделать вывод о возможности корректной интер-
претации отдельно взятого скрипта данной средой. Однако она не дает
представления о типе и версии браузера, поддержке им конкретных воз-
можностей и технологий. А такая информация для обеспечения безоши-
бочной работы бывает часто необходима.
Много полезной для разработчика информации о браузере предостав-
ляют свойства объекта [Link]. Информация об их назначе-
нии, а также минимальных версиях браузеров, в которых осуществлена
их поддержка, приведена в табл. 5.2.
Таблица 5.2. Свойства объекта [Link]
Поддержка
Свойство Описание браузерами
MSIE NN Opera
appCodeName Содержит кодовое имя брау- 3.02 2.0 3.0
зера. По историческим при-
чинам в большинстве случа-
ев содержит значение
"Mozilla"
appMinorVersion Дополнительный номер вер- 4.0 — —
сии браузера Microsoft
Internet Explorer
Ãëàâà 5. Определение параметров операционного окружения 127
Таблица 5.2 (продолжение)
Поддержка
Свойство Описание браузерами
MSIE NN Opera
appName Содержит строку, указываю- 3.02 2.0 3.0
щую тип браузера. Однако
этому значению не стоит до-
верять, т. к. браузеры могут
фальсифицировать его в
зависимости от настроек
пользователя
appVersion Содержит полную информа- 3.02 2.0 3.0
цию о версии браузера. На-
пример: "4.0 (compatible;
MSIE 6.0; Windows NT 5.1;
MyIE2; Maxthon; .NET CLR
1.0.3705)"
browserLanguage Строка, идентифицирующая 4.0 6.0
язык браузера (содержит код
языка)
cookieEnabled Логическое значение, указы- 4.0 6.0 6.0
вающее, включена ли в брау-
зере поддержка cookie
cpuClass Указывает тип процессора 4.0 — —
language Строка, идентифицирующая — 4.0 5.0
язык браузера. В Internet
Explorer это значение содер-
жится в свойстве
browserLanguage
onLine Значение равно false, если 4.0 — —
включен режим автономной
работы, иначе — true
platform Строка, указывающая плат- 4.0 4.0 5.0
форму, на которой запущен
браузер, исполняющий
скрипт. Например: "Win32",
"Linux i686", "MacPPC"
systemLanguage Содержит код языка и, воз- 4.0 — —
можно, код страны системных
настроек
128 Часть II. Решение типовых задач программирования скриптов
Таблица 5.2 (окончание)
Поддержка
Свойство Описание браузерами
MSIE NN Opera
userAgent Строка, передаваемая в виде 3.02 2.0 3.0
HTTP-заголовка USER-AGENT:
на сервер при запросе стра-
ницы. Содержит информацию
о типе, версии, ядре браузе-
ра, операционной системе
userLanguage Содержит код языка, выбран- 4.0 — 7.0
ного пользователем
plugins[] Коллекция объектов, содер- 4.0 3.0 3.0
жащих информацию о моду-
лях расширения (plug-ins),
используемых браузером
mimeTypes[] Коллекция объектов, содер- 4.0 3.0 3.0
жащих информацию о под-
держиваемых браузером
MIME-типах
В табл. 5.2 не указаны некоторые специфические свойства, реализуемые
только одним типом браузера. В любом случае, вы можете получить
список всех свойств объекта navigator путем их перечисления в цикле
for...in. Пример вывода значений описанных выше свойств можно
найти в файле examples\05\ex_5_02.htm на компакт-диске. Также этот
файл содержит примеры определения семейства браузера, языковых
настроек, используемых модулей расширений, поддержки Java и cookie
на основе свойств и методов объекта navigator.
Определение семейства браузера производится на основе анализа значе-
ния свойства userAgent и реализуется функцией GetBrowserTypeFrom-
UserAgent. Функция использует поиск набора строк, характерного для
каждого конкретного типа браузера в строке userAgent. Соответствие
конкретных наборов строк типам задается в виде массива объектов
(arrTokens), поэтому перечень определяемых браузеров может быть
легко расширен.
Языковые настройки определяются в функции GetUserLanguage как зна-
чение одного из свойств: language, userLanguage или browserLanguage
объекта navigator, в зависимости от того, какие из них поддерживаются
Ãëàâà 5. Определение параметров операционного окружения 129
браузером. Функция возвращает строковое значение кода языка так, как
оно содержится в указанных свойствах. Код языка соответствует стан-
дарту ISO 639 и может также содержать код страны в соответствии со
стандартом ISO 3166 после символа подчеркивания (например, ru_RU).
Как уже указывалось, информация о модулях расширений (plug-ins)
представляется свойствами объектов коллекции plugins объекта
navigator. В примере examples\05\ex_5_02.htm реализован простой вы-
вод информации о модулях путем перебора в цикле всех объектов кол-
лекции. На практике вывод подобной статистики вряд ли когда-либо
понадобится, однако это не означает, что коллекция plugins абсолютно
бесполезна. С ее помощью можно легко производить проверку наличия
установленных расширений (например, проигрывателей Flash или пото-
кового видео) и формировать некоторые части Web-страницы с учетом
этой информации (к примеру, включать в страницу Flash-анимацию при
наличии проигрывателя или обычную картинку при его отсутствии).
Простейший способ проверить факт наличия установленного модуля —
проанализировать существование соответствующего объекта в коллек-
ции. Так, установить, используется ли модуль расширения "Shockwave
Flash", можно при помощи условия:
if(typeof([Link]['Shockwave Flash']) !=
"undefined")
// Flash доступен
В случае выполнения условия можно сгенерировать фрагмент HTML-
разметки, включив в документ Flash-ролик при помощи элемента EMBED.
Однако следует учесть, что использование данного метода для тестиро-
вания поддержки браузером определенных технологий содержит источ-
ник возможных проблем. Во-первых, для проверки наличия определен-
ного модуля расширения необходимо совершенно точно знать его имя
(в предыдущем примере это 'Shockwave Flash'), а оно в один прекрас-
ный момент может быть изменено производителем модуля. Во-вторых,
подобный способ не дает верного результата в Internet Explorer. Дело в
том, что этот браузер использует встраиваемые в документ элементы
управления ActiveX для обработки специфических мультимедиа-
данных. Вследствие этого, в нем реализованы фиктивные коллекции
plugins[] и mimeTypes[] объекта navigator — число их элементов все-
гда равно нулю, и они предусмотрены только для предотвращения оши-
бок времени исполнения в неаккуратно написанных скриптах. Поэтому
использованию коллекции [Link][] должна предшествовать
проверка типа браузера. В случае Internet Explorer использование кол-
130 Часть II. Решение типовых задач программирования скриптов
лекции plugins бессмысленно, но можно воспользоваться элементом
OBJECT в HTML-разметке для включения в Web-страницу нужного муль-
тимедийного документа. Правда, если требуемый ActiveX-компонент не
будет найден на машине пользователя, браузер запросит разрешение на
его загрузку из Интернета.
Другая возможность узнать о поддерживаемых браузером техноло-
гиях состоит в использовании метода hasFeature объекта
[Link]. Этот объект поддерживается браузерами
Mozilla, Opera 7.0, Internet Explorer 5.0, Netscape 6.0. Прототип метода
hasFeature выглядит так:
[Link](strFeature, strVersion);
Параметр strFeature должен содержать кодовое обозначение техноло-
гии, а strVersion — код версии технологии (например "1.0", "2.0"),
факт поддержки которой необходимо выяснить. Метод возвращает true,
если поддержка осуществляется браузером, и false в противном случае.
В табл. 5.3 приведены возможные значения параметра strFeature
(в столбце "Код технологии") и описания соответствующих средств.
В столбце "Версия DOM" указана первая версия спецификации DOM,
в которой эти средства определяются (в параметре strVersion имеет
смысл передавать номер версии, не ниже указанной в таблице).
Таблица 5.3. Значения параметра strFeature
Версия
Код технологии Описание
DOM
Core 1.0 Поддержка базовых интерфейсов DOM
CSS 2.0 Поддержка базовых интерфейсов DOM CSS
CSS2 2.0 Поддержка дополнительных интерфейсов
DOM CSS
Events 2.0 Поддержка модели событий DOM
HTML 1.0 Поддержка HTML-модели DOM
HTMLEvents 2.0 Поддержка событий HTML 4.0 и DOM уров-
ня 0, не вызываемых действиями пользова-
теля (например: load, submit и т. д.)
MouseEvents 2.0 Поддержка событий мыши (часть DOM-мо-
дели событий)
Ãëàâà 5. Определение параметров операционного окружения 131
Таблица 5.3 (окончание)
Версия
Код технологии Описание
DOM
MutationEvents 2.0 Поддержка событий изменения структуры
документа (часть DOM-модели событий)
Range 2.0 Поддержка range-API — интерфейсов рабо-
ты с диапазонами содержимого документа
StyleSheets 2.0 Поддержка интерфейсов работы с табли-
цами стилей
Traversal 2.0 Поддержка интерфейсов обхода дерева
документа
UIEvents 2.0 Поддержка событий, вызываемых дейст-
виями пользователя (например, перемеще-
ние фокуса ввода)
Views 2.0 Поддержка интерфейсов представления
документа
XML 1.0 Интерфейсы поддержки XML
Таким образом определить, реализуются ли полностью объектной мо-
делью браузера интерфейсы работы с таблицами стилей CSS, описанные
в спецификации DOM уровня 2, можно при помощи следующего кода:
if([Link]('CSS', '2.0'))
// полностью поддерживается CSS API DOM уровня 2
К сожалению, данный метод также не дает абсолютно достоверных дан-
ных о поддерживаемых браузером возможностях. Браузер может реали-
зовывать только часть функциональности, рекомендованной некоторым
разделом спецификации DOM, при этом, естественно (т. к. реализация
не является полной), метод hasFeature вернет false. Однако для удов-
летворения потребностей конкретного скрипта поддерживаемых функ-
ций может вполне хватить. Поэтому в большинстве случаев гораздо
проще полагаться на анализ типа и версии браузера, либо (что еще более
надежно) — на анализ существования конкретных объектов и их мето-
дов. В примере ex_5_02.htm реализован вывод информации о поддержи-
ваемых браузером стандартах на основе метода hasFeature для версий
DOM от 1.0 до 3.0. Попробуйте открывать этот документ в браузерах
разных типов и версий — вы получите весьма интересные данные об их
отношении к стандартам DOM.
132 Часть II. Решение типовых задач программирования скриптов
Определение параметров дисплея
Существует еще одна группа параметров, значения которых могут быть
небезынтересны Web-мастеру. Они относятся к свойствам используемой
графической среды. В JavaScript эти параметры представлены в виде
свойств объекта [Link], поддержка которого осуществляется
всеми графическими браузерами, начиная с ранних версий
(Internet Explorer 4.0, Netscape 4.0, Opera 5.0 и т. д.). Описание этих
свойств приведено в табл. 5.4.
Таблица 5.4. Свойства объекта [Link]
Свойство Пояснение
height Высота дисплея в пикселах
width Ширина дисплея в пикселах
availHeight Высота экранной области, доступной для
размещения окон (не занятой панелью за-
дач, меню и т. д.), в пикселах
availWidth Ширина экранной области, доступной для
размещения окон (не занятой панелью за-
дач, меню и т. д.), в пикселах
colorDepth Глубина цвета изображения на дисплее
(в битах на пиксел)
bufferDepth Z-глубина пиксела внеэкранного буфера
deviceXDPI Физическое разрешение по оси x дисплея,
в DPI (точках на дюйм)
deviceYDPI Физическое разрешение по оси y дисплея,
в DPI (точках на дюйм)
logicalXDPI Логическое разрешение по оси x дисплея,
в DPI (точках на дюйм)
logicalYDPI Логическое разрешение по оси y дисплея,
в DPI (точках на дюйм)
updateInterval Значение интервала обновления монитора
(возможна установка значения)
fontSmoothingEnabled Логическое значение, указывающее, при-
меняется ли сглаживание экранных шриф-
тов
Ãëàâà 5. Определение параметров операционного окружения 133
Из приведенных здесь свойств несколько поддерживаются всеми вида-
ми браузеров (height, width, availHeight, availWidth, colorDept). Ос-
тальные — только некоторыми. В примере examples\05\ex_5_03.htm
реализован вывод значений этих свойств. Поскольку в Microsoft Internet
Explorer почему-то недоступно перечисление свойств screen через цикл
for...in, в скрипте использован массив их имен.
Примеров практического применения объекта screen можно привести
достаточно много. Большинство систем сбора статистики, таких как
Hotlog, [Link] и др., добавляют значения его свойств в URL изображе-
ния счетчика для передачи на сервер (информация о параметрах вычис-
лительных систем пользователей, посещающих сайт, интересна разра-
ботчикам дизайна). Также можно оптимизировать вид графической час-
ти сайта, основываясь на значении глубины цвета текущего устройства
отображения (свойство colorDepth), загружая, например, изображения с
цветовой палитрой из 216 "безопасных" цветов, если значение
colorDepth меньше 24, и полноцветные изображения, если это не так.
Это может помочь избежать проблем отображения сайта в браузерах
портативных устройств.
ГЛАВА 6
Дата и время
В этой главе мы рассмотрим работу с данными, представляющими зна-
чения времени и даты, объект Date и его применение в скриптах для
создания динамических эффектов на Web-странице.
Объект Date
Для хранения и манипулирования данными времени и даты в JavaScript
существует объект Date. Он инкапсулирует достаточно много методов,
дающих разработчику весьма широкие возможности и позволяющих
гибко оперировать данными. Объект Date специфицируется в первой
версии ECMAScript и поддерживается браузерами, начиная с весьма
ранних версий (Internet Explorer 3, Netscape Navigator 2, все версии
Mozilla, Firefox и т. д.). Однако необходимо заметить, что реализации
этого объекта в старых браузерах во многом некорректны. Поэтому его
не следует использовать в Internet Explorer и Netscape версий ниже 4.
Традиционно, объект Date хранит информацию в виде числа миллисе-
кунд, прошедших с 0 часов 0 минут 1 января 1970 года, по времени UTC
(Universal Coordinated Time, всеобщее скоординированное время). При
использовании всех методов этого объекта нумерация часов, минут, се-
кунд, миллисекунд, месяцев и дней недели ведется, начиная с 0. То есть,
индекс месяца января равен 0, декабря — 11. Дни недели нумеруются,
начиная с воскресенья (индекс воскресенья — 0, понедельника — 1,
субботы — 6). Нумерация дней месяца ведется с 1, т. е. допустимый
диапазон этих величин — от 1 до 31.
Ãëàâà 6. Дата и время 135
Для создания объекта Date существует несколько конструкторов:
oDate = new Date();
oDate = new Date(datePattern);
oDate = new Date(year, month, date[, hours[, minutes[,
seconds[, ms]]]]);
Здесь:
datePattern — строковое либо целочисленное значение даты/вре-
мени, которым будет инициализирован объект. Если значение явля-
ется строкой, то оно должно содержать данные в формате, пригод-
ном для метода parse объекта Date. Если значение целочисленное, то
оно интерпретируется как количество миллисекунд, прошедших с 0
часов 0 минут 1 января 1970 года, по времени UTC;
year — целое число, представляющее год;
month — целое число в диапазоне от 0 до 11, представляющее месяц;
date — целое число в диапазоне от 1 до 31, представляющее день
месяца;
hours — целое число в диапазоне от 0 до 23, представляющее часы;
minutes — целое число в диапазоне от 0 до 59, представляющее ми-
нуты;
seconds — целое число в диапазоне от 0 до 59, представляющее се-
кунды;
ms — целое число в диапазоне от 0 до 999, представляющее миллисе-
кунды.
Конструкторы Date, имеющие аргументы, создают объект, инициализи-
рованный переданными им значениями. Конструктор, не имеющий ар-
гументов, инициализирует создаваемый объект значением текущего
времени (UTC) машины, на которой исполняется скрипт (создание объ-
екта Date таким образом — наиболее часто применяемый способ полу-
чения текущего времени).
Операции с объектом Date
Как уже было сказано, объект Date предоставляет большой набор мето-
дов, дающих широкие возможности работы с данными даты/времени.
В первую очередь стоит выделить большую группу get/set-функций,
позволяющих легко получать и устанавливать любые компоненты даты
и времени объекта Date. Это функции: getFullYear, setFullYear,
136 Часть II. Решение типовых задач программирования скриптов
getMonth, setMonth, getDate, setDate, getHours, setHours, getMinutes,
setMinutes, getSeconds, setSeconds, getMilliseconds, setMilliseconds,
getDay, получающие и устанавливающие, соответственно, год, месяц,
день месяца, час, минуты, секунды, миллисекунды, день недели (обрати-
те внимание, что день недели можно только получить), используя
локальное время. Эти методы берут на себя всю работу по преобразова-
нию данных из внутреннего формата, в случае получения значений
(get-функции) и производят необходимое конвертирование обратно в
миллисекундное смещение относительно 0:00:00 01.01.1970 в случае
установки значений (кроме того, при помощи функций getTime и
setTime можно непосредственно получить или установить это смещение).
Существуют также функции getYear и setYear, имеющие то же на-
значение, что и функции getFullYear, setFullYear. Однако они яв-
ляются устаревшими и не рекомендуются к использованию из-за не-
однозначной семантики работы с датами. Дело в том, что диапазон от
1900 до 1999 года представляется этими функциями двузначным чис-
лом, а все за пределами этого диапазона — четырехзначными. На-
пример, для объекта Date, содержащего дату в 1991 году, функция
getYear вернет число 91, но для даты в 2006 году будет возвращено
2006.
Для всех перечисленных выше функций есть аналоги, оперирующие
UTC-временем: getUTCFullYear, setUTCFullYear, getUTCMonth,
setUTCMonth, getUTCDate, setUTCDate, getUTCHours, setUTCHours,
getUTCMinutes, setUTCMinutes, getUTCSeconds, setUTCSeconds,
getUTCMilliseconds, setUTCMilliseconds, getUTCDay. Кстати, смещение
(в минутах) локального времени машины от времени UTC можно узнать
при помощи метода объекта Date getTimezoneOffset.
Теперь рассмотрим применение этих методов. Поскольку каждый из них
оперирует отдельно взятой составляющей даты, то, очевидно, их очень
удобно использовать для вычислений моментов времени на основе не-
которого начального значения. Так, инициализировав объект Date, к
примеру, текущим временем, а затем изменив год при помощи
setFullYear, можно узнать день недели, который будет того же числа
того же месяца, но в другом году:
var date = new Date();
[Link]('Сегодня : ' + [Link]() + '<br>');
[Link]([Link]() + 1);
[Link]('Через год : ' + [Link]());
Ãëàâà 6. Дата и время 137
В этом примере мы увеличиваем год для объекта date на 1, получив те-
кущее значение года через getFullYear, прибавив к нему 1, а затем ус-
тановив новое значение при помощи setFullYear. Здесь используется
метод toString объекта Date для преобразования даты/времени в строку
(он будет описан далее). Вот вывод, сгенерированный приведенным
выше скриптом:
Сегодня : Mon Jan 16 04:24:02 UTC+0300 2006
Через год : Tue Jan 16 04:24:02 UTC+0300 2007
Отсюда видно, что 16-го января 2006 года был понедельник, а 16-го ян-
варя 2007 года будет вторник. Таким же образом можно менять все ос-
тальные компоненты даты.
Одной из главных особенностей методов установки отдельных компо-
нентов даты/времени является возможность комплексного изменения
всех компонентов при установке значения одной из них. Это происходит
в случае, если устанавливаемое значение компонента больше макси-
мального значения из диапазона его возможных значений либо отрица-
тельно. В этом случае изменение компонента влияет на значения компо-
нентов высших "порядков". Так, например, вызвав для объекта Date,
инициализированного временем 10 января 2006 года 00:00:00, метод
setHours(32), получим новое значение даты 11 января 2006 года
08:00:00. Отсюда ясно виден принцип работы этих методов. Следует
заметить, что конвертирование значений производится с учетом всех
"календарных" особенностей. Использование методов модифицирова-
ния даты иллюстрирует пример, доступный на компакт-диске в файле
examples\06\ex_6_01.htm. Далее приведен код этого примера:
var d = document;
var dSnap = new Date(2004, 1, 28); // 28 февраля 2004 года
var dTemp = new Date([Link]());
[Link]('Начальная дата : ' + [Link]()
+ '<br>');
[Link]([Link]() + 1); // увеличим дату
// на 1 день
[Link]('Через 1 день : ' + [Link]()
+ '<br>');
[Link]([Link]() + 1); // увеличим дату еще
// на 1 день
[Link]('Через 2 дня : ' + [Link]()
+ '<br>');
138 Часть II. Решение типовых задач программирования скриптов
[Link]([Link]()); // восстановим
// первоначальную дату
[Link]([Link]() + 1); // увеличим год
// на 1
[Link]('Через 1 год : ' + [Link]()
+ '<br>');
[Link]([Link]() + 1); // увеличим дату
// на 1 день
[Link]('Через 1 год 1 день : ' + [Link]()
+ '<br>');
[Link]([Link]() + 61); // увеличим дату
// еще на 61 день
[Link]('Через 1 год 62 дня : ' + [Link]()
+ '<br>');
Для преобразования даты в строковое представление здесь используется
метод toLocaleString, учитывающий информацию локализации и даю-
щий более удобочитаемый текст (он может различаться в разных брау-
зерах). Вот результат, сгенерированный скриптом в Microsoft Internet
Explorer 6.0:
Начальная дата : 28 февраля 2004 г. 0:00:00
Через 1 день : 29 февраля 2004 г. 0:00:00
Через 2 дня : 1 марта 2004 г. 0:00:00
Через 1 год : 28 февраля 2005 г. 0:00:00
Через 1 год 1 день : 1 марта 2005 г. 0:00:00
Через 1 год 62 дня : 1 мая 2005 г. 0:00:00
Как можно видеть, при установке даты учитывается количество дней в
месяцах, високосные года и т. д. Обратите особое внимание на способ
копирования значений из одного объекта Date в другой. Мы не исполь-
зуем присваивание:
dTemp = dSnap;
Вместо этого мы инициализируем объект количеством миллисекунд,
полученных у другого объекта:
[Link]([Link]());
Данная операция скопирует значение времени в dTemp в то время, как в
результате присваивания переменная dTemp превратилась бы в ссылку на
объект dSnap, и при дальнейших операциях с dTemp изменялись бы дан-
ные объекта dSnap.
Ãëàâà 6. Дата и время 139
Конвертирование даты/времени в строку
Как было указано ранее, создаваемый при помощи конструктора без
параметров объект типа Date инициализируется значением текущего
момента времени. Очевидно, простейшим примером его практического
применения мог бы стать вывод текущей даты и/или времени на Web-
странице. Понятно, что для отображения значения объекта Date (кото-
рое хранится в виде числа миллисекунд) необходимо осуществить его
конвертирование в приемлемое для восприятия текстовое представ-
ление.
Объект Date инкапсулирует несколько методов, позволяющих конверти-
ровать его данные в строку. Методы toString, toUTCString,
toGMTString, toLocaleString возвращают строки, содержащие инфор-
мацию как о дате, так и о времени, причем методы toUTCString и
toGMTString форматируют строку согласно интернет-стандартам, ис-
пользуя универсальное скоординированное время. Методы toString и
toLocaleString используют локальное время, но toLocaleString фор-
матирует строку в соответствии с текущими установками локализации
операционной системы (в Windows задаются через панель управления).
При помощи методов toTimeString и toDateString можно получить
строку, содержащую, соответственно, только время и только дату. При
помощи toLocaleTimeString и toLocaleDateString — то же самое, но с
учетом установок локализации. На компакт-диске содержится файл
примера examples\06\ex_6_02.htm, получающий текущую дату и конвер-
тирующий ее в строковое представление при помощи всех указанных
функций. Далее представлен результат его работы в браузере Internet
Explorer 6.0:
toString() : Mon Jan 16 14:21:03 UTC+0300 2006
toUTCString() : Mon, 16 Jan 2006 11:21:03 UTC
toGMTString() : Mon, 16 Jan 2006 11:21:03 UTC
toLocaleString() : 16 января 2006 г. 14:21:03
toTimeString() : 14:21:03 UTC+0300
toDateString() : Mon Jan 16 2006
toLocaleTimeString() : 14:21:03
toLocaleDateString() : 16 января 2006 г.
Как видно, при помощи методов toLocaleString, toLocaleTimeString,
toLocaleDateString можно получить текстовое представление да-
ты/времени во вполне "человеческом" формате. Однако вы можете лег-
ко убедиться, что в различных браузерах эти методы дают разный ре-
140 Часть II. Решение типовых задач программирования скриптов
зультат. Вот фрагмент страницы, сгенерированной скриптом примера
ex_6_02.htm в Opera 7.0:
toLocaleString() : 21.02.2006 11:16:40
Таким образом, если встает задача произвольного форматирования дан-
ных времени, содержащихся в объекте Date, единственно возможным
вариантом представляется получение отдельных ее компонентов (с по-
мощью функций типа getFullYear, getHours и т. д.) и формирования из
них необходимой строки.
Разработка объекта,
содержащего метод конвертирования даты/времени
в строку произвольного формата
Теперь давайте перейдем к практической реализации механизма, позво-
ляющего конвертировать данные объекта Date в строку произвольного
формата. Очевидным решением в этом случае является создание своего
типа объектов, наследующего функциональность объекта Date и инкап-
сулирующего дополнительный метод форматирования данных. Реализа-
ция такого объекта приведена в файле examples\Lib\[Link] на компакт-
диске, а в файле examples\06\ex_6_03.htm находится пример его исполь-
зования. Давайте разберемся, как он устроен.
Тип объекта называется CVDate. В терминах объектно-ориентированного
подхода JavaScript (языка на базе прототипов) для создания нового типа
объектов, осуществляющего наследование функциональности объектов
другого типа, необходимо написать функцию-конструктор этих объек-
тов, и присвоить ее свойству prototype ссылку на экземпляр объекта-
прототипа (см. разд. "Объекты" в главе 1). То есть, в нашем случае это
могло бы выглядеть так:
function CVDate()
{
// инициализация объекта
...
}
[Link] = new Date(0);
К сожалению, данный подход здесь не применим. Объекты, создавае-
мые такой функцией-конструктором, действительно будут наследовать
все методы объекта Date по "классической" схеме наследования
JavaScript, однако вызов этих методов будет приводить к ошибкам вре-
мени исполнения:
Ãëàâà 6. Дата и время 141
var oDate = new CVDate();
var nYear = [Link](); // это вызовет ошибку
Дело в том, что методы Date, как "встроенного" объекта JavaScript, реа-
лизуются ядром исполнения сценариев. При вызове им должна пе-
редаваться ссылка на действительный объект Date. Однако объекты,
обычно инициализируемые пользовательскими функциями-конструкторами
(ссылка на инициализируемый объект передается в конструктор через
идентификатор this), являются "пустышками", создаваемыми при по-
мощи конструктора Object(). Таким образом, при вызове методов объ-
екта CVDate, унаследованных от Date через цепочку прототипов, им бу-
дет передана ссылка на объект, созданный конструктором Object(), а не
Date(), что и приведет к ошибке.
Очевидным выходом из данного положения является создание новых
объектов при помощи конструктора Date() и добавление им необходи-
мых свойств и методов. В этом случае наследование не осуществляется,
и создание таких объектов можно производить при помощи обычной
функции. Однако было бы хорошо сохранить семантику создания объ-
ектов CVDate при помощи оператора new. К счастью, если функция-
конструктор возвращает значение, оно также будет являться результа-
том исполнения оператора new. Поэтому в конструкторе CVDate, нахо-
дящемся в файле [Link], создается новый объект Date, ссылка на него
передается в функцию Init, выполняющую добавление свойств и мето-
дов, а затем созданный объект используется как возвращаемое значение.
С объектом this никаких манипуляций не производится.
Также как и конструктор Date, конструктор CVDate принимает перемен-
ное число аргументов. Их смысл и порядок следования полностью ана-
логичен аргументам конструктора Date.
Метод объекта CVDate, осуществляющий конвертирование даты в тек-
стовую строку, называется Format. Он осуществляет форматирование
данных в соответствии со строкой-шаблоном, передаваемой ему в каче-
стве параметра, и возвращает результат:
var str = [Link]("Сейчас %H часов %M минут");
Строка-шаблон содержит специальные маркеры (коды), начинающиеся
со знака % (процент), заменяемые в ходе форматирования на значения
компонентов даты/времени. Список возможных маркеров содержится в
файлах [Link] и ex_6_03.htm.
Метод Format производит замену маркеров строки-шаблона при помощи
последовательности вызовов вспомогательного метода FormatOne, кото-
рый заменяет в переданной ему строке все вхождения одного маркера:
142 Часть II. Решение типовых задач программирования скриптов
[Link] = function(strFormat)
{
...
str = [Link](str, "A", this.m_aDay[[Link]()]);
str = [Link](str, "w", [Link]());
...
Кроме FormatOne, к создаваемому объекту добавляется еще один вспо-
могательный метод — FormatWidth. Он служит для преобразования чи-
словых значений в строку заданной длины, дополняя ее, если надо, ве-
дущими нулями.
Среди кодов форматирования, поддерживаемых методом Format, есть
такие, как: %a и %A (сокращенное и полное название дня недели), %b, %B,
%Bg (сокращенное, полное название месяца и название месяца в роди-
тельном падеже), а также %p — индикатор времени суток (AM или PM).
Соответствующие названия задаются в виде массивов строк, являющих-
ся свойствами объекта CVDate: m_aDay — массив названий дней,
m_aDayShort — массив сокращенных названий дней, m_aMonth — назва-
ния месяцев, m_aMonth — названия месяцев в родительном падеже,
m_aAMPM — названия индикаторов времени суток. Устанавливая новые
значения этих свойств, можно "настраивать" вывод метода Format уже
созданного объекта CVDate по своему усмотрению. Например,
var oDate = new CVDate(0);
[Link]([Link]("Время суток: %p<br>"));
oDate.m_aAMPM = ["ДП", "ПП"];
[Link]([Link]("Время суток: %p<br>"));
выведет:
Время суток: AM
Время суток: ДП
Ознакомившись с перечнем маркеров, поддерживаемых методом
Format, вы сможете увидеть, что он предоставляет достаточно широкие
возможности форматирования.
Применение объектов Date и CVDate
Теперь мы рассмотрим примеры, иллюстрирующие возможности прак-
тического применения объекта Date и объекта CVDate, разработка кото-
рого описана в предыдущем разделе.
Ãëàâà 6. Дата и время 143
Вывод текущего времени на Web-странице
Ярким примером, на котором можно продемонстрировать применение
объекта CVDate, является создание скрипта, эмулирующего электронные
часы — выводящего текущее время в некоторой части Web-страницы.
Конечно, такой элемент, как "часики", является чисто декоративным, не
несущим функциональной нагрузки. Но он очень прост в реализации и
демонстрирует основные принципы создания скриптов подобного рода.
Поэтому здесь мы не обойдем его своим вниманием.
Готовый скрипт, отображающий время в двух форматах, как показано на
рис. 6.1, внедрен в HTML-документ examples\06\ex_6_04.htm, находя-
щийся на компакт-диске. Давайте рассмотрим, как он работает.
Рис. 6.1. Часы
Так как будет использоваться объект CVDate, к документу подключен
внешний скрипт examples\Lib\[Link]. Принцип работы основного
скрипта состоит в периодическом получении текущего времени, форми-
ровании его текстового представления в нужном формате и установке
этого текста в определенную часть Web-страницы. Такой частью обыч-
но является некоторый элемент-контейнер (в нашем случае это будет
SPAN), имеющий уникальное значение атрибута ID для более легкого
доступа к нему. В документе ex_6_04.htm существуют два таких контей-
нера с идентификаторами clock_0 и clock_1.
Мы будем использовать DOM для вывода текста на Web-странице. Эле-
менты-контейнеры SPAN изначально не содержат текста, т. е. соответст-
вующие им объекты DOM не имеют дочерних текстовых узлов. Мы соз-
дадим такие узлы и добавим их в элементы-контейнеры:
var oNode_0 = [Link]("");
var oNode_1 = [Link]("");
[Link]("clock_0").appendChild(oNode_0);
[Link]("clock_1").appendChild(oNode_1);
144 Часть II. Решение типовых задач программирования скриптов
Имея доступ к этим узлам DOM-дерева через объекты oNode_0 и
oNode_1, мы всегда сможем изменить их текстовое содержимое, устано-
вив значение свойства nodeValue.
Поскольку время на "часах" должно изменяться, мы будем использовать
таймер для периодического обновления текста в элементах-контейнерах.
Для обработки событий таймера необходимо создать функцию-
обработчик, которая будет вызываться с заданным интервалом. В ней
мы просто будем создавать объект CVDate, используя конструктор без
параметров, чтобы получать текущее время, а затем обновлять содер-
жимое "часов" результатом вызова метода Format:
function OnTimer()
{
var oDate = new CVDate();
oNode_0.nodeValue = [Link](([Link]() % 2) ?
"%H %M" : "%H:%M");
oNode_1.nodeValue = [Link]("%I:%M:%S %p");
}
Обратите внимание, каким образом достигается получение "мигающего"
двоеточия — разделителя минут и секунд в первых "часах". Мы просто
используем разные шаблоны, передаваемые методу Format ("%H %M" или
"%H:%M") в зависимости от четности текущего значения числа секунд.
Таймер запускается при помощи метода setInterval объекта window с
интервалом 1 секунда (1000 миллисекунд). Перед его запуском метод
OnTimer вызывается напрямую для установки начальных значений вре-
мени.
OnTimer();
[Link]("OnTimer()", 1000);
Редирект с задержкой и выводом времени
до перехода
Скрипт, который мы рассмотрим сейчас, служит для создания страницы-
редиректора, обычно используемой на форумах, блогах, гостевых кни-
гах для кратковременного отображения некоторой информации с после-
дующим автоматическим переходом на другую страницу сайта. Часто на
такой странице кроме информационного сообщения отображается вре-
мя, оставшееся до перехода. Пример подобной страницы с внедренным
в нее скриптом находится в файле examples\06\ex_6_05.htm на компакт-
диске.
Ãëàâà 6. Дата и время 145
В HTML-разметке информационного сообщения содержится элемент-
контейнер для вывода числа секунд, оставшихся до редиректа:
<span id="tick"></span>
В начале скрипта производится инициализация переменных и добавле-
ние дочернего текстового DOM-узла в элемент-контейнер:
var nDelay = 5;
var nTimerID = 0;
var oDateEnd = new Date();
[Link]([Link]() + nDelay);
var oNode_0 = [Link]([Link](10));
[Link]("tick").appendChild(oNode_0);
Переменная nDelay определяет число секунд, через которое должен
произойти переход на другую страницу. Задавая ее значение, можно
настраивать скрипт на определенную задержку. Объект oDateEnd содер-
жит время момента перехода. Оно вычисляется на основе текущего зна-
чения времени путем его увеличения на nDelay секунд.
В этом скрипте также используется таймер. В функции-обработчике
событий таймера OnTimer создается объект Date для получения значения
текущего времени. Затем вычисляется разница (в миллисекундах) между
текущим временем и временем, сохраненным в oDateEnd (переменная
ms). В случае положительного значения ms — выводится значение вре-
мени до редиректа (это делается, как и в предыдущем скрипте через ус-
тановку значения свойства nodeValue), а если время истекло
(ms <= 0) — таймер останавливается и инициируется переход на другую
страницу.
function OnTimer()
{
var oDate = new Date();
var ms = [Link]() - [Link]();
if(ms <= 0)
{
[Link](nTimerID);
[Link] = "[Link]
}
else
oNode_0.nodeValue = ([Link](ms / 1000) +
1).toString();
}
nTimerID = [Link]("OnTimer()", 100);
146 Часть II. Решение типовых задач программирования скриптов
Для большей точности отсчета времени таймер запускается с интерва-
лом 100 миллисекунд.
При разработке такой страницы для реального сайта имеет смысл ввести
в ее раздел HEAD метатег, вызывающий переход к целевому документу
через заданное время даже при отключенном исполнении скриптов в
браузере пользователя:
<META HTTP-EQUIV="REFRESH" CONTENT="5;
URL=[Link]
ГЛАВА 7
Работа с cookie
В этой главе мы рассмотрим понятие cookies (закладок), возможности и
приемы работы с ними средствами JavaScript, а также практические
примеры использования cookie в клиентских сценариях.
Что такое cookie?
В двух словах, cookie — это небольшой фрагмент данных, который мо-
жет быть передан сервером клиенту (для простоты в качестве програм-
мы-клиента далее будем подразумевать браузер) вместе с содержимым
запрашиваемого документа. Этот фрагмент данных может быть сохра-
нен клиентом на компьютере пользователя, а при следующем обраще-
нии к документу — передан обратно на сервер. Кроме того, в браузере
доступ к cookies, связанных с документом, возможен при помощи Java-
Script.
Поскольку механизм обмена cookies позволяет сохранять данные на
клиентской машине, типичными областями их применения являются:
автоматическая аутентификация пользователей в системах аутенти-
фикации различных интернет-сервисов: автоматический "вход" на
форумы или сайты, требующие регистрации, в Web-интерфейс поч-
товых сервисов и т. д.;
автоматическое заполнение Web-форм, например, автоматическое
заполнение поля подписи в форме добавления сообщения в гостевую
книгу;
148 Часть II. Решение типовых задач программирования скриптов
сохранение различных пользовательских настроек. К примеру, на
сайте может присутствовать раздел выбора цветовой схемы оформ-
ления его страниц. Информация о предпочитаемой пользователем
схеме может быть сохранена на сервере, но это требует наличия сер-
верных скриптов для динамической генерации страниц на ее основе
(например, в зависимости от пользовательских настроек в раздел
HEAD документа будут добавляться ссылки на различные таблицы
стилей). Однако данные об используемой схеме можно сохранить в
cookie, а ссылки на таблицы стилей добавлять в раздел HEAD при по-
мощи JavaScript-сценария.
Cookie в деталях
Теперь давайте разберемся, как же "работают" cookies и какие возмож-
ности они нам реально предоставляют.
Cookie и HTTP
Как уже было сказано, cookies могут быть переданы браузеру сервером
вместе с ответом на запрос какого-либо документа. Изначально cookies
являются именно частью HTTP-протокола, а сохранение cookie браузе-
ром — частью реализации поддержки этого протокола. Браузеру cookies
передаются в виде поля Set-Cookie: заголовка HTTP-ответа сервера.
В заголовке ответа может присутствовать несколько таких полей. Вот
типичный пример поля Set-Cookie:.
Set-Cookie: PHPSESSID=d77736ea7445934fe40d7530d8d458c2; path=/
Присутствие такого поля в заголовке ответа сервера должно привести к
"запоминанию" браузером cookie с именем PHPSESSID и значением
d77736ea7445934fe40d7530d8d458c2. Кроме того, указание подмножест-
ва URL, которым сопоставим данный cookie в виде последовательности
path=/, приведет к тому, что этот cookie будет отсылаться серверу при
запросе любого документа в пределах домена сайта, с которого был за-
прошен документ, установивший cookie.
Возврат cookie на сервер осуществляется в поле COOKIE: HTTP-запроса
браузера. К примеру, получив с сервера единственный cookie в том виде,
как было показано выше, при следующем обращении к документам
с того же сайта браузер включит в HTTP-запрос поле:
Cookie: PHPSESSID=d77736ea7445934fe40d7530d8d458c2
Таким образом и осуществляется обмен cookies между сервером и кли-
ентом.
Ãëàâà 7. Работа с cookie 149
Приведенный ранее случай строки Set-Cookie: — один из простейших.
На самом деле, это поле может включать гораздо больше информации.
Вот его формат:
Set-Cookie: <name>=<value>[; <name>=<value>]...
[; expires=<date>][; domain=<domain_name>]
[; path=<some_path>][; secure][; httponly]
Отсюда видно, что поле Set-Cookie: состоит из последовательности
строк, разделенных символами ; (точка с запятой). Рассмотрим их зна-
чения:
<name>=<value> — каждая подобная пара устанавливает один cookie
с именем name и значением value. Из описания формата видно, что
в строке Set-Cookie: таких пар может быть несколько;
expires=<date> — задает дату/время окончания действия устанавли-
ваемых данным полем cookies <date> должно являться строкой,
представляющей GMT-время (по гринвичскому меридиану) в форма-
те: DAY, DD-MMM-YYYY HH:MM:SS GMT (например, Tue, 28 Feb 2006
12:41:04 GMT). После истечения срока действия cookies, они удаля-
ются браузером. Если время действия cookie не указано (cookie уста-
навливается без атрибута expires), им считается время окончания
интернет-сессии. Такие cookies часто называют сессионными — они
удаляются сразу после закрытия окна браузера;
domain=<domain_name> — указывает окончание множества доменных
имен, для которых действительны устанавливаемые cookies. К при-
меру, если при установке cookies значение <domain_name> указано
как .[Link], эти же cookies могут быть отосланы серверу при
запросе документов с [Link] и [Link] (допол-
нительным условием отсылки cookies будет являться проверка зна-
чения атрибута path — см. далее). Установка этого значения исполь-
зуется достаточно часто. Например, при наличии единой системы
аутентификации на сайте, на форуме сайта (если форум на поддоме-
не) логично разрешить отсылку cookie, идентифицирующего пользо-
вателя для обоих ресурсов, что поможет избежать повторного ввода
пароля уже авторизовавшимся пользователем при переходе с одного
из них на другой. Во избежание неконтролируемой отсылки cookie
значение <domain_name> не должно содержать только имя зоны (на-
пример, .com, .[Link]);
path=<some_path> — это значение задает подмножество URL, для
которых действительны устанавливаемые cookies. Например, если
150 Часть II. Решение типовых задач программирования скриптов
значение <some_path> при установке cookies задано как /temp, то ус-
тановленные cookies будут отсылаться при запросе документов,
включая пути /temp000 и /temp/[Link]. Если значение path не оп-
ределено, в качестве него принимается путь к ресурсу, при запросе
которого были установлены cookies;
secure — установка этого атрибута указывает, что cookies должны
быть защищенными, т. е. отсылаться на сервер только по протоколу
HTTPS (протокол HTTP, использующий SSL — Secure Socket Layer,
позволяющий пересылать шифрованные данные);
httponly — если этот атрибут указан, устанавливаемые cookies не
будут доступны клиентским скриптам (в некоторых случаях это при-
меняется из соображений безопасности).
Все перечисленные атрибуты не обязательны в поле заголовка ответа
сервера Set-Cookie:. Как уже упоминалось, простейший случай уста-
новки cookie — указание единственной пары <имя>=<значение>.
Cookie и JavaScript
Доступ к cookies, соответствующим документу, в JavaScript производит-
ся при помощи операций с единственным свойством объекта document:
[Link]
Спецификация DOM уровня 1 определяет атрибут cookie интерфейса
HTMLDocument, реализацией которого и является это свойство. Таким
образом, свойство [Link] поддерживается всеми современ-
ными браузерами. Хотя атрибут cookie интерфейса HTMLDocument имеет
тип DOMString и, соответственно, [Link] является строкой,
семантика работы с этим свойством сильно отличается от манипулиро-
вания обычным строковым объектом в JavaScript.
Установка, модификация, получение и удаление cookie
Установка cookie из сценария производится при помощи присваивания
свойству [Link] строкового значения, формат которого прак-
тически идентичен формату поля Set-Cookie: HTTP-заголовка, рас-
смотренного ранее:
<name>=<value>[; expires=<date>][; path=<some_path>]
[; domain=<domain_name>][; secure]
Как можно заметить, существуют всего два отличия формата этой стро-
ки от формата строки Set-Cookie: (в обоих случаях атрибуты имеют
одинаковый смысл):
Ãëàâà 7. Работа с cookie 151
пара <name>=<value> может быть только одна. Если в строке будет
задаваться несколько пар имен/значений cookie, все, кроме первой,
будут проигнорированы;
отсутствует атрибут httponly. Поскольку он указывает на недоступ-
ность устанавливаемого cookie для скрипта, то установка его скрип-
том бессмысленна.
Простой пример установки cookie может выглядеть так:
[Link] = "test=test_value; expires=Wed, 1 Mar 2006
00:00:00";
Далее будет показано, что это не простая операция присваивания.
Модификация существующего cookie может быть произведена установ-
кой cookie с тем же именем, но новым значением и атрибутами.
Получение установленных cookies сводится к получению значения свой-
ства [Link]. Оно содержит строку в виде последовательности
разделенных символами "; " пар "имя=значение" всех cookies, сопос-
тавленных документу. Примером такой строки может являться:
var1=0x5F564943; var2=USER0; temp=10
Удаление отдельного cookie производится путем (обратите особое вни-
мание) установки его значения с указанием времени истечения срока
действия раньше текущего момента времени. Физически (из файлов на
диске) cookies, скорее всего, будут удалены только при закрытии брау-
зера.
Свойство [Link] доступно во всех популярных браузерах,
начиная с ранних версий (Internet Explorer 3.02, Netscape 2.0,
Opera 3.0). А описанный метод установки и получения cookie работает
одинаково корректно как в старых, так и в новых версиях всех браузе-
ров, исключая Opera. Дело в том, что в Opera до версии 7.0 (включи-
тельно) скрипт не может установить cookie, если документ, к которому
он подключен или в который он внедрен, открыт локально (по прото-
колу [Link] Если же документ загружается, например, по протоколу
[Link] все работает как надо. Начиная с версии 7.50, подобные про-
блемы в Opera отсутствуют.
Для подкрепления практикой всего сказанного, рассмотрим код примера
examples\07\ex_7_01.htm, доступного на компакт-диске. В нем устанав-
ливаются cookies с именами cookie_1 и cookie_2, далее значение
cookie_2 модифицируется, а затем cookie_2 удаляется при помощи ука-
152 Часть II. Решение типовых задач программирования скриптов
зания значения expired соответствующего моменту времени на 1 год
раньше текущего момента. После каждого действия производится вывод
значения атрибута [Link].
[Link] = "cookie_1=val_1"; // установка cookie_1
[Link]([Link]);
[Link] = "cookie_2=val_2"; // установка cookie_2
[Link]([Link]);
[Link] = "cookie_2=val_2_modified"; // модификация
// cookie_2
[Link]([Link]);
// удаление cookie_2
var oDate = new Date();
[Link]([Link]() - 1);
[Link] = "cookie_2=; expires=" + [Link]();
[Link]([Link]);
Вывод скрипта таков:
cookie_1=val_1
cookie_1=val_1; cookie_2=val_2
cookie_1=val_1; cookie_2=val_2_modified
cookie_1=val_1
Теперь, на основе изложенного материала и результатов примера, сде-
лаем несколько выводов относительно важных аспектов использования
cookie средствами JavaScript.
Свойство [Link] не является обычной строкой — при
присваивании ему значения, содержащего данные cookie, происхо-
дит не копирование, а "аккумулирование" данных в виде пар
<имя>=<значение>. Строго говоря, это свойство не хранит данные, а
реализует интерфейс доступа к cookie, используя семантику работы с
объектом-строкой.
Все операции с cookie (установка, модификация, удаление) произво-
дятся при помощи присваивания свойству [Link] новых
значений.
Значение, возвращаемое свойством [Link], является стро-
кой и содержит только пары <имя>=<значение cookie>, но не содер-
жит атрибутов, указанных при их установке. Это означает, что, уста-
новив cookie, впоследствии мы не можем получить срок его дейст-
вия, домен, путь, узнать, является ли cookie защищенным.
Ãëàâà 7. Работа с cookie 153
Поскольку свойство [Link] содержит имена и значения
всех cookies документа, то для получения значения произвольного
cookie по его имени нам придется осуществить разбор этой строки.
При установке cookie разделительными символами в строке являют-
ся "; " (точка с запятой и пробел), поэтому их нельзя использовать в
значениях cookie. Кроме того, недопустимо применять символы та-
буляции, возврата каретки, перевода строки и др. Следовательно, для
того чтобы записывать в значения cookie произвольные строки сим-
волов, необходимо осуществлять их предварительное кодирование
(этот принцип будет показан в разд. "Библиотека функций работы
с cookie" далее в этой главе).
Проверка поддержки доступа к cookie
Для определения доступности скрипту операций работы к cookie в дан-
ный момент существует свойство логического типа cookieEnabled объ-
екта navigator:
if([Link])
// доступны операции установки/получения cookie
Свойство [Link] доступно во всех современных
браузерах. Однако если вы планируете сделать свой скрипт совмести-
мым со старыми браузерами, необходимо учесть возможность отсутствия
поддержки в них этого свойства. Если свойство [Link]
действительно отсутствует, то условие, приведенное выше, вернет
false, хотя операции доступа к cookie все-таки могут быть доступны.
Поэтому единственным надежным методом определения поддержки
cookie может являться попытка установки и получения "тестового" coo-
kie.
Cookie со стороны браузера
Используя cookie, необходимо четко осознавать их "непостоянную при-
роду" и, как следствие, возможность их применения для решения кон-
кретных задач. Прежде всего, сфера применения cookie определяется
тем, что их хранение и манипулирование ими (отсылка на сервер, пре-
доставление доступа к cookie клиентским скриптам) находится исклю-
чительно в ведении браузера. Браузер обеспечивает возможность хране-
ния, но не гарантирует абсолютную сохранность, или целостность coo-
kie.
Браузер является прикладной программой и находится полностью под
контролем пользователя. Многие браузеры позволяют устанавливать
154 Часть II. Решение типовых задач программирования скриптов
максимальное время хранения cookie, большинство браузеров преду-
сматривают возможность выборочного, и все — полного их удаления.
Поэтому, указывая время истечения срока действия cookies, вы лишь
определяете максимальный верхний предел времени, в течение которого
они могут быть сохранены на машине пользователя. Браузеры могут
иметь настройки, запрещающие прием cookie со всех или некоторых
сайтов.
Во время работы браузеры, для повышения быстродействия, в основ-
ном, хранят cookies в оперативной памяти. Однако при завершении
работы они выгружают эти данные на диск. Как правило, cookies сохра-
няются в текстовых файлах. Их формат и расположение зависят от опе-
рационной системы и конкретного браузера. Например, в Win-
dows 2000/XP вы можете обнаружить cookie браузера Mozilla в файле:
<системный диск>:\Documents and Settings\
<профиль пользователя>\Application Data\Mozilla\Profiles
\<имя профиля Mozilla>\<подкаталог>.slt\ [Link]
Internet Explorer хранит cookie в виде отдельных файлов в папке:
<системный диск>:\Documents and Settings\
<профиль пользователя>\Cookies\
Эти файлы можно легко редактировать в обычном текстовом редакторе,
произвольно меняя значения сохраненных cookies.
Помимо всего прочего, есть существенные ограничения на объем и чис-
ло cookies, устанавливаемые различными браузерами. Не стоит исполь-
зовать cookie длиной больше 2 Кбайт. Общее число cookies, установлен-
ных для одного домена не должно превышать 20, а общий их объем —
4 Кбайт.
Практическое применение cookie
Теперь, владея информацией о возможностях и принципах использова-
ния cookies в клиентских скриптах, рассмотрим несколько примеров их
практического применения. Мы начнем с разработки библиотеки функ-
ций, предоставляющей API для удобного манипулирования cookies. За-
тем мы создадим документ, использующий эту библиотеку для "запоми-
нания" информации, введенной пользователем, а также информации о
количестве и времени просмотра документа.
Ãëàâà 7. Работа с cookie 155
Библиотека функций работы с cookie
Итак, для начала определим круг задач, которые должна решать библио-
тека. Поскольку основными операциями работы с cookie являются уста-
новка и получение их значений, в библиотеке должны присутствовать
соответствующие функции: SetCookie и GetCookie. Удаление конкрет-
ного cookie будет производиться при помощи функции DeleteCookie.
Удобной может оказаться операция удаления всех cookies, связан-
ных с документом, поэтому мы включим в библиотеку функцию
DeleteAllCookie, производящую данную операцию. Как указывалось в
предыдущем разделе, в случае отсутствия у объекта navigator свойства
cookieEnabled определение поддержки cookie браузером можно произ-
вести только при помощи установки/чтения тестового cookie, поэтому в
библиотеку добавлена функция IsCookieEnabled, реализующая комби-
нированный способ тестирования поддержки cookie. Таким образом,
библиотека будет включать в себя пять функций: SetCookie, GetCookie,
DeleteCookie, DeleteAllCookie и IsCookieEnabled. Теперь рассмотрим
реализацию каждой из функций библиотеки (вся библиотека содержится
в файле examples\Lib\[Link] на компакт-диске).
Функция SetCookie производит установку cookie с указанным именем,
значением и набором атрибутов. Она определяется так:
function SetCookie(strName, oValue)
Для обеспечения гибкости функция принимает переменное число пара-
метров, и ее прототип может быть записан следующим образом:
SetCookie(strName, oValue [, oDateExpires [, strDomain
[, strPath [, bSecure]]]])
Аргументы имеют следующие значения:
strName — имя устанавливаемого cookie (строка);
oValue — значение устанавливаемого cookie, может быть значением
простого типа либо ссылкой на объект — в любом случае это значе-
ние будет преобразовано в строку при помощи метода toString;
oDateExpires — дата окончания срока действия cookie, может зада-
ваться в виде ссылки на объект Date, числа (миллисекундного сме-
щения) либо строки, пригодной для передачи методу [Link]();
strDomain — значение атрибута domain устанавливаемого cookie
(строка);
strPath — значение атрибута path устанавливаемого cookie (строка);
156 Часть II. Решение типовых задач программирования скриптов
bSecure — значение параметра интерпретируется как логическое.
В случае, если оно преобразуется в true, для cookie устанавливается
атрибут secure.
Таким образом, для создания cookie с нужным набором атрибутов надо
лишь передать SetCookie дополнительные параметры. Теперь рассмот-
рим реализацию функции. Поскольку число ее параметров переменно
(обязательны только strName и oValue), мы используем массив
arguments[] для доступа к их значениям.
function SetCookie(strName, oValue)
{
var argv = [Link];
var argc = [Link];
...
Переменная argv содержит ссылку на массив аргументов, а argc — их
количество. Далее параметр с индексом 2 — oDateExpires (если он ус-
тановлен и не равен null), преобразуется в объект Date (код преобразо-
вания см. в файле [Link]), ссылка на который сохраняется в перемен-
ной oDate. Затем, собственно, производится сама установка cookie:
[Link] =
strName + "=" + escape([Link]()) +
(oDate ? "; expires=" + [Link]() : "") +
(((argc > 3) && (argv[3])) ? "; domain=" + argv[3] : "") +
(((argc > 4) && (argv[4])) ? "; path=" + argv[4] : "") +
((argc > 5) ? (argv[5] ? "; secure" : "") : "");
Рассматривая этот код, необходимо обратить внимание на два момента.
Установка атрибутов cookie зависит не только от того, был ли пере-
дан в функцию соответствующий параметр (анализируется значение
переменной argc), но и от того, конвертируется ли значение этого
параметра в логическое значение true (анализируется сам параметр).
Это делает возможным передачу в функцию SetCookie в качестве
параметров oDateExpires, strDomain, strPath значений null, false,
либо пустой строки для того, чтобы избежать установки соответст-
вующего параметру атрибута.
В качестве значения cookie устанавливается результат преобразова-
ния параметра oValue в строку, обработанный функцией escape. Это
делается для того, чтобы закодировать недопустимые для значения
cookie символы (пробелы, точки с запятой и т. д.) в виде escape-
последовательностей (вида %XX или %uXXXX, где XX и XXXX — код сим-
Ãëàâà 7. Работа с cookie 157
вола) и избежать ошибок при установке cookie. В функции GetCookie
будет производиться обратное преобразование.
Как пример использования функции SetCookie, рассмотрим установку
cookie с именем "USER", значением "Basil B. Poupkine" и атрибутом
path, равным "/":
SetCookie("USER", "Basil B. Poupkine", null, null, "/");
Обратите внимание, что в качестве значений параметров oDateExpires и
strDomain мы просто передали null — в результате cookie будет уста-
новлен с единственным атрибутом path.
Функция GetCookie определена как:
function GetCookie(strName)
Она возвращает строковое значение cookie, имя которого передано ей
в качестве параметра strName, либо null, если cookie с таким именем не
сопоставлен документу. Принцип работы функции прост. Он состоит
в линейном поиске пары <имя>=<значение> в строке strCookie (ссылка
на [Link]):
var i = 0, j;
var strFind = strName + "=";
var strCookie = [Link];
do
{
j = i + [Link];
if([Link](i, j) == strFind)
{
var nEnd = [Link](";", j);
if(nEnd < 0)
nEnd = [Link];
return unescape([Link](j, nEnd));
}
}
while(i = [Link](" ", j) + 1);
В цикле do...while производится сравнение фрагмента строки
strCookie, являющегося именем (со следующим за ним знаком =) оче-
редного cookie из [Link] со строкой strFind, сформированной
из переданного через параметр strName значения и знака равенства.
В случае успешности сравнения (нужный cookie найден) производится
поиск позиции окончания подстроки в значении cookie (ищется ;, и по-
158 Часть II. Решение типовых задач программирования скриптов
зиция сохраняется в переменной nEnd), а затем значение искомого coo-
kie извлекается, декодируется функцией unescape и возвращается как
результат выполнения функции. Если сравнение неудачно, в инструкции
while ищется признак конца пары <имя>=<значение> (ищется пробел).
Если он не найден — в строке больше нет пар. Если найден — следую-
щая за ним позиция будет началом имени очередного cookie.
Функция DeleteCookie осуществляет удаление cookie с именем, пере-
данным ей в качестве параметра. Ее код очень мал, поэтому привожу его
полностью:
function DeleteCookie(strName)
{
[Link] = strName + "=0; expires=" +
(new Date(0)).toGMTString();
}
Функция устанавливает cookie с именем strName (используется значе-
ние 0, но может быть и любое другое, это не важно) и атрибутом
expired, соответствующим времени 00:00:00 01.01.1970 (возвращается
методом toGMTString создаваемого тут же объекта Date), что приво-
дит к удалению этого cookie. Мы не используем здесь вызов SetCookie
(функция сама формирует строку для присваивания свойству
[Link]) по соображениям быстродействия.
Функция DeleteAllCookie предназначена для удаления всех cookies,
связанных с документом. Для удаления cookie необходимо знать его
имя. Соответственно, для удаления всех cookies, установленных для до-
кумента, необходимо получить список их имен. Такой список получает-
ся путем разбора строки [Link] практически аналогично тому,
как это делается в функции GetCookie (см. код DeleteAllCookie). В цик-
ле производится поиск знака =, являющегося признаком окончания име-
ни cookie, а затем пробела, являющегося признаком окончания пары
<имя>=<значение>. Имена найденных cookies помещаются в массив arr,
а потом производится их последовательное удаление функцией
DeleteCookie:
for(i = 0; i < [Link]; i++)
DeleteCookie(arr[i]);
Последней функцией библиотеки, которую нам осталось рассмотреть,
является IsCookieEnabled. Как уже говорилось, факт возможности рабо-
ты с cookie клиентским скриптом можно выяснить, проанализировав
свойство [Link]. Но если браузер не поддерживает
Ãëàâà 7. Работа с cookie 159
это свойство, можно попытаться установить и прочитать "тестовый"
cookie. Однако, хотя этот метод и универсален, он работает несравнимо
медленнее простого получения значения свойства. Поэтому функция
IsCookieEnabled анализирует наличие у объекта navigator свойства
cookieEnabled и, когда оно присутствует, возвращает его значение:
if(typeof([Link]) != "undefined")
return [Link];
Иначе применяется способ с установкой cookie:
var _c = "__V_TEST_COOKIE_NAME_";
SetCookie(_c, "1");
if(GetCookie(_c) != null)
{
DeleteCookie(_c);
return true;
}
Вместо имени тестового cookie __V_TEST_COOKIE_NAME_ вы можете ис-
пользовать любое другое. Главное, чтобы оно было уникально в рамках
того проекта, где используется библиотека [Link].
Использование библиотеки работы с cookie:
скрипт, запоминающий имя пользователя,
количество и дату посещений страницы
Для испытания библиотеки [Link] в работе надо написать скрипт, ко-
торый бы сохранял в cookie какие-либо данные, а потом отображал их в
браузере. Желательно, чтобы они были понятными и осмысленными.
В качестве таких данных я выбрал дату последнего посещения страни-
цы, количество посещений и имя пользователя, которое он должен будет
ввести.
Скрипт (см. пример examples\07\ex_7_02.htm на компакт-диске) генери-
рует на странице "шапку" в виде двух блоков. Первый из них содержит
информацию, полученную из cookie, а второй — либо форму для ввода
имени пользователя (если оно не содержалось в cookie), либо ссылку для
"сброса" имени (удаления соответствующего cookie). На рис. 7.1 приве-
ден вид окна браузера с загруженным документом ex_7_02.htm в со-
стоянии, когда имя пользователя не определено.
На рис. 7.2 можно видеть тот же документ после ввода имени пользова-
теля.
160 Часть II. Решение типовых задач программирования скриптов
Рис. 7.1. Работа скрипта, когда имя пользователя не определено
Рис. 7.2. Работа скрипта после ввода имени пользователя
Теперь рассмотрим, как действует этот скрипт. Для его работы необхо-
димы библиотеки [Link] и [Link]. В документе ex_7_02.htm они
подключаются непосредственно перед телом основного скрипта.
Применительно к нашей задаче (однотипная работа с группой cookie),
удобным представляется применение объектно-ориентированного под-
хода. Я решил инкапсулировать все данные, касающиеся cookies, а так-
же методы их чтения и сохранения в объекте CPreferences. Функция-
конструктор CPreferences реализована в самом начале скрипта. Итак,
объект типа CPreferences инкапсулирует:
свойство — массив имен cookie aCookies;
свойства m_strUser (строка), m_nVisit (целое число), m_odtVisit
(объект типа CVDate), содержащие, соответственно, имя пользовате-
ля, количество посещений страницы и дату последнего визита, в ко-
торые будут помещаться значения, прочитанные из cookie, и значе-
ния которых будут в cookie записываться;
метод Init, осуществляющий инициализацию переменных m_strUser,
m_nVisit, m_odtVisit значениями null, 0 и null соответственно;
Ãëàâà 7. Работа с cookie 161
метод Load, производящий получение значений cookie с именами из
массива aCookies и, в соответствии с этими значениями, заносящий
данные в переменные m_strUser, m_nVisit, m_odtVisit;
метод Save, сохраняющий данные переменных m_strUser, m_nVisit,
m_odtVisit в cookie со сроком действия 100 лет.
Функция-конструктор CPreferences вызывает метод Load (который,
в свою очередь, вызывает метод Init), поэтому созданный оператором
new объект типа CPreferences будет иметь свойства m_strUser,
m_nVisit, m_odtVisit, содержащие данные, прочитанные из cookie (либо
значения по умолчанию, присвоенные методом Init).
В скрипте создается один объект типа CPreferences, и на основе значе-
ний его свойств генерируется первый блок "шапки" страницы. Тут же
(сразу после вывода информации) эти значения изменяются — увеличи-
вается счетчик посещений, обновляется время последнего визита (созда-
ется новый объект CVDate).
var oPreferences = new CPreferences();
with(document)
{
oPreferences.m_nVisit++; // увеличиваем счетчик посещений
write("<div class='block'>");
write("Здравствуйте");
if(oPreferences.m_strUser != null)
write(", " + oPreferences.m_strUser);
...
Затем обновленная информация сохраняется, и генерируется второй
блок "шапки". Здесь анализируется свойство m_strUser объекта
oPreferences и, если оно равно null (имя пользователя не было получе-
но из cookie), в блоке генерируется форма для ввода имени, иначе —
ссылка для "сброса" информации об имени:
[Link]();
with(document)
{
write("<div class='block'>");
if(oPreferences.m_strUser == null)
{
write("<FORM id='user'
onsubmit='return OnUserLogin(this);'" +
"action='" + [Link] + "'>");
...
}
162 Часть II. Решение типовых задач программирования скриптов
else
{
write("<a href='" + [Link] + "' " +
"onclick='return OnUserLogout();'>Сбросить данные об
имени пользователя (войти анонимно)</a>");
...
Действия по "запоминанию" имени пользователя производятся в функ-
ции OnUserLogin, вызываемой из обработчика onsubmit формы (о рабо-
те с формами см. в главе 11). В ней проверяется, действительно ли
пользователь ввел имя в поле формы (если нет — выводится соответст-
вующее сообщение, и отправка формы отменяется), и если имя введе-
но — оно заносится в свойство oPreferences.m_strUser, а затем вызы-
вается метод [Link](). Для сброса данных об имени поль-
зователя служит функция OnUserLogout, вызываемая из обработчика
onclick ссылки. Она запрашивает подтверждение на выполнение этого
действия с помощью метода confirm и в случае положительного ответа
пользователя присваивает свойству oPreferences.m_strUser значение
null и вызывает метод [Link](), сохраняя все значения в
cookie.
Работа скрипта выглядит так, как будто бы пользователь авторизуется
на сайте. Хотя, конечно же, без поддержки со стороны сервера автори-
зацию осуществлять невозможно. Однако такой скрипт вполне можно
использовать на своей домашней страничке (возможно, вашим посети-
телям доставит удовольствие вежливое личное обращение). Поскольку
работа с cookie ведется только средствами клиентского скрипта, для
размещения такой страницы подойдет любой хостинг. Ну, и конечно же
подобный скрипт является простым и наглядным примером использова-
ния cookie вообще.
ГЛАВА 8
Регулярные выражения
В этой главе мы рассмотрим реализацию и работу с регулярными выра-
жениями в JavaScript, а также способы и примеры их использования при
создании клиентских сценариев.
Основы регулярных выражений
Регулярное выражение представляет собой строку символов, содержа-
щую определенные синтаксические конструкции (метасимволы), кото-
рая может интерпретироваться различными программными средствами
в качестве шаблона при выполнении операций обработки текста (на-
пример, поиске фрагментов текста или их замене). Из языков програм-
мирования можно выделить те, в которых поддержка регулярных выра-
жений осуществляется "встроенными" средствами (обычно это также
означает возможность литеральной записи регулярных выражений), и
те, в которых такая поддержка не осуществляется (для обработки текста
на основе регулярных выражений в таких языках необходимо использо-
вать библиотеки функций или классов). К первой группе относятся Perl,
JavaScript, ко второй — C, C++, PHP.
Создание регулярных выражений
Регулярное выражение состоит из шаблона и флагов. При этом шаблон
является основой регулярного выражения, определяющей критерии по-
иска, а флаги задают параметры сопоставления шаблона. Можно ска-
зать, что задача создания регулярного выражения сводится к написанию
его шаблона.
164 Часть II. Решение типовых задач программирования скриптов
Шаблоны регулярных выражений
Шаблон регулярного выражения состоит из последовательностей обыч-
ных (литеральных) и специальных символов (метасимволов). При поис-
ке на основе регулярного выражения последовательности литеральных
символов сопоставляются идентичным последовательностям в строке
поиска, обеспечивая прямые совпадения этой части шаблона. Шаблоны,
составленные только из литеральных символов, часто называют про-
стыми (например, /abc/, /code/, /JavaScript/). Так, совпадение про-
стого шаблона /code/ произойдет при поиске в строках "In code we
trust" и "I am coder" с подстрокой "code".
Специальные символы позволяют вводить в шаблон регулярного выра-
жения конструкции, сопоставляющиеся: с множеством возможных по-
следовательностей символов, с последовательностями, соответствую-
щими определенным условиям, c управляющими символами, с симво-
лами Unicode. Они также позволяют определять "подшаблоны" и
множества символов. Например, шаблон /a\d+b/ в строке "aaa a456b cc"
сопоставится с подстрокой "a456b", а в строке "e f g a987654b r t y" —
с подстрокой "a987654b". В этом шаблоне a (в начале) и b (в конце) яв-
ляются литеральными символами, а \d и + — специальными. Последо-
вательность \d соответствует любому одиночному цифровому символу,
а + указывает, что предшествующий символ (любая цифра) должен при-
сутствовать 1 или более раз. Таким образом, шаблон /a\d+b/ соответст-
вует любой последовательности символов, начинающийся с a, заканчи-
вающийся b, между которыми находятся одна или более цифр.
В табл. 8.1 приведено описание специальных символов, которые могут
использоваться в регулярных выражениях JavaScript.
Таблица 8.1. Специальные символы регулярных выражений Ja-
vaScript
Символ Описание
\ Служит для обозначения и экранирования специальных сим-
волов.
Некоторые специальные символы представляются как
\<символ> (например, \b, \d, \s). Таким образом, для опреде-
ленных символов стоящий перед ними обратный слэш указывает
на то, что символ должен интерпретироваться как специальный.
Некоторые специальные символы представляются одним сим-
волом (например, *, +). Для того чтобы ввести такие символы в
шаблон в качестве литеральных, необходимо указать перед
Ãëàâà 8. Регулярные выражения 165
Таблица 8.1 (продолжение)
Символ Описание
ними обратный слэш. Например, символ . (точка) — специаль-
ный. Он сопоставляется с любым одиночным символом, исклю-
чая символ новой строки. Таким образом, шаблон /a./ будет
сопоставляться подстрокам: "aa", "ab", "a7" и т. д. Но если мы
хотим, чтобы шаблон сопоставлялся только с последователь-
ностью "a.", необходимо записать его так: /a\./
^ Сопоставляется с началом строки. Началом строки считается
начало строки поиска, а также, если для регулярного выражения
установлен флаг многострочного поиска, позиция после симво-
лов разрыва строк.
Например, шаблон /^abc/ сопоставится с последователь-
ностью "abc" в строке поиска "abcdefg", но не сопоставится этой
же последовательности в строке "ddabc"
$ Сопоставляется с концом строки. Концом строки считается ко-
нец строки поиска, а также, если для регулярного выражения
установлен флаг многострочного поиска, позиции непосредст-
венно перед символами разрыва строк.
Например, шаблон /^abc/ сопоставится с последователь-
ностью "abc" в строке поиска "ddabc", но не сопоставится этой
же последовательности в строке "aa abc d"
* Квантификатор, указывающий, что данная часть шаблона со-
поставляется с последовательностью, образованной повторе-
нием предшествующего символа 0 или более раз (такая после-
довательность может не содержать ни одного символа). По
умолчанию является "жадным", т. е. ищется максимальное чис-
ло совпадений.
Например, шаблон /abc*d/ может сопоставляться с последова-
тельностями: "abd" (символ "c" повторяется 0 раз), "abcd" или
"abcccccccd"
+ Квантификатор, указывающий, что данная часть шаблона со-
поставляется с последовательностью, образованной повторе-
нием предшествующего символа 1 или более раз. По умолча-
нию является "жадным", т. е. ищется максимальное число сов-
падений. Это также эквивалентно {1,}.
Например, шаблон /abc+d/ сопоставляется с "abcd",
"abcccccccd", но не сопоставляется с "abd"
? Квантификатор, указывающий, что данная часть шаблона со-
поставляется с последовательностью, образованной повторе-
нием предшествующего символа 0 или 1 раз. По умолчанию
166 Часть II. Решение типовых задач программирования скриптов
Таблица 8.1 (продолжение)
Символ Описание
является "жадным", т. е. ищется максимальное число совпаде-
ний. Может также употребляться сразу же после квантификато-
ров *, +, ?, {}, делая их нежадными (ищется минимальное чис-
ло совпадений).
Например, шаблон /abc?d/ сопоставляется с "abd", "abcd", но
не сопоставляется с "abcccd"
. Сопоставляется с любым одиночным символом, исключая сим-
вол новой строки.
Например, шаблон /ab.c/ будет сопоставляться последова-
тельностям: "abDc", "ab7c", "ab=c"
(x) В данном шаблоне x также является шаблоном. Шаблон (x)
сопоставляется с теми же последовательностями символов, с
которыми сопоставляется x, но при этом происходит "запомина-
ние" совпавшей с шаблоном строки в результирующем массиве.
Такую конструкцию называют захватывающими скобками.
Например, при сопоставлении шаблона /a(b+)c/ со строкой
"dd eeabbbc dd" результирующий массив будет содержать зна-
чение "bbb"
(?:x) В данном шаблоне x также является шаблоном. Шаблон (?:x)
сопоставляется с теми же последовательностями символов, с
которыми сопоставляется x, и при этом не происходит "запоми-
нания" совпавшей с шаблоном строки в результирующем мас-
сиве. Такую конструкцию называют незахватывающими скоб-
ками
x(?=y) В данном шаблоне x и y также являются шаблонами. Шаблон
x(?=y) сопоставляется с теми же последовательностями сим-
волов, с которыми сопоставляется x, но только в том случае,
если непосредственно за подстрокой, сопоставляемой с x, сле-
дует подстрока, сопоставляемая с y. Данная конструкция явля-
ется позитивным вперед смотрящим (lookahead) утверждени-
ем. Результатом операции сопоставления является строка, сов-
павшая с шаблоном x.
Например, шаблон /a+(?=\d+)/ (означает: одна или более букв
"a", после которых следует одна или более цифра) сопоставим
последовательностям: "a123", "aaa6", но не сопоставим с
"aaabb" или "abc642". А результатом сопоставления этого шаб-
лона со строкой "ddd aa600 bbb" будет являться последователь-
ность "aa"
Ãëàâà 8. Регулярные выражения 167
Таблица 8.1 (продолжение)
Символ Описание
x(?!y) В данном шаблоне x и y также являются шаблонами. Шаблон
x(?!y) сопоставляется с теми же последовательностями сим-
волов, с которыми сопоставляется x, но только в том случае,
если непосредственно за подстрокой, сопоставляемой с x, не
следует подстрока, сопоставляемая с y. Данная конструкция
является негативным вперед смотрящим (lookahead) утвер-
ждением. Результатом операции сопоставления является стро-
ка, совпавшая с шаблоном x.
Например, шаблон /aaa(?!\d+)/ (означает: три буквы "a", по-
сле которых не следует ни одна цифра) сопоставляется после-
довательности "aaa" в строках "aaaa", "aaabc", но не сопостав-
ляется в строке "aa aaa7bb cc"
x|y Символ | позволяет задать несколько альтернативных шабло-
нов для сопоставления. Иными словами, шаблон x|y сопостав-
ляется последовательности, которой сопоставляется шаблон x
или шаблон y. Символом | можно объединять любое количест-
во шаблонов.
Например, шаблон /aaa|\d+|bb/ будет сопоставляться с по-
следовательностями "aaa", "bb" и любыми последовательностя-
ми цифр
{n} Конструкция, указывающая, что данная часть шаблона должна
сопоставляться с последовательностью, образованной повторе-
нием предшествующего символа ровно n раз (где n — положи-
тельное целое число).
Например, шаблон /a{3}b/ эквивалентен шаблону /aaab/, а
шаблон /\d{3}/ означает: любые три цифры, и сопоставляется
с "123" в строке "aa123bb" или с "111" в строке "ddd 111234690ght ty"
{n,} Конструкция, указывающая, что данная часть шаблона должна
сопоставляться с последовательностью, образованной повторе-
нием предшествующего символа n или более раз (где n — по-
ложительное целое число).
Например, шаблон /\d{3,}/ означает: любые три или более
цифр, и сопоставляется с "123" в строке "aa123bb" и с
"111234690" в строке "ddd 111234690ght ty"
{n,m} Конструкция, указывающая, что данная часть шаблона должна
сопоставляться с последовательностью, образованной повторе-
нием предшествующего символа n и более, но меньшее или
равное m раз (где n и m — положительные целые числа).
Например, шаблон /\d{1,3}/ означает: любые цифры в коли-
честве от одной до трех, и сопоставляется с "12" в строке
"aa12bb" и с "234" в строке "ddd 234690ght ty"
168 Часть II. Решение типовых задач программирования скриптов
Таблица 8.1 (продолжение)
Символ Описание
[xyz] Конструкция, задающая множество символов. Подобный шаб-
лон сопоставляется с любым одиночным символом, принадле-
жащим множеству, определенному в скобках []. Множество
задается перечислением либо указанием диапазона.
Например, шаблон /[abcd]/ эквивалентен шаблону /[a-d]/ и
сопоставляется с любым из символов "a", "b", "c" или "d"
[^xyz] Конструкция, задающая множество символов. Подобный шаб-
лон сопоставляется с любым одиночным символом, не принад-
лежащим множеству, определенному в скобках []. Множество
задается перечислением либо указанием диапазона.
Например, шаблон /[^abcd]/ эквивалентен шаблону /[^a-d]/
и сопоставляется с символами "e", "f", "Z" или "+", но не сопос-
тавляется с "a", "b", "c" и "d"
[\b] Сопоставляется с одиночным символом backspace (это не то же
самое, что и \b)
\b Сопоставляется с границей слова, т. е. позицией между послед-
ним символом слова и символом-разделителем (пробел, пере-
вод строки).
Например, шаблон /cd\b/ сопоставляется с "cd" в строке
"abcd efg"
\B Сопоставляется с неграницей слова.
Например, шаблон /b\B/ сопоставится с "b" в строке "abc", но
не в строке "ab c"
\cx Сопоставляется с управляющим символом x. Значение x долж-
но находиться в диапазоне a—z или A—Z, иначе предполагает-
ся, что \c — литеральный символ.
Например, /\cM/ сопоставляется с символом, генерируемым
нажатием комбинации клавиш <Ctrl>+<M> (возврат каретки)
\d Сопоставляется цифровому символу. Эквивалент: [0-9]
\D Сопоставляется не цифровому символу. Эквивалент: [^0-9]
\f Сопоставляется символу погона страницы (form-feed). Эквива-
лент: \x0c и \cL
\n Сопоставляется символу перевода строки (line-feed). Эквива-
лент: \x0a и \cJ
\r Сопоставляется символу возврата каретки (carriage return). Эк-
вивалент: \x0d и \cM
Ãëàâà 8. Регулярные выражения 169
Таблица 8.1 (окончание)
Символ Описание
\s Сопоставляется одиночному пробельному символу, включая
пробел, символ табуляции, прогон страницы, перевод строки
и т. д. Эквивалент: [ \f\n\r\t\v\u00A0\u2028\u2029].
Например, /a\sb/ сопоставится с подстрокой "a b" в строке
"ddda bccc"
\S Сопоставляется с любым одиночным непробельным символом.
Эквивалент: [^\f\n\r\t\v\u00A0\u2028\u2029]
\t Сопоставляется с символом табуляции. Эквивалент: \x09 и \cI
\v Сопоставляется с символом вертикальной табуляции. Эквива-
лент: \x0b и \cK
\w Сопоставляется с любым алфавитным символом, включая сим-
вол подчеркивания (underscore). Эквивалент: [A-Za-z0-9_]
\W Сопоставляется с любым неалфавитным символом, включая
символ подчеркивания (underscore). Эквивалент: [^A-Za-z0-9_]
\0 Сопоставляется с символом с кодом 0 (NUL)
\xhh Сопоставляется с символом, код которого равен hh (где hh —
двухсимвольное шестнадцатеричное число). Например, \x41
сопоставляется с символом "A"
\uhhhh Сопоставляется с Unicode-символом, код которого равен hhhh
(где hhhh — четырехсимвольное шестнадцатеричное число).
Например, \u00A9 сопоставляется с символом авторского права
\n Сопоставляется с n-й по счету захваченной последователь-
ностью символов (см. "захватывающие скобки" ранее), либо
одиночным символом.
Если до данного фрагмента шаблона захвачено n или более
последовательностей, то фрагмент шаблона сопоставляется
последовательности с номером n. Например, шаблон /(.)\1/
сопоставляется двум идущим подряд одинаковым символам,
а шаблон /(.)(.)\2\1/ — таким последовательностям, как
"abba", "bccb", "azza" и т. д.
Если до данного фрагмента шаблона не захвачено n последова-
тельностей, то шаблон сопоставляется символу с кодом, n (n
трактуется как восьмеричное число). Например, шаблон
/a(.)\40b/ сопоставляется последовательностям "az b" или
"a+ b", т. к. \40 в данном случае соответствует пробелу (код 32
в десятичной системе счисления)
170 Часть II. Решение типовых задач программирования скриптов
В общем, принципы сопоставления шаблонов регулярных выражений
можно выразить в нескольких тезисах, описывающих случаи совпадения
отдельных частей шаблона с элементами строки, с которой ведется со-
поставление:
любой обычный символ (не метасимвол) соответствует самому себе;
строка обычных символов соответствует самой себе;
множество (класс) символов, задаваемый при помощи квадратных
скобок [], соответствует одному символу — любому из указанного
множества либо любому вне указанного множества (если первый
символ в скобках — ^);
метасимвол соответствует одному или множеству символов (см.
табл. 8.1);
для отмены действия метасимвола нужно поставить перед ним об-
ратный слэш \;
внутри шаблона можно задавать "подшаблоны" (при помощи круг-
лых скобок), а затем ссылаться на результаты их сопоставления по
номеру подшаблона (конструкция \n, где n — номер). Подшаблон
рассматривается как часть шаблона;
внутри шаблона можно задавать альтернативные последовательности
подшаблонов (разделяя их символом |). Альтернативная последова-
тельность будет соответствовать одному из ее подшаблонов — тому,
который сопоставился в каждом конкретном случае.
Усвоив несколько этих правил, можно составлять шаблоны регулярных
выражений любой сложности.
Флаги регулярных выражений
Кроме шаблона, в состав регулярного выражения может входить набор
флагов, задающих параметры поиска на основе этого выражения. Всего
возможны три флага:
g — флаг глобального поиска. По умолчанию поиск в строке преры-
вается при первом удачном сопоставлении шаблона регулярного вы-
ражения с подстрокой. Флаг g позволяет производить поиск по всей
строке и получать все результаты сопоставлений шаблона;
i — флаг поиска без учета регистра;
m — флаг многострочного поиска. При указании этого флага строка,
содержащая символы перевода строки, рассматривается как несколь-
Ãëàâà 8. Регулярные выражения 171
ко строк, и поиск ведется в каждой такой строке. При этом символы
^ и $ сопоставляются началу и концу каждой такой строки (без ука-
зания флага m они сопоставляются только началу и концу всей стро-
ки, в которой ведется поиск).
Эти флаги могут использоваться в любой последовательности и в любых
комбинациях. Например, следующие регулярные выражения абсолютно
идентичны:
/abc\d+/gmi, /abc\d+/img, /abc\d+/mig
Работа с регулярными выражениями
в JavaScript
Поддержка регулярных выражений в JavaScript осуществляется при по-
мощи объекта ядра языка RegExp (специфицирован в ECMAScript 3, дос-
тупен в JavaScript 1.2). Обработка данных на основе регулярных выра-
жений может производиться методами exec и test объектов типа
RegExp, а также методами match, replace, search и split объектов
String (они используют существующий экземпляр объекта RegExp,
ссылка на который должна передаваться в эти методы в качестве пара-
метра). Далее мы рассмотрим объект RegExp и методы объекта String,
работающие с регулярными выражениями.
Объект RegExp
Создание этого объекта может быть произведено при помощи функции-
конструктора RegExp() либо при помощи записи регулярного выраже-
ния в литеральной нотации. В общем виде оба варианта выглядят сле-
дующим образом:
var oRegExp = /pattern/[flags];
var oRegExp = new RegExp("pattern", ["flags"]);
Здесь pattern является шаблоном, а flags — набором флагов регуляр-
ного выражения. То есть, конструирование объекта RegExp, используя
литерал регулярного выражения, может выглядеть так:
var oRegExp1 = /abc(d+)ef/; // регулярное выражение
// без флагов
var oRegExp2 = /abc(d+)ef/mg; // регулярное выражение
// с флагами
172 Часть II. Решение типовых задач программирования скриптов
А при помощи конструктора так:
var oRegExp1 = new RegExp("abc(d+)ef"); // выражение
// без флагов
var oRegExp2 = new RegExp("abc(d+)ef", "mg"); // выражение
// с флагами
Конкретный способ создания объекта RegExp желательно выбирать на
основе информации о назначении создаваемого объекта. Когда регуляр-
ное выражение остается постоянным на этапе времени исполнения, це-
лесообразно применять литеральную форму записи. Это, в общем слу-
чае, ведет к повышению производительности. Конструктор же создает
объекты типа RegExp динамически, что позволяет использовать его для
компиляции регулярных выражений с заранее неизвестным шаблоном и
флагами (например, регулярные выражения, введенные пользователем).
Методы объекта RegExp
Объект RegExp имеет метод compile, позволяющий изменять содержа-
щееся в нем регулярное выражение во время исполнения скрипта. Ме-
тод compile принимает параметры, аналогичные параметрам конструк-
тора RegExp():
var oRegExp = RegExp("\\d+", "g"); // создаем объект
// типа RegExp
[Link]("abc\\d+", "im"); // изменяем его регулярное
// выражение
Метод exec объекта RegExp выполняет поиск совпадения в строке. Он
возвращает массив информации, содержащий строки, сопоставленные
шаблону и всем его подшаблонам. Метод exec ищет только первое сов-
падение, флаг g регулярного выражения (даже если и был установлен)
игнорируется. Возвращаемый методом exec массив имеет два дополни-
тельных свойства: index, содержащее позицию символа, начиная с ко-
торого произошло совпадение с шаблоном, и input, содержащее всю
строку, в которой выполнялся поиск. Таким образом, следующий фраг-
мент скрипта:
[Link](/abc(\d+(\d))/.exec("ddd abc903 hhh"));
сгенерирует такой вывод (выведен массив из трех элементов, возвра-
щенный exec):
abc903,903,3
Ãëàâà 8. Регулярные выражения 173
Здесь первый элемент (abc903) является результатом сопоставления все-
го шаблона, второй (903) — результатом сопоставления подшаблона
(\d+(\d)), а третий (3) — результат сопоставления вложенного подшаб-
лона (\d). В случае успешного поиска этот метод также обновляет
свойства объекта регулярного выражения и свойства предопределенного
объекта регулярного выражения RegExp (см. разд. "Свойства объекта
RegExp" далее в этой главе). Если поиск неудачен, метод exec возвра-
щает null.
Метод test также ищет совпадение шаблона в строке поиска, однако не
возвращает данных сопоставления. Если поиск успешен, test возвраща-
ет true, если неудачен — false. Метод test логично использовать в
случае, если необходимо просто проверить, существует ли в строке ком-
бинация символов, сопоставляющаяся с шаблоном регулярного выраже-
ния, т. к. он работает быстрее exec. Например, при использовании в
предыдущем примере метода test вместо exec:
[Link](/abc(\d+(\d))/.test("ddd abc903 hhh"));
будет выведено просто true.
Свойства объекта RegExp
Начиная этот раздел, сразу же следует подчеркнуть один важный мо-
мент. Говоря о свойствах объекта RegExp, нужно отдельно рассматри-
вать набор свойств, которыми обладает каждый конкретный объект типа
RegExp, созданный в процессе исполнения скрипта, и свойства глобаль-
ного объекта RegExp (единственного объекта, всегда доступного через
идентификатор RegExp в глобальной области видимости).
Свойства глобального объекта RegExp изменяются при выполнении лю-
бых операций с регулярными выражениями (вызов методов объектов
типа RegExp и объектов типа String) и содержат значения, полученные
при выполнении последней операции. Эти свойства описаны в табл. 8.2.
Таблица 8.2. Свойства глобального объекта RegExp
Свойство Описание
$1—$9 Эти свойства содержат результаты сопоставления (по
порядку) первых девяти подшаблонов основного шаб-
лона
$& Аналогично свойству [Link]
$' Аналогично свойству [Link]
174 Часть II. Решение типовых задач программирования скриптов
Таблица 8.2 (окончание)
Свойство Описание
$* Аналогично свойству [Link]
$+ Аналогично свойству [Link]
$_ Аналогично свойству [Link]
$` Аналогично свойству [Link]
index Содержит позицию, начиная с которой произошло сов-
падение с шаблоном
input Содержит всю строку, в которой выполнялся поиск.
В Mozilla также если присвоить значение этому свойст-
ву, оно будет использоваться как строка поиска при
вызове методов exec и test без параметров
lastMatch Содержит результат сопоставления всего шаблона
lastParen Содержит результат сопоставления последнего под-
шаблона
leftContext Содержит текст из строки поиска от начала до позиции
сопоставления шаблона
rightContext Содержит текст из строки поиска от конца совпавшей
части до конца строки
multiline Устанавливая значение этого свойства логического
типа, можно "включать" и "выключать" многострочный
режим обработки регулярных выражений, аналогично
использованию при их создании флага m
Стоит отметить, что JavaScript-движки современных браузеров могут не
поддерживать некоторых из этих свойств. Например, эксперименты по-
казали, что Internet Explorer не поддерживает свойства multiline и $*, а
Opera не поддерживает ничего из вышеперечисленного, кроме свойств
$1—$9. Таким образом, решение о возможности использования кон-
кретных свойств глобального объекта RegExp может приниматься лишь
исходя из требований совместимости с конкретным браузером.
Вот небольшой пример использования свойств глобального объекта RegExp:
/a(\d)(\d)b/.exec("aaa bbb a94b ccc");
[Link]("RegExp.$1 = " + RegExp.$1 +
"; RegExp.$2 = " + RegExp["$2"]);
Ãëàâà 8. Регулярные выражения 175
Этот пример выведет:
RegExp.$1 = 9; RegExp.$2 = 4
То есть, в свойство $1 глобального объекта RegExp записался результат
сопоставления первого подшаблона (\d), а в $2 — второго. Заметьте,
что к свойствам глобального объекта RegExp можно обращаться как к
свойствам любого другого объекта — в нотации свойств (RegExp.$1)
или элементов массива (RegExp.["$2"]). Однако к свойствам $&, $', $*,
$+, $_, $` следует обращаться только в нотации доступа к элементам
массива, т. к. их имена содержат символы, недопустимые для употреб-
ления в идентификаторах.
Свойства объектов типа RegExp, созданных на этапе времени исполнения
скрипта, изменяются при выполнении операций только с конкретным
объектом. Установка их значений влияет на поведение только того
объекта, для которого они устанавливаются. Эти свойства описаны в
табл. 8.3.
Таблица 8.3. Свойства объектов типа RegExp
Свойство Описание
global Логическое значение, указывающее наличие флага g
у регулярного выражения
ignoreCase Логическое значение, указывающее наличие флага i
у регулярного выражения
lastIndex Содержит позицию символа, следующего за последова-
тельностью, совпавшей с шаблоном в результате по-
следней операции сопоставления. Значение свойства
актуально, только если используется регулярное выраже-
ние с флагом g. Метод exec использует это свойство как
позицию начала поиска, что позволяет последовательно
искать все сопоставимые с шаблоном последовательно-
сти в строке. Сбрасывается в 0 при вызове методов
search, replace, match объектов String
source Содержит текст шаблона регулярного выражения
Методы объекта String, обрабатывающие текст
на основе регулярных выражений
Как уже упоминалось, кроме методов объекта RegExp, с регулярными
выражениями работают и некоторые методы объекта String. Объект
регулярного выражения передается в эти методы в качестве параметра.
176 Часть II. Решение типовых задач программирования скриптов
Метод [Link]() подобен методу [Link](), т. е. выполняет
поиск совпадения шаблона с фрагментами строки. Он возвращает мас-
сив информации, содержащий строки, сопоставленные шаблону. Однако
при установленном флаге g регулярного выражения match ищет все сов-
падения. Также при установленном флаге g возвращаемый методом
match массив будет содержать свойство index. Его значением является
позиция символа, начиная с которого произошло последнее совпадение.
Вот небольшой пример использования метода [Link]().
var oRegExp = /\d+/g;
var str = "ID = 123 №234 abc345def";
[Link]([Link](oRegExp));
Он выведет:
123,234,345
Заметьте, в случае использования [Link]() было бы выведено
только 123.
Метод [Link]() производит замену последовательностей сим-
волов в строке на основе регулярного выражения. Прототип метода
таков:
replace(oRegExp, oString)
Здесь oRegExp — объект регулярного выражения, в соответствии с кото-
рым производится поиск по строке. oString — строка, на которую про-
изводится замена сопоставленной регулярному выражению последова-
тельности. Важной особенностью этого метода является возможность
использования в строке oString символов-заменителей $1—$9. Это по-
зволяет организовать гибкую замену совпавших с шаблоном последова-
тельностей, используя в замещающей строке части исходной строки,
сопоставленные подшаблонам. Например, следующий код выводит ре-
зультат замены всех чисел, состоящих из двух цифр в строке
"67 888 14 de87 23", на число 77.
[Link]("67 888 14 de87 23".replace(/\b\d\d\b/g,
"77"));
Будет сгенерирован следующий вывод:
77 888 77 de87 77
Теперь немного усложним задачу. Допустим, нам надо произвести заме-
ну всех чисел, состоящих из двух цифр, на числа с обратным порядком
цифр (т. е. 67 заменить на 76 и т. д.). Очевидно, для этого нам необхо-
Ãëàâà 8. Регулярные выражения 177
димо выделять первую и вторую цифру числа. Поэтому изменим шаблон
/\b\d\d\b/g на /\b(\d)(\d)\b/g. Захватывающие скобки обеспечат "за-
поминание" первой цифры числа (совпавшей с первым подшаблоном
(\d)) в переменной $1, а второй — в переменной $2. Чтобы решить за-
дачу, замещающая строка должна выглядеть так: $2$1. То есть, второй
символ поместим на первое место, а первый — на второе. Соответст-
венно, код изменится на:
[Link]("67 888 14 de87 23".replace(/\b(\d)(\d)\b/g,
"$2$1"));
И он выведет:
76 888 41 de87 32
Поскольку переменные вида $n в замещающей строке можно использо-
вать неоднократно, а самих переменных — 9 (не так уж и мало), с по-
мощью метода replace можно производить весьма сложную обработку
текста на основе регулярных выражений.
Метод search объекта String служит для простого поиска подстроки на
основе регулярного выражения. Метод возвращает позицию первого
символа последовательности в строке поиска, совпавшей с шаблоном
регулярного выражения, или –1, если совпадений не найдено. Флаг гло-
бального поиска g в регулярном выражении игнорируется. В следующем
фрагменте кода производится поиск десятичного числа (точнее, просто
любой последовательности цифр):
[Link]("abc bcd cde 6549204759\
ooo".search(/\b(\d+)\b/));
Результатом в данном случае будет значение 12. Этот метод полезен,
если необходимо получить только позицию искомой последовательно-
сти в строке, но данные сопоставления не нужны.
Метод split объекта String делит строку на отрезки по границам, обра-
зованным строкой-разделителем, либо последовательностями символов,
сопоставимых регулярному выражению. Возвращаемым значением яв-
ляется массив строк — фрагментов исходной строки, найденных между
разделяющими последовательностями. Метод имеет два прототипа:
split([oRegExp [, nCount]]);
split([strSeparator [, nCount]]);
Здесь oRegExp — объект регулярного выражения, strSeparator — стро-
ка-разделитель, nCount — максимальное число элементов возвращаемо-
го массива. Стоит отметить, что метод split доступен в JavaScript 1.1,
178 Часть II. Решение типовых задач программирования скриптов
в то время как возможность использования в split регулярных выраже-
ний появилась в JavaScript 1.2.
Как следует из описаний прототипов, метод split может вызываться без
параметров. В этом случае будет возвращен массив, содержащий один
элемент — строку, инкапсулируемую объектом, для которого был вы-
зван метод split. Если указать в качестве первого параметра пустую
строку, то будет возвращен массив, образованный разделением строки
поиска на отдельные символы (кстати, этот прием часто оказывается
весьма полезным).
Вот небольшой пример использования метода split с регулярным вы-
ражением:
[Link]("abc 000 \t def\r\n 777".split(/\s+/g));
Здесь строка "abc 000 \t def\r\n 777" делится по границам, образован-
ным последовательностями любых пробельных символов (шаблон \s+).
Как видите, строка содержит пробелы, символы табуляции, переводы
строки. Все последовательные комбинации таких символов будут ис-
пользоваться в качестве разделителей, и этот код выведет:
abc,000,def,777
Метод split полезен для решения многих задач. Например, с его по-
мощью можно выделять все пары <имя>=<значение> из строки cookies,
связанных с документом (в главе 7 мы использовали поиск с помощью
indexOf для обеспечения совместимости со старыми браузерами), а за-
тем производить разделение каждой пары на строку имени и строку зна-
чения.
Практическое использование
регулярных выражений
Ранее в этой главе мы рассмотрели объект RegExp и методы объектов
типа String, работающие с регулярными выражениями. Их использова-
ние позволяет решать достаточно широкий круг задач, возникающих
при создании клиентских сценариев, на JavaScript. В общем случае, уме-
лое применение регулярных выражений в скриптах позволяет сократить
объем кода и повысить их эффективность. Особенно это касается задач
разбора текста с регулярной структурой и поиска в тексте подстрок,
множество которых можно описать шаблонами регулярных выражений
Ãëàâà 8. Регулярные выражения 179
(применение регулярных выражений в этих случаях намного эффектив-
нее, чем реализация собственных алгоритмов разбора и поиска).
Однако большинство случаев применения регулярных выражений в
скриптах, предназначенных для работы в исполняющей среде браузеров,
связано с проверкой корректности и фильтрацией данных, вводимых
пользователем в поля форм. Проверка корректности данных форм кли-
ентскими сценариями позволяет избежать отправки заведомо некор-
ректных данных обработчикам этих форм и известить пользователей о
совершенных ими ошибках, повышая тем самым эксплуатационные ха-
рактеристики Web-сервисов, использующих подобный подход. Работе с
формами в JavaScript посвящена глава 11. Во многих примерах к гла-
ве 11 (расположенных в каталоге examples\11 на компакт-диске) вы най-
дете примеры использования регулярных выражений.
Примеры часто употребляемых
регулярных выражений
Далее мы рассмотрим регулярные выражения, часто используемые для
проверки корректности данных при создании клиентских сценариев для
браузеров. В качестве примера к данной главе была создана небольшая
библиотека функций, находящаяся в файле examples\Lib\re_valid.js на
прилагаемом к книге компакт-диске. Все функции, входящие в нее,
осуществляют проверку текстовых значений, передаваемых им первым
параметром, на соответствие определенным критериям при помощи
регулярных выражений. Данная библиотека будет использоваться в
примерах для следующих глав книги (особенно в примерах главы 11).
Далее для регулярных выражений, присутствующих в библиотеке
re_valid.js, будут приводиться ссылки на функции, использующие эти
выражения. Некоторые функции очень просты и возвращают всего лишь
результат исполнения метода test регулярного выражения (как вы пом-
ните, при помощи метода test осуществляется проверка на сопоставле-
ние строки регулярному выражению). Код более сложных функций бу-
дет поясняться дополнительно.
Итак, начнем с шаблонов регулярных выражений для проверки коррект-
ности текстовых данных простой структуры. Для начала рассмотрим
несколько несложных выражений, поняв работу которых вы сможете
без труда разобраться и со всеми остальными примерами. В табл. 8.4
приведено несколько шаблонов регулярных выражений с описанием
сопоставляемых им данных.
180 Часть II. Решение типовых задач программирования скриптов
Таблица 8.4. Шаблоны регулярных выражений
Шаблон Сопоставляемое множество символов
/^\s+$/ Группа любых пробельных символов (пробелов,
табуляций и т. д.) в любом количестве и последо-
вательности
/^[a-zA-Z]$/ Один (и только один) любой латинский алфавит-
ный символ
/^[a-zA-Z]+$/ Любое количество любых латинских алфавитных
символов
/^\d$/ Один (и только один) любой цифровой символ
/^\d+$/ Любое количество любых цифровых символов
/^([a-zA-Z]|\d)$/ Один (и только один) любой латинский алфавит-
ный либо цифровой символ
/^[a-zA-Z0-9]+$/ Любое количество любых латинских алфавитных и
цифровых символов
Обратите внимание на метасимволы ^ (признак начала строки) и $ (при-
знак конца строки) в начале и в конце всех вышеприведенных шаблонов.
Путем их использования достигается сопоставление шаблонов только с
целой текстовой строкой. К примеру, шаблон /^[a-zA-Z]$/ сопоставля-
ется со строками "a", "b", "Z", но не сопоставляется со строкой "ab".
Если же опустить символ $, выражение превратится в /^[a-zA-Z]/ и
будет сопоставляться с любой строкой, начинающейся на латинский
алфавитный символ. Данный подход (использование метасимволов на-
чала и конца строки) необходимо применять при проверке корректности
данных, введенных в однострочные текстовые поля (элемент INPUT с
атрибутом type="text"), если необходимо, чтобы значение текстового
поля целиком соответствовало определенному критерию.
Используя такие регулярные выражения, можно создать группу простых
функций для проверки строк на соответствие заданным критериям. На-
пример, функция, определяющая, состоит ли строка только из алфавит-
ных латинских символов, могла бы выглядеть так:
function IsStringAlphabetical(str)
{
return /^[a-zA-Z]+$/.test(str);
}
Ãëàâà 8. Регулярные выражения 181
Однако данный подход не обеспечивает гибкости, необходимой при
решении реальных задач. Так, проверку данных, вводимых пользовате-
лем в поля формы, часто приходится осуществлять с учетом ограниче-
ний на длину этих данных (например, в текстовое поле должна быть
введена строка длиной не менее и не более определенного количества
символов). Поэтому логично было бы реализовывать эти функции с
возможностью задания таких ограничений, определив, например, до-
полнительные параметры для передачи значений минимальной и макси-
мальной допустимой длины данных.
Как вы знаете, ввести ограничения на количество повторений символа
(или множества символов) в регулярное выражение можно при помощи
использования метасимволов вида: {n}, {n,} и {n,m}. Например, шаблон
регулярного выражения, сопоставляющийся со строкой, состоящей из не
менее четырех, но не более восьми алфавитных латинских символов,
может выглядеть так: /^[a-zA-Z]{4,8}$/. Такой подход можно исполь-
зовать и для реализации функций проверки данных на основе регуляр-
ных выражений с учетом ограничений на их длину. Однако в этом
случае шаблон регулярного выражения должен будет формироваться
динамически, на основе значений, переданных в функцию парамет-
ров ограничений. Библиотека re_valid.js содержит функцию
VerifyStringWithCount, осуществляющую проверку строки на сопоста-
вимость шаблону регулярного выражения, генерируемого этой функци-
ей на основе "базового" шаблона и значений параметров минимально и
максимально возможного числа повторений множеств символов, сопос-
тавимых "базовому" шаблону. Далее приведен листинг данной функции.
function VerifyStringWithCount(strPattern, strText,
nMin, nMax)
{
strMin = ((typeof(nMin) == "number") ? [Link]() :
"0");
strMax = ((typeof(nMax) == "number") ? [Link]() :
"");
strPattern = "^" + strPattern + "{" + strMin + "," +
strMax + "}$";
var oRegExp = new RegExp(strPattern);
return [Link](strText);
}
Строка шаблона, служащего основой формируемого в функции шаблона
регулярного выражения, передается в качестве параметра strPattern.
Параметр strText — строка, проверяемая на соответствие критерию
182 Часть II. Решение типовых задач программирования скриптов
корректности. Параметры nMin и nMax соответственно — минимально и
максимально допустимое число повторений последовательностей сим-
волов, сопоставимых шаблону strPattern.
Как видно из листинга, функция генерирует шаблон регулярного выра-
жения вида /^strPattern{strMin,strMax}$/. Обратите внимание, что в
случае передачи в качестве параметров nMin и nMax нечисловых значе-
ний в генерируемый шаблон будут подставлены значения ограничений
по умолчанию (0 в качестве минимального и пустая строка в качестве
максимального значения). Таким образом, если при вызове данной
функции параметры nMin и nMax не будут указаны, то будет сгенериро-
ван шаблон вида /^strPattern{0,}$/, сопоставляющийся как с пустой
строкой, так и со строкой, содержащей очень большое количество по-
вторений последовательностей символов, сопоставимой с подшаблоном,
определяемым параметром strPattern. Если же не указать только пара-
метр nMax, сгенерируется шаблон вида /^strPattern{strMin,}$/, кото-
рый будет сопоставляться со строкой, содержащей минимум nMin (мак-
симум не ограничен) повторений последовательностей, сопоставимых
с strPattern.
Теперь, используя функцию VerifyStringWithCount и шаблоны простых
регулярных выражений, приведенные выше, мы можем реализовать не-
сколько функций проверки корректности данных с учетом ограничений
на их длину. Так, в библиотеку re_valid.js были добавлены функции Is-
StringAlphabetical, IsStringDigit, IsStringAlphanumeric для провер-
ки, состоит ли строка только из латинских алфавитных, цифровых и ла-
тинских алфавитных и цифровых символов соответственно. Данные
функции просто возвращают результат исполнения функции Verify-
StringWithCount, передавая ей специфический шаблон регулярного вы-
ражения в качестве первого параметра, а также проверяемую строку и
параметры nMin и nMax. Например, код функции IsStringAlphabetical
выглядит следующим образом (код функций IsStringDigit и
IsStringAlphanumeric выглядит аналогично):
function IsStringAlphabetical(str, nMin, nMax)
{
return VerifyStringWithCount("[a-zA-Z]", str, nMin, nMax);
}
Заметьте, что строка-шаблон, передаваемая функции VerifyStringWithCount,
может задавать несколько последовательных множеств символов, одна-
ко генерируемый в VerifyStringWithCount результирующий шаблон
содержит метасимвол типа {m,n}, ограничивающий количество повто-
Ãëàâà 8. Регулярные выражения 183
рений только последней последовательности. Это удобно использовать
для создания функций, проверяющих данные на соответствие более
сложным критериям корректности, в соответствии с которыми строка
данных должна начинаться определенной последовательностью сим-
волов.
В библиотеке re_valid.js содержатся функции IsValidIdentifier,
IsValidNickName и IsValidSignedInteger. Функция IsValidIdentifier
проверяет корректность идентификатора, записанного в стиле С++
(строка должна начинаться на любой алфавитный латинский символ, за
которым могут следовать алфавитные латинские, цифровые символы,
а также символы подчеркивания _). Функция IsValidNickName произво-
дит проверку корректности имени пользователя (Nick Name), которое
обычно требуется вводить при регистрации учетных записей в различ-
ных Web-сервисах (например, форумах). А функция
IsValidSignedInteger — проверку, является ли строка знаковым целым
числом. Далее приведен код функции IsValidIdentifier.
function IsValidIdentifier(str, nMin, nMax)
{
if(typeof(nMin) == "number")
nMin -= 1;
else
nMin = 0;
if(typeof(nMax) == "number")
nMax -= 1;
return VerifyStringWithCount("[a-zA-Z]([a-zA-Z0-9_])", str,
nMin, nMax);
}
Как видите, в функцию VerifyStringWithCount передается строка-
шаблон [a-zA-Z]([a-zA-Z0-9_]). Последовательность [a-zA-Z] этого
шаблона определяет множество символов, допустимых к использованию
в качестве первого символа строки, передаваемой в параметре str. По-
следовательность ([a-zA-Z0-9_]) определяет множество допустимых
символов, следующих за первым. Параметры nMin и nMax функции
IsValidIdentifier определяют минимальную и максимальную длину
всего идентификатора. Для того чтобы учесть первый символ, определяе-
мый последовательностью [a-zA-Z], в функцию VerifyStringWithCount
передаются значения, на единицу меньшие значений nMin и nMax.
Код функции IsValidNickName аналогичен коду IsValidIdentifier
184 Часть II. Решение типовых задач программирования скриптов
за тем лишь исключением, что в функцию VerifyStringWithCount
передается строка-шаблон [a-zA-Z]([a-zA-Z0-9_\. ]). Функция
IsValidSignedInteger просто возвращает результат исполнения
VerifyStringWithCount, передавая ей шаблон [-+]?\d, при этом пара-
метры nMin и nMax функции задают длину цифровой части числа, т. е.
длину числа без учета знака.
Усложняя шаблоны, передаваемые в функцию VerifyStringWithCount,
можно проверять строки текстовых данных на соответствие более слож-
ным условиям. Например, используя шаблон (\b[a-z0-9]+\s*), можно
проверить, содержит ли строка несколько слов (алфавитно-цифровых
последовательностей), в количестве, определяемом диапазоном значе-
ний параметров функции VerifyStringWithCount. Такую проверку про-
изводит функция IsStringContainXWords, включенная в библиотеку
re_valid.js.
function IsStringContainXWords(str, nMin, nMax)
{
return VerifyStringWithCount("(\\b[a-z0-9]+\\s*)",
str, nMin, nMax);
}
Лидирующий метасимвол \b шаблона позволяет выделить начало каж-
дого слова. В конце же слова может присутствовать любое количество
пробельных символов (последовательность \s*). Как видите, при помо-
щи функции VerifyStringWithCount можно производить проверку кор-
ректности достаточно разнородных данных.
Теперь рассмотрим несколько регулярных выражений, позволяющих
проверять корректность данных с более сложной структурой. Простым
примером таких данных могут являться числа с плавающей точкой. Та-
кие числа могут содержать несколько цифр в целой и несколько цифр в
дробной частях (например, 123.456). Простейшее регулярное выраже-
ние для проверки корректности записи таких чисел может выглядеть
следующим образом:
/^\d+\.\d+$/
Такой шаблон сопоставляется только строкам, содержащим запись
дробного числа, имеющего и целую, и дробную часть ( 1.26, 100.2). Од-
нако дробное число может быть записано и без целой части (например,
.15), а также с указанием знака (+ или -). Регулярное выражение, учиты-
вающее знак, а также возможность отсутствия целой части, может вы-
глядеть так:
/^[-+]?\d*\.\d+$/
Ãëàâà 8. Регулярные выражения 185
Обратите внимание, вследствие применения метасимвола ? данный
шаблон будет сопоставляться со строками, содержащими запись дроб-
ного числа как с указанием знака, так и без него. Данное выражение ис-
пользуется функцией IsValidSignedFloat, присутствующей в библиоте-
ке re_valid.js.
Достаточно часто в формах регистрации учетных записей Web-сервисов
требуется указать дату своего рождения. Обычно для ввода даты преду-
сматривается несколько полей (отдельные поля для ввода года, месяца,
числа месяца). Однако можно использовать для этой цели одно тексто-
вое поле, попросив пользователя ввести дату в формате yyyy-mm-dd, где
yyyy — год, mm — номер месяца, dd — число месяца. Давайте составим
регулярное выражение для проверки данных такого формата. Положим,
что допустимо вводить год в диапазоне от 1000 до 9999, номер месяца
от 01 до 12 и число месяца от 00 до 31. Для удобства будем допускать
использование разделителей -, /, . (точка) и " " (пробел). То есть, до-
пустимыми форматами даты будут: yyyy-mm-dd, yyyy/mm/dd, [Link]
и yyyy mm dd.
Сначала составим шаблон, с которым будут сопоставляться допустимые
значения года. Так как первым символом строки, представляющей год,
могут являться цифры в диапазоне от 1 до 9, а следующими символа-
ми — любые цифры (а всего символов должно быть четыре), то шаблон
будет выглядеть так: [1-9]\d\d\d. Теперь составим шаблон для данных
номера месяца. Номер месяца должен находиться в диапазоне от 00 до 12.
То есть, это всегда две цифры, причем, в случае если первая цифра 0, то
вторая может изменяться от 1 до 9 (номера месяца 01—09), а если пер-
вая цифра 1, то вторая изменяется от 0 до 2 (номера месяца 10—12).
Следовательно, шаблон регулярного выражения, сопоставляемый кор-
ректным значениям номера месяца, может выглядеть так: 0[1-9]|1[0-2].
Аналогично, шаблон для числа в месяце будет: 0[1-9]|[12][0-9]|3[01].
Шаблон для допустимого множества разделителей выглядит просто: [-
/\.]. Соединив вместе все описанные подшаблоны, получим результи-
рующий шаблон, пригодный для проверки корректности строки даты:
/^[1-9]\d\d\d[- /\.](0[1-9]|1[0-2])[- /\.](0[1-9]|[12][0-9]|3[01])$/
Поскольку в данном шаблоне подшаблон [- /\.] используется дважды,
ему будут сопоставляться строки типа: 2006-01/02, 2006/05 04, т. е. раз-
делители между годом и номером месяца и номером месяца и числом
месяца могут быть разного типа. Чтобы сделать обязательным исполь-
зование одинаковых разделителей, необходимо применить обратные
ссылки в регулярном выражении. Для этого применим к первому под-
186 Часть II. Решение типовых задач программирования скриптов
шаблону [- /\.] операцию "захватывающие скобки", а вместо символа-
разделителя во втором подшаблоне вставим ссылку на захваченную по-
следовательность символов (она будет первой по счету). В результате
регулярное выражение будет выглядеть так:
/^[1-9]\d\d\d([- /\.])(0[1-9]|1[0-2])\1(0[1-9]|[12][0-9]|3[01])$/
Данное выражение используется функцией IsValidDate библиотеки
re_valid.js.
Последним типом регулярных выражений, которые мы рассмотрим в
этой главе, будут выражения для проверки корректности адреса элек-
тронной почты (e-mail). Следует сразу отметить, что полное регулярное
выражение для проверки e-mail в соответствии с форматом, определяе-
мым RFC 822, очень громоздкое (вы можете найти его в файле
addons\[Link] на прилагаемом к книге компакт-диске). Поэтому
на практике обычно используются регулярные выражения различной
сложности, осуществляющие лишь частичную проверку допустимости
формата почтового адреса. Регулярное выражение обычно выбирается
соответственно поставленной задаче. Далее мы рассмотрим несколько
таких выражений.
Самый простой шаблон регулярного выражения для проверки почтового
адреса, который я когда-либо видел, таков: /^.+\@.+\..+$/. Он сопос-
тавляется любой строке, начинающейся с любой последовательности
символов, после которой следует символ @, после которого должна сно-
ва идти последовательность любых символов, затем — точка, и опять —
последовательность любых символов. То есть, данному шаблону бу-
дут сопоставляться строки: "ab@[Link]", "a#> b@c d.e f" и
"ab@c....d...e..f". Очевидно, что этот шаблон мало применим для
решения реальных задач. Необходимо, как минимум, ограничить мно-
жество символов, допустимых к употреблению в имени пользователя и
домена адреса. Также необходимо ввести ограничение на длину имени
доменной зоны (com, net и т. д.). Следующий шаблон учитывает выше-
указанные ограничения:
/^[\w\.\-]+@[\w\-]+\.[a-zA-Z]{2,4}$/
Ему сопоставляются строки: "ab@[Link]", "a.b@[Link]", но не сопостав-
ляются строки: "a.b@[Link]", "a#b@[Link]". Однако ему также сопостав-
ляется: "a...b@[Link]". К тому же стоит учесть, что почтовый адрес мо-
жет находиться на поддомене любого уровня (например, следующий
адрес корректен [Link]@[Link]). С учетом всего
вышеперечисленного, новый шаблон наиболее корректен:
/^([\w]+)(\.[\w]+)*@([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,4}$/
Ãëàâà 8. Регулярные выражения 187
Ему не сопоставляются строки "a...b@[Link]" и "a#b@c [Link]", но сопостав-
ляются "dummy@[Link]", "[Link]@[Link]".
Именно этот шаблон используется в функции IsValidEmail библиотеки
re_valid.js, осуществляющей проверку корректности e-mail. Конечно,
этот шаблон не учитывает возможность использования вместо символи-
ческого имени домена IP-адреса (например, user@[Link]), а также
существования доменных зон с длинными именами, таких как museum
(справедливости ради стоит заметить, что пользователей с почтовыми
адресами в таких зонах крайне мало), но зато он достаточно прост и
весьма эффективен.
Для тестирования функций библиотеки re_valid.js был создан пример.
Файл примера ex_08_01.htm расположен в каталоге examples\08 на при-
лагаемом к книге компакт-диске.
ГЛАВА 9
Протоколы
mailto и javascript
Как вы, наверное, помните, ранее нами было рассмотрено понятие URI
(Uniform Resource Identifier). В этой главе мы будем оперировать поня-
тием URL (Uniform Resource Locator), являющимся подмножеством URI,
поэтому определим подробнее этот термин.
Итак, URL является строкой, записанной в определенном формате, уни-
кально идентифицирующей некоторый ресурс в сети. Описание формата
URL дано в RFC 1738 (этот документ основан на RFC 1630, описываю-
щем формат URI). Согласно RFC 1738, URL записывается в виде:
<scheme>:<scheme-specific-part>
Здесь <scheme> — имя "схемы" URL, обозначающей способ обработки и
метод доступа к данным, идентифицируемым строкой <scheme-
specific-part>. Вид этой строки, как и тип содержащихся в ней дан-
ных, зависит от схемы. Чаще всего схема определяет протокол доступа
к данным.
В общих чертах протокол можно охарактеризовать как набор правил и
стандартов, на основе которых производится обмен данными (например,
HTTP, FTP, file). Реализация поддержки того или иного протокола зави-
сит от конкретного браузера. К примеру, Microsoft Internet Explorer под-
держивает протокол res, позволяющий просматривать медиаданные,
содержащиеся в виде ресурсов в PE-модулях Windows (exe, dll и т. д.).
Другие браузеры не поддерживают этот протокол (в них эта поддержка
просто не нужна). В этой главе нас будут интересовать только протоко-
лы mailto и javascript.
Ãëàâà 9. Протоколы mailto и javascript 189
Протокол mailto
Этот протокол предназначен для отправки писем через почтовую систе-
му пользователя. URL-схема mailto была описана в 1994 году в
RFC 1738 и предполагала наличие в URL только почтового адреса полу-
чателя без каких-либо дополнительных данных. То есть, ссылка, вклю-
чающая URL типа mailto, могла бы выглядеть так:
<A href="[Link] письмо</A>
Активация подобной ссылки обычно вызывает запуск почтового клиента
(почтовой программы, настроенной на обработку почты по умолчанию в
операционной системе, либо собственного почтового клиента браузера).
В 1998 году в RFC 2368 был определен расширенный формат URL на
основе схемы mailto, позволяющий кроме адреса электронной почты
указывать в URL дополнительные заголовки и содержимое сообщения.
Сегодня этот формат поддерживается в той или иной мере всеми брау-
зерами.
Согласно RFC 2368, формат URL типа mailto выглядит как:
mailto: [ to ] [ headers ]
Здесь to должен являться списком разделенных запятой адресов получа-
телей в соответствии с RFC 822, а headers — набором дополнительных
заголовков (CC, BCC, body, subject) в виде пар заголовок>=<значение>,
разделенных знаком & и отделенных от "to знаком ?. Заголовки имеют
следующий смысл:
CC — включает адреса поля "копия" ("cc" или carbon copy);
BCC — включает адреса поля "скрытая копия" ("bcc" или blind carbon
copy);
body — тело почтового сообщения;
subject — тема сообщения.
В URL типа mailto символы ?, =, & зарезервированы для разделения час-
тей URL. Также не допускается использование символов пунктуации
и разделителей. Эти символы необходимо заменять escape-последова-
тельностями. Поскольку символ & в HTML является служебным, он
должен быть заменен на & в URL типа mailto, содержащихся в
HTML-документах.
Таким образом, согласно RFC 2368 URL для отправки письма могут
выглядеть так:
190 Часть II. Решение типовых задач программирования скриптов
[Link]
[Link]
Текст%20Текст
[Link]
Текст
Эти URL эквивалентны и определяют, что почтовый клиент должен соз-
дать сообщение с темой "Тема", содержимым "Текст Текст", адресо-
ванное получателям a1@[Link] и a2@[Link] (как видите, формат
URL этого типа достаточно гибок). Однако на данный момент сущест-
вует ряд ограничений на использование URL типа mailto в гипертексто-
вых документах, обусловленных многочисленными отклонениями раз-
личных почтовых клиентов от рекомендаций RFC 2368. Далее я просто
приведу результаты своих экспериментов.
Microsoft Outlook 2003 не воспринимает адреса получателей, указанные
после заголовка to (хотя, Outlook Express 6, например, прекрасно с этим
справляется). К тому же, если адреса получателей разделены запятой,
выдается сообщение об ошибке при отправке письма. В Outlook 2003
адреса должны разделяться точкой с запятой (%3B%20 в виде escape-
последовательности). Также существуют весьма серьезные ограничения
на длину URL типа mailto, которую программы Outlook 2003 и Outlook
Express и могут обработать. В Windows 2000 программа Outlook 2003
обрабатывала URL типа mailto длиной максимум в 2021 символ. Про-
грамма Outlook Express 4.72, установленная в Windows 98, не смогла
обработать URL длиной больше 463 символа. Как видите, ограничения
весьма существенные.
Наиболее корректно, полностью соответствуя RFC 2368, работает поч-
товый клиент Mozilla. Хуже всего работает клиент Opera (автор исполь-
зовал Opera 7.50, в Opera 8.02 почтовый клиент, должно быть, работает
корректнее), воспринимая лишь один адрес только после mailto: (вме-
сто декодирования escape-последовательностей в списке адресов клиент
Opera производит их кодирование, что делает невозможным использо-
вание списка адресов). Строго говоря, почтовый клиент Opera кодирует
все поля, переданные из URL типа mailto, в том числе поле темы и тела
сообщения, что приводит к отображению в виде escape-последова-
тельностей всего текста, который набран не латиницей.
Таким образом, для обеспечения наилучшей совместимости с большин-
ством почтовых клиентов при использовании в гипертекстовых доку-
ментах URL на основе протокола mailto разумно соблюдать следующие
ограничения:
Ãëàâà 9. Протоколы mailto и javascript 191
применять только URL вида
[Link]
и не использовать конструкции типа
[Link]
не использовать списки адресов получателей (указывать один адрес
после mailto: и, если необходимо, отсылать копии сообщения дру-
гим адресатам, один адрес в поле CC и один адрес в поле BCC);
ограничивать общую длину URL примерно 450 символами.
Пока мы рассмотрели только особенности URL на основе протокола
mailto и возможности применения гиперссылок, имеющих целью URL
типа mailto. Но настоящий интерес для нас представляют возможности
использования mailto-протокола при разработке клиентских сценариев
на JavaScript.
JavaScript и URL типа mailto
Интерактивными элементами Web-страниц, использующими URL типа
mailto, являются ссылки и формы. Сразу отметим, что такие элементы,
в общем, самодостаточны (активизация mailto-ссылки вызывает запуск
почтовой программы и без применения JavaScript). Поэтому задач, в
которых может потребоваться программная инициализация почтового
клиента и создание сообщения с заданными параметрами, не так уж и
много. Очевидно, что наибольший интерес для нас представляет форми-
рование текста почтового сообщения (например, включение в него не-
которой полезной информации). Таким образом, хорошим примером
для рассмотрения темы совместного применения протокола mailto и
JavaScript может являться создание скриптов, формирующих текст со-
общений об ошибках, замеченных пользователем на вашем сайте.
URL типа mailto и JavaScript в ссылках
Должно быть, посещая различные информационные ресурсы в Интерне-
те, вы не раз сталкивались с ошибками или опечатками в опубликован-
ных на них материалах. Вы также могли видеть, что часто, в целях по-
вышения качества содержимого сайтов, их администраторы размещают
на страницах специальный текстовый блок, содержащий просьбу, адре-
сованную посетителям ресурса, сообщать о любых ошибках, замечен-
ных ими на сайте. Во многих случаях такой блок содержит mailto-
192 Часть II. Решение типовых задач программирования скриптов
ссылку с адресом электронной почты администратора сайта. Очевидно,
что при описании проблемы в почтовом сообщении необходимо указать
URL страницы сайта, на которой обнаружена ошибка. Однако пользова-
тель может и не сделать этого (например, просто забыть). Используя
JavaScript, мы можем сделать ссылку более "интеллектуальной". Щел-
чок по такой ссылке вызовет создание почтового сообщения, включаю-
щего URL текущей страницы.
Фактически, нашей задачей является динамическое формирование зна-
чения свойства href ссылки (URL типа mailto). Это может быть сделано
либо путем генерации фрагмента документа при помощи метода
[Link], либо заменой значения атрибута href элемента A, за-
данного в HTML-разметке. Поскольку ссылка должна присутствовать
на странице и при отключенных скриптах, лучше задать ее в HTML-
разметке. А чтобы "иметь все в одном месте", изменение атрибута href
можно производить в ее обработчике onclick:
<a href="[Link] onclick="
[Link] = '[Link] +
'&body=%09Здравствуйте%20!%0D%0A%0D%0AНа%20странице%20' +
[Link] + '%20замечена%20ошибка:%0D%0A%0D%0A' +
'[пожалуйста%2C%20опишите%20ошибку]';"
>Сообщить об ошибке на этой странице</a>
Таким образом, если пользователь отключил исполнение скриптов в
браузере, при щелчке на этой ссылке будет использоваться URL, задан-
ный HTML-разметкой, иначе будет исполнен код, содержащийся в об-
работчике onclick, и значение атрибута href будет заменено на новый
URL, включающий текст сообщения с адресом текущего документа
([Link]). Этот код содержится в примере examples\09
\ex_9_01.htm.
Теперь немного усовершенствуем этот пример, предоставив пользовате-
лю возможность ввести текст сообщения об ошибке еще до запуска поч-
товой программы. Используем метод prompt для запроса текста сообще-
ния, и полученную информацию будем также включать в URL типа mail-
to в параметр body:
<a href="[Link]
onclick="
var str = prompt('Пожалуйста, опишите ошибку', '');
[Link] = '[Link]
'&body=%09Здравствуйте%20!%0D%0A%0D%0AНа%20странице%20' +
Ãëàâà 9. Протоколы mailto и javascript 193
[Link] + '%20замечена%20ошибка:%0D%0A%0D%0A' +
(str ? encMailURLComponent(str) :
'[пожалуйста%2C%20опишите%20ошибку]');"
>Сообщить об ошибке на этой странице</a>
Этот пример также содержится в файле ex_9_01.htm. Как видите, если
пользователь вводит некоторый текст в диалоговом окне метода prompt
(заносится в переменную str), перед добавлением к URL, он обрабаты-
вается функцией encMailURLComponent. Эту функцию также можно уви-
деть в файле ex_9_01.htm. Она осуществляет замену недопустимых для
URL типа mailto символов на их escape-эквиваленты.
При желании, можно вынести код обработчика onclick в отдельную
функцию во внешний скрипт, который можно будет подключать ко всем
документам, содержащим ссылки "обратной связи". В этом же скрипте
производить поиск таких ссылок в документе (например, по значению
атрибута name) и назначать для этой группы ссылок обработчиком
onclick данную функцию. Главным препятствием в использовании по-
добных "интеллектуальных" ссылок является ограничение на длину URL
типа mailto.
URL типа mailto и JavaScript в формах
Формы кратко описывались в главе 2. Работа с ними средствами Java-
Script подробно описана в главе 11. Здесь мы рассмотрим только ис-
пользование форм, данные которых передаются обработчику протокола
mailto браузера.
URL, задающий обработчик данных формы, определяется значением
свойства action элемента FORM. Данные формы могут передаваться об-
работчику методом GET, либо POST. Метод передачи данных задается
свойством method элемента FORM. Он определяет тип HTTP-запроса при
отправке данных формы по протоколу HTTP или HTTPS, а также способ
представления данных.
Метод отправки формы GET подразумевает передачу информации через
параметры в URL. То есть, процесс передачи данных абсолютно иден-
тичен тому, который происходит при щелчке по ссылке. В случае от-
правки формы методом GET URL формируется путем добавления к со-
держимому свойства action группы параметров, именами которых яв-
ляются имена элементов формы (свойство name), а значениями —
текущие значения этих элементов. Причем недопустимые символы в
URL автоматически кодируются. Таким образом, при отправке формы,
использующей схему mailto в URL свойства action и содержащей эле-
194 Часть II. Решение типовых задач программирования скриптов
менты с именами заголовков почтового сообщения, будет запущена
почтовая программа и создано сообщение со значениями параметров,
содержащихся в полях формы. То есть, при отправке следующей формы
(нажатии кнопки Послать) будет создаваться сообщение с полем
subject и телом, введенным в соответствующие поля.
<FORM action="[Link] method="GET">
<INPUT type="text" name="subject">
<TEXTAREA name="body" cols=40 rows=5></TEXTAREA>
<INPUT type="submit" value="Послать">
</FORM>
Возможность использования таких форм весьма привлекательна (авто-
матически формируется URL типа mailto, автоматически кодируются
недопустимые символы, некоторые поля формы можно сделать скры-
тыми и задать из значения), но, к сожалению, крайне ограничена (из-за
несоответствия большинства почтовых программ RFC 2368 и особенно-
стей кодирования URL — все пробелы во всех полях будут заменены
знаком +). Вы можете убедиться в этом сами, поэкспериментировав с
подобной формой, используя пример examples\09\ex_9_02.htm. Однако
мы применим формы для той же цели, для которой применяли mailto-
ссылки — отправка отчета об ошибке, замеченной на странице пользо-
вателем. Поскольку объем информации, отправляемый методом GET,
весьма ограничен, обратим внимание на формы, отправляемые методом
POST.
При отправке формы методом POST ее данные кодируются и передаются
обработчику в теле запроса, отдельно от URL. Способ кодирования дан-
ных указывается значением атрибута enctype элемента FORM. По умол-
чанию используется метод application/x-www-form-urlencoded. При
отправке формы, атрибут action которой содержит URL типа mailto,
использующей данный метод кодирования, будет создано почтовое со-
общение, содержащее данные формы как вложение (attach). Для того
чтобы данные полей формы были помещены в тело письма (в текстовом
виде), необходимо использовать метод кодирования text/plain. Данные
будут представлены в виде <имя элемента формы>=<значение элемента>
(для отчета об ошибке такой формат вполне подходит). Поскольку фор-
ма отправляется методом POST, объем текста формируемого сообщения
может быть весьма большим.
На компакт-диске находится пример examples\09\ex_9_03.htm, демонст-
рирующий применение mailto-формы, которая отправляется методом
POST для создания почтовых сообщений, включающих URL текущего
Ãëàâà 9. Протоколы mailto и javascript 195
документа, комментарий пользователя (запрашивается с помощью ме-
тода prompt), а также часть текста документа, которая была выделена
пользователем на момент отправки формы. Такое сообщение поможет
администратору сайта очень быстро выявить проблему. В то же время,
от пользователя требуется минимум усилий для создания этого сообще-
ния — при обнаружении ошибки пользователь просто выделяет ее
мышью и нажимает кнопку вверху страницы, после чего, если хочет,
вводит свой комментарий.
Принцип работы примера очень прост. Все поля mailto-формы в доку-
менте ex_9_03.htm являются скрытыми (видима только кнопка submit).
Их значения задаются в функции OnFeedbackSubmit, вызываемой из об-
работчика onsubmit перед отправкой формы. Именно в этой функции и
производится запрос комментария пользователя, определение адреса
текущего документа, получение фрагмента выделенного в документе
текста. Все полученные значения заносятся в соответствующие поля.
Параметр action формы содержит URL типа mailto, задающий адрес
получателя и тему сообщения. На случай, если исполнение скриптов в
браузере отключено, все поля формы содержат значения по умолчанию.
К сожалению, почтовый клиент браузера Opera, как и в случае с mailto-
ссылками и формами, отправляемыми методом GET, ведет себя своеоб-
разно (данные формы просто не переносятся в создаваемое сообщение).
Это необходимо учесть при применении подобного скрипта на страни-
цах реального сайта.
Протокол javascript
Указание протокола javascript в URL предполагает, что данные, содер-
жащиеся в URL, являются JavaScript-кодом. Такой URL записывается
в форме:
javascript:<JavaScript-код>
Здесь <JavaScript-код> представляет собой набор корректных утвер-
ждений JavaScript, разделенных точкой с запятой (;). Вот простой при-
мер URL типа javascript:
javascript:var str = prompt('',''); alert(str);
Код, содержащийся в URL типа javascript, исполняется при интерпрета-
ции браузером этого URL. То есть, исполнение кода зависит от контек-
ста, в котором употребляется URL. Существует много возможностей
использования URL типа javascript.
196 Часть II. Решение типовых задач программирования скриптов
Возможности применения URL
типа javascript
Должно быть, чаще всего URL типа javascript применяется в ссылках
(атрибут href элемента A). Обработка URL ссылки происходит после
активизации ее пользователем, если только обработчик onclick ссылки
(если установлен) не возвратил false. Таким образом, при совершении
щелчка по ссылке:
<a href="javascript:alert('1');" onclick="alert('0');">
JS ref</a>
отобразится окно сообщения с содержимым "0", а потом — с содержи-
мым "1". Однако при щелчке по ссылке:
<a href="javascript:alert('1');" onclick="return false;">
JS ref</a>
ничего не произойдет, поскольку URL типа javascript не будет обрабо-
тан. Часто URL типа javascript в атрибуте href используют только для
создания ничего не адресующих ссылок. Допустим, на странице вам
нужна ссылка, при щелчке на которую ничего не произойдет. Если в
элементе A не указать атрибут href, то браузеры будут интерпретировать
его как якорь и, следовательно, отображать его содержимое как простой
текст. Если указать в качестве значения href пустую строку, то браузеры
будут загружать либо заново ту же страницу, либо "корневой" ресурс.
Однако щелчок по следующей ссылке не вызовет загрузки какого-либо
документа.
<a href="javascript:void(0);">JS ref</a>
В гипертекстовом документе ссылки также могут быть представлены
элементами AREA, задающими активные области клиентских навигаци-
онных карт. Атрибут href элемента AREA определяет URL ссылки. Как и
в случае с элементом A, значением атрибута href может быть URL типа
javascript. На компакт-диске находится пример examples\09\ex_9_04.htm,
демонстрирующий применение URL типа javascript в атрибутах href
элементов AREA. В этот документ внедрен рисунок, изображающий не-
сколько геометрических фигур. Рисунок использует клиентскую навига-
ционную карту, значения атрибутов href элементов AREA которой пред-
ставляют собой URL типа javascript, содержащие код для отображения
окон сообщений с описанием соответствующих фигур.
Применение URL типа javascript возможно также в формах (в качестве
значения атрибута action элемента FORM). Если у формы задан обработ-
Ãëàâà 9. Протоколы mailto и javascript 197
чик onsubmit, то вызывается сначала он. Если код обработчика не воз-
вратил false, происходит обработка URL в атрибуте action и, если это
URL типа javascript, исполнение соответствующего кода.
Одним из самых интересных применений протокола javascript является
формирование содержимого документов в частях фреймов, встроенных
фреймов и popup-окон. Адрес документа, загружаемого во фрейм или
встроенный фрейм, задается значением атрибута src элементов FRAME и
IFRAME соответственно. Создание нового окна браузера производится с
помощью метода [Link], которому первым параметром передается
URL документа, загружаемого в создаваемое окно. Обычно значениями
атрибута src и первого параметра метода [Link] являются адреса
внешних документов. Однако существуют случаи, в которых загрузка
внешнего документа не особо целесообразна (например, документ очень
мал по объему, но должен формироваться динамически). Применение
URL типа javascript в таких случаях представляет собой простой способ
динамического формирования документа целиком на клиентской стороне.
На текущий момент "загрузка" данных по протоколу javascript поддер-
живается всеми популярными браузерами. Для того чтобы из URL типа
javascript, указанного в качестве источника данных фрейма или popup-
окна, был сформирован документ, JavaScript-код, находящийся в URL,
должен вычисляться в строку, содержащую данные этого документа так,
как если бы он был загружен из внешнего источника. Далее приведены
два абсолютно одинаковых по содержанию примера использования URL
типа javascript как источника данных документа встроенных фреймов.
<IFRAME src="javascript:'<body>JS Doc</body>';"></IFRAME>
<IFRAME src="javascript: var str = '<body>
JS Doc</body>';str;"></IFRAME>
На компакт-диске содержится немного усложненный пример
(examples\09\ex_9_05.htm), применения протокола javascript для форми-
рования документа во встроенном фрейме — в нем формируемый доку-
мент содержит внедренный скрипт, генерирующий часть этого докумен-
та. Пример examples\09\ex_9_06.htm демонстрирует применение URL
типа javascript при создании popup-окна.
При использовании протокола javascript стоит учесть тот факт, что брау-
зеры семейства Mozilla кодируют нелатинские символы в строке дан-
ных, в которую вычисляется код URL типа javascript, в виде escape-
последовательностей, что приводит к неправильному отображению не-
латинского текста в сформированных документах в этих браузерах. Од-
198 Часть II. Решение типовых задач программирования скриптов
нако это почти не сказывается на возможностях практического приме-
нения URL типа javascript данным способом. Так, необходимость дина-
мического формирования документа во внедренном фрейме обычно
возникает в связи с использованием скрытых фреймов (например, для
фоновой отправки форм без перезагрузки текущей страницы).
Далее речь пойдет о весьма экзотических способах применения URL
типа javascript, а именно — о включении его в ссылки на таблицы сти-
лей, изображения и значения свойств CSS. Для начала — совершенно
бесполезный, на мой взгляд, пример использования URL типа javascript
в ссылке на таблицу стилей:
<link rel="stylesheet" type="text/css"
href="javascript:alert('JS!'); 'DIV {color: green}';">
Этот код "сработает" в Internet Explorer и Opera — будет отображено
окно сообщения с текстом "JS!", а также в список таблиц стилей доку-
мента будет добавлена таблица, содержащая правило DIV {color: green}.
Очевидно, это не самый элегантный способ динамического формирова-
ния таблиц стилей в документе.
Должно быть, вы помните, что значением некоторых CSS-свойств также
может являться URL. К таким свойствам относятся: background,
background-image, list-style. Код URL типа javascript, указанный
в качестве значений таких свойств, исполняется в браузерах Internet
Explorer и Opera. К примеру, при загрузке документа, включающего
следующий элемент разметки браузером Internet Explorer либо Opera,
будет отображено окно сообщения с текстом "JS!".
<a href="#"
style="background:URL(javascript:alert('JS!');)">link</a>
Значением атрибута src элемента IMG также может являться URL типа
javascript:
<IMG src="javascript:alert('JavaScript URL message !');">
Примечательно, что при помощи URL типа javascript можно генериро-
вать изображения на стороне клиента (об этом будет рассказано далее).
Но сейчас обратим внимание на другой факт — употребление протокола
javascript в данном контексте позволяет исполнять на странице произ-
вольный JavaScript-код. Эта возможность не несет практической пользы
для разработчика клиентских сценариев, однако является одним из спо-
собов проведения атак на различные сетевые ресурсы, содержимое ко-
торых формируется пользователями этих ресурсов (такие как форумы и
гостевые книги).
Ãëàâà 9. Протоколы mailto и javascript 199
Предположим, что на некотором форуме допустимо использование не-
скольких HTML-тегов для визуального оформления текста, причем раз-
решены некоторые атрибуты (например, тег FONT с атрибутом color
и т. д.). Если осуществляется недостаточная фильтрация атрибутов, и с
некоторым HTML-элементом допустимо употребление атрибута style,
злоумышленник может внедрить в страницу произвольный JavaScript-
код путем добавления сообщения, используя конструкцию:
<ТЕГ style="background:URL(javascript:<JavaScript-код>)">...
Однако гораздо чаще в форумах и гостевых книгах допускается указа-
ние ссылок на внешние изображения. Часто, даже если применение
HTML-разметки запрещено, для оформления текста пользователям
предлагается использовать набор специальных тегов (так называемые
теги bbCode). Например, включить в сообщение изображение, располо-
женное по определенному адресу в Интернете, можно так:
[img][Link]
При генерации страницы скрипт форума заменяет псевдотег [IMG] на
HTML-тег <IMG>, помещая в атрибут src адрес изображения, указанный
пользователем. Если скрипт форума не производит проверки корректно-
сти адреса изображения и допускает использование URL типа javascript,
то злоумышленник также может легко внедрить в страницу свой
JavaScript-код. Сложность и объем внедренного таким образом кода не
ограничивается длиной URL. Например, следующая конструкция сгене-
рирует в документе разметку, вызывающую загрузку и исполнение
внешнего скрипта:
<IMG src="javascript:[Link]('<scr'+'ipt
src=\'[Link]
type=\'text/javascript\'></scr'+'ipt>');">
Такой код будет исполняться при посещении страницы пользователями,
работающими с Internet Explorer и Opera. И поскольку часть из них
прошла авторизацию, внедренный скрипт может выполнять от их имени
практически любые действия (например, создавать на форуме множест-
во бессмысленных сообщений). Также при помощи внедренного в стра-
ницу JavaScript-кода злоумышленник может отсылать cookies пользова-
телей своему серверному скрипту, который будет сохранять эти данные.
Таким образом он вполне может получить пароль администратора ата-
куемого ресурса. Поэтому при разработке различных сетевых сервисов
не стоит забывать и о таких возможностях использования URL типа
javascript.
200 Часть II. Решение типовых задач программирования скриптов
Напоследок рассмотрим неочевидный на первый взгляд способ приме-
нения URL типа javascript — в адресной строке браузера. Браузер будет
интерпретировать URL типа javascript, введенный в адресную строку,
как и любой другой URL, однако при этом будет исполнен содержащий-
ся в нем JavaScript-код. Примечательно то, что код из адресной строки
исполняется в контексте активного окна браузера. То есть, если в актив-
ном окне загружен некоторый документ, код из адресной строки полу-
чает доступ ко всей иерархии объектов, связанных с этим окном и доку-
ментом. Эту особенность часто используют для совершения некоторых
шаблонных действий с загруженной в браузер страницей. Иногда это
оказывается весьма полезным. Приведем несколько примеров.
Вероятно, вы посещали в Интернете сайты, на страницах которых "от-
ключено" контекстное меню (при щелчке правой кнопкой мыши вместо
меню появляется либо окно сообщения, либо вообще ничего не проис-
ходит). Следующий URL типа javascript, активизированный в адресной
строке, "включает" меню.
javascript: var _f = function(w){try{var d=[Link];
var b=[Link];
[Link]=[Link]=[Link]=[Link]=
null; }catch(e){}}; _f(window); if([Link]) for(i=0;
i < [Link]; i++) _f([Link][i]);
Встречаются страницы, при закрытии которых открывается еще не-
сколько окон браузера либо окно с тем же документом. Создание таких
popup-окон обычно производится в обработчиках событий закрытия
страницы. Следующий URL типа javascript содержит код, устанавли-
вающий в null соответствующие обработчики.
javascript: var d = document; var b = [Link];
void([Link]=[Link]=[Link]=[Link]=
null);
Также может оказаться полезным приведенный далее URL типа java-
script. Он выводит в окне сообщения значения всех cookies, сопостав-
ленных документу.
javascript:alert([Link](";").join("\n"));
Это лишь несколько примеров, демонстрирующих возможности подоб-
ного применения URL типа javascript. Однако случаев, в которых можно
использовать данный подход, гораздо больше: получение HTML-раз-
метки документа, сгенерированного скриптом, снятие "псевдозащиты"
от копирования, которую так любят придумывать начинающие Web-
Ãëàâà 9. Протоколы mailto и javascript 201
мастера, и т. д. Многие разработчики имеют в своем арсенале набор та-
ких URL для выполнения типовых действий с произвольным доку-
ментом.
Генерация изображений
через протокол javascript
Чуть ранее уже упоминалось, что при помощи использования URL типа
javascript можно формировать произвольные графические изображения.
К сожалению, данный метод генерирования изображений не документи-
рован. К тому же, он позволяет создавать только монохромные растры
(по крайней мере, автору не удалось найти способ сформировать цвет-
ное или полутоновое изображение). Эта возможность поддерживается
практически всеми браузерами (в Internet Explorer, правда, метод пере-
стает работать в ОС Windows XP SP2).
Итак, для того чтобы URL типа javascript служил источником данных
изображения, содержащийся в нем код должен вычисляться в строку
следующего вида:
"#define t_width X\n#define t_height Y\nstatic char t_bits[] =
{DATA}"
Здесь X — ширина, Y — высота изображения в пикселах, DATA — набор
элементов массива t_bits, представляющий собой данные битовой кар-
ты. Величина X должна быть кратна 8. Элементы массива t_bits долж-
ны быть представлены шестнадцатеричными числами в диапазоне
0x00—0xFF. Поскольку изображение монохромное, каждый байт дан-
ных (каждый элемент массива t_bits) описывает 8 пикселов растра.
Далее приведен несколько сокращенный фрагмент примера examples\09
\ex_9_07.htm, находящегося на компакт-диске, который демонстрирует
применение URL типа javascript для формирования изображений.
...
<script type="text/javascript">
var bgURL="#define t_width 8\n#define t_height 8\nstatic char
t_bits[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}";
var imgURL = "#define t_width 8\n#define t_height 8\nstatic char
t_bits[] = {0xFF,0x81,0x81,0x81,0x81,0x81,0x81,0xFF}";
</script></head>
<body background="javascript:bgURL;">
<img src="javascript:imgURL;" width="64" height="64">
...
202 Часть II. Решение типовых задач программирования скриптов
Как можно видеть, в этом примере с помощью URL типа javascript зада-
ется изображение фона страницы, а также создается внедренное в доку-
мент изображение.
Поскольку данный метод генерирования картинок не документирован,
его не стоит широко использовать на практике. Однако, вероятно, вам
захочется поэкспериментировать с такой возможностью. Очевидно,
формирование подобных URL типа javascript — весьма сложное заня-
тие. Поэтому автор написал скрипт, позволяющий создавать динамиче-
ские изображения простым и естественным способом. Скрипт содер-
жится в файле examples\Lib\[Link] и представляет собой реализацию
объекта CCanvas. Объекты типа CCanvas эмулируют графическую по-
верхность и предоставляют набор методов для "рисования" на ней. Ав-
тор реализовал рисование только нескольких графических примитивов
(точка, отрезок, закрашенный и не закрашенный прямоугольник), одна-
ко вы можете самостоятельно реализовать и другие методы, по своему
усмотрению. Мы не будем рассматривать здесь "устройство" этого
скрипта все по той же причине малой практической значимости. Однако
вы можете легко начать его применение, ознакомившись с описанием
методов объекта CCanvas, приведенным в файле examples\Lib\[Link],
а также примером examples\09\ex_9_08.htm генерации изображения с
помощью объекта CCanvas (см. компакт-диск).
ЧАСТЬ III
Создание
динамических эффектов
Глава 10. Управление интерфейсом браузера
Глава 11. Работа с формами
Глава 12. Устанавливаем виртуальный сервер
Глава 13. Создание анимационных эффектов
В этой части мы будем решать задачи, в той или иной степени свя-
занные с динамическими преобразованиями Web-страниц, т. е.
скрипты, создаваемые нами, будут каким-либо образом изменять со-
держимое или структуру документа. Мы рассмотрим работу с окном
браузера, формами и изображениями, разработаем множество скрип-
тов, создающих анимационные эффекты. Большое внимание будет
уделено принципам организации дружественного пользовательского
интерфейса.
ГЛАВА 10
Управление интерфейсом
браузера
В этой главе мы рассмотрим способы управления окном браузера и от-
дельными его составляющими. По своей сути, задачи, рассматриваемые
здесь, очень просты, а их решения, в основном, сводятся к использова-
нию методов и свойств объекта window. Однако необходимость в неко-
торых типовых операциях, описываемых далее (например, создание
окон с заданными параметрами), может возникать весьма часто, поэто-
му данная тема, безусловно, заслуживает внимания.
Прежде всего, необходимо подчеркнуть некоторую специфику работы с
параметрами окна браузера средствами клиентских скриптов. Во-
первых, объект window, упоминавшийся ранее, принадлежит объектной
модели браузера (см. главу 4), на которую не существует какого-либо
стандарта и которая специфична для каждого конкретного пользова-
тельского агента. Во-вторых, браузер является частью рабочей среды
пользователя, и манипулирование его параметрами может нарушать це-
лостность и логичность пользовательского интерфейса. В-третьих, брау-
зер может быть сконфигурирован таким образом (именно во избежание
изменения интерфейса сценариями Web-страниц), что действия, произ-
водимые клиентским скриптом и направленные на изменение его пара-
метров, не принесут ожидаемого эффекта. Именно поэтому изменять
параметры окон браузера следует только для решения специфических
задач.
Создание окон браузера
с заданными параметрами
Бывают случаи, когда клиентский сценарий должен в произвольный мо-
мент времени инициализировать создание нового окна браузера с за-
206 Часть III. Создание динамических эффектов
грузкой в него некоторого документа. При этом часто желательно, что-
бы создаваемое окно обладало определенным набором свойств (разме-
ры, положение на экране и т. д.). Такие окна обычно называют всплы-
вающими (popup). Они могут быть созданы методами [Link], либо
[Link]. Прототипы этих методов таковы:
[Link](sUrl [, sName] [, sFeatures] [, bReplace])
[Link]([sURL] [, sName] [, sFeatures] [, bReplace])
Оба метода вызывают загрузку документа с заданным URL в окно с ука-
занным именем. В ряде случаев (см. далее) создается новое окно.
Метод [Link] может быть использован двумя способами. Он
либо "открывает" существующий документ для записи в него данных
при помощи методов write или writeln (в этом случае задаются только
первые два параметра), либо работает как метод [Link]. В первом
случае метод [Link] возвращает ссылку на новый объект доку-
мента, во втором — на объект созданного окна.
Метод [Link] инициирует загрузку документа в окно с указанным
именем. При необходимости (в зависимости от значения параметра
sName) такое окно будет создано. Метод возвращает ссылку на новый
объект window. Параметры методов [Link] и [Link] имеют
одинаковое назначение. Их описание приведено в табл. 10.1.
Таблица 10.1. Параметры методов [Link]
и [Link]
Параметр Описание
sUrl Строка, определяющая URL документа, загружаемого в
создаваемое окно. Если параметр не указан, будет загру-
жен документ about:blank
sName Строковое значение, задающее имя создаваемого окна.
Данный параметр может являться либо произвольным име-
нем, либо одним из предопределенных значений. Если за-
дано произвольное имя окна, то в дальнейшем это имя мо-
жет использоваться как значение атрибута target элемен-
тов A, FORM, либо при повторном вызове метода open.
В этом случае (если окно с данным именем уже существу-
ет) указанный документ будет загружен в него
sFeatures Строка опций создаваемого окна. Представляет собой
список разделенных запятыми пар вида название_
опции=значение
Ãëàâà 10. Управление интерфейсом браузера 207
Таблица 10.1 (окончание)
Параметр Описание
bReplace Параметр логического типа, определяющий, будет ли при
загрузке документа в уже существующее окно создаваться
новая запись в истории посещений страниц, сопоставлен-
ной данному окну (работа с этим списком возможна через
объект [Link]). Если значение false, в список
будет добавлена новая запись, если значение равно true,
URL загружаемого документа заменит последнюю запись
В табл. 10.2 приведен список возможных предопределенных значений
параметра sName.
Таблица 10.2. Значения параметра sName
Значение Описание
_blank Будет создано новое, неименованное окно
_media Доступно в Internet Explorer 6 и выше. Документ будет загру-
жен в панель Media браузера
_parent Если метод open выполняется в скрипте документа, загру-
женного во фрейм, новый документ будет открыт в родитель-
ском окне фрейма, иначе этот параметр интерпретируется
как _self
_search Доступно в Internet Explorer 5 и выше. Новый документ загру-
жается в панель поиска браузера
_self Новый документ загружается в текущее окно или фрейм
_top Новый документ загружается в текущее окно верхнего уровня
Теперь рассмотрим опции, которые возможно указать в строке
sFeatures (табл. 10.3).
Таблица 10.3. Значения параметра sFeatures
Опция Описание
left Определяет позицию левого края окна в пикселах
top Определяет позицию верхнего края окна в пикселах
width Задает ширину окна в пикселах
208 Часть III. Создание динамических эффектов
Таблица 10.3 (окончание)
Опция Описание
height Задает высоту окна в пикселах
resizable Определяет, возможно ли изменение размера окна поль-
зователем. Значение по умолчанию — "yes"
fullscreen Позволяет отобразить окно браузера в "полноэкранном"
режиме. Работает только в Internet Explorer. Значение по
умолчанию — "no"
channelmode Работает только в Internet Explorer. Позволяет открыть
браузер развернутым на весь экран (в theater-режиме) и
отобразить "панель каналов" (channel band). Значение по
умолчанию — "no". Допускается использование данной
опции совместно с опцией fullscreen
titlebar Работает только в Internet Explorer и только в случае
HTML-приложений или диалоговых окон. Определяет,
должен ли отображаться заголовок окна браузера. Зна-
чение по умолчанию — "yes"
toolbar Определяет, должна ли быть отображена основная па-
нель инструментов браузера. Значение по умолчанию —
"yes"
menubar Определяет, должно ли быть отображено меню окна
браузера. Значение по умолчанию — "yes"
status Определяет, должна ли быть отображена строка состоя-
ния в окне браузера. Значение по умолчанию — "yes"
location Определяет, должна ли быть отображена в окне браузе-
ра строка для ввода URL для загрузки другого документа.
Значение по умолчанию — "yes"
directories Определяет, должна ли быть отображена в окне браузе-
ра панель, содержащая кнопки ссылок, закладок и т. д.
Значение по умолчанию — "yes"
scrollbars Определяет, должны ли быть доступны в окне браузера
полосы прокрутки. Значение по умолчанию — "yes"
Значениями опций left, top, width, height должны являться целые чис-
ла. Тип остальных опций — логический. Допускается указание "yes"
или "1" в качестве значения истинности, и "no", либо "0" в качестве
ложного значения. Если при вызове метода open параметр sFeatures не
Ãëàâà 10. Управление интерфейсом браузера 209
указан, используются значения по умолчанию для всех опций. Если па-
раметр sFeatures определен, то все неуказанные в данной строке опции
считаются установленными в значение "no".
Рассмотрим несколько примеров создания окон с заданными парамет-
рами (для простоты будем везде использовать метод open объекта
window). Самый простой способ создать popup-окно — использовать
метод open, указав только первый параметр. Например:
[Link]("[Link]
Данный вызов откроет окно, имеющее размеры, расположение, набор
панелей инструментов, определяемые настройками браузера и парамет-
рами графической среды. Однако часто возникает необходимость созда-
вать popup-окна заданного размера. Такие окна применяют для органи-
зации различного рода быстрых голосований, рейтингов (например, для
оценки качества постов на форуме). Обычно в этом случае popup-окно
открывается javascript-ссылкой, а URL загружаемого документа являет-
ся адресом серверного скрипта голосований (через параметры в URL
скрипту передаются необходимые данные). Назначение такого окна —
послать данные на сервер и закрыться сразу же после получения ответа,
поэтому его размер делают достаточно малым. Используем опции width
и height:
[Link]("[Link] "_blank",
"width=400,height=200");
Исполнение данного кода приведет к созданию окна шириной 400, вы-
сотой 200 пикселов без меню, строки состояния, полос прокрутки, пане-
лей инструментов и возможности изменять его размер. Для того чтобы
"включить" какие-либо элементы управления, необходимо явно исполь-
зовать соответствующие опции:
[Link]("[Link] "_blank",
"left=16,top=16,width=400,height=200,resizable=1,toolbar=1,
menubar=1,status=1,location=1,directories=1,scrollbars=1");
Вы можете самостоятельно поэкспериментировать с опциями, исполь-
зуемыми при создании popup-окон. Для более комфортного изучения
данной темы автором написан небольшой скрипт (генератор кода
создания popup-окон), представленный в примере examples\10
\ex_10_01.htm, который поможет вам быстро сгенерировать код в соот-
ветствии с выбранными опциями и тут же просмотреть результат.
210 Часть III. Создание динамических эффектов
Изменение параметров
окна браузера
Начиная этот раздел главы, следует сразу отметить, что динамическое
изменение параметров окна браузера мало применимо к реальным зада-
чам. К тому же, использование различных эффектов, связанных с по-
добными изменениями, отнюдь не является хорошим стилем при созда-
нии Web-сайтов. Однако задачей данной части главы является освеще-
ние соответствующих возможностей, а что с ними делать — решать вам.
Примеры перемещения
и изменения размеров окна браузера
Должно быть, существует не так много задач, для решения которых тре-
бовалось бы переместить или изменить размер окна браузера.
В основном такая необходимость может возникать при создании HTML-
приложений (HTML applications) для Internet Explorer. Также можно
пытаться развернуть окно браузера до требуемого размера, если страни-
ца оптимизирована под высокое разрешение экрана (правда, это являет-
ся крайне плохим стилем).
Для обеспечения возможности манипулирования положением и разме-
ром окон документов практически все современные браузеры поддер-
живают методы moveTo, resizeTo, moveBy, resizeBy объекта window. Вот
их прототипы:
[Link](iX, iY)
[Link](iX, iY)
[Link](iWidth, iHeight)
[Link](iX, iY)
Все параметры этих методов являются целыми числами, задающими
координаты в пикселах. Методы moveTo и moveBy производят абсолют-
ное и относительное, соответственно, позиционирование окна на экране.
Параметрами метода moveTo являются значения координаты точки,
в которую будет перемещен левый верхний угол окна браузера. Значе-
ниями параметров метода moveBy являются величины, на которые левый
верхний угол окна браузера будет смещен относительно его текущего
положения. Эти методы перемещают окно, не меняя его размеров.
Методы resizeTo и resizeBy позволяют изменять размеры окна браузе-
ра, не меняя его позиции на экране (координаты левого верхнего угла).
Ãëàâà 10. Управление интерфейсом браузера 211
Метод resizeTo устанавливает ширину и высоту окна браузера, равны-
ми значениям, переданным ему параметрам iWidth и iHeight. Метод
resizeBy изменяет ширину и высоту окна относительно текущих значе-
ний на величины iX и iY.
Кроме описанных методов, во многих браузерах (таких как Opera,
Mozilla, Netscape Navigator) существует возможность получения и изме-
нения координат и размеров окна, используя такие свойства объекта
window, как: screenX (координата левого края окна), screenY (координа-
та верхнего края окна), outerWidth (ширина окна), outerHeight (высота
окна). В некоторых браузерах (например, Konqueror) эти свойства дос-
тупны только для получения значений.
Теперь давайте рассмотрим примеры. Здесь нужно сделать небольшое
замечание. Поскольку браузер может не поддерживать методы измене-
ния размеров и положения окна, но поддерживать свойства screenX,
screenY, outerWidth и outerHeight, в файле examples\10\[Link] со-
держится реализация методов moveTo и resizeTo через эти свойства.
Данный скрипт подключается в примерах ex_10_02.htm, ex_10_03.htm и
ex_10_04.htm, также находящихся в папке examples\10 на компакт-
диске. Его код крайне прост, поэтому здесь не приводится.
Простейшей задачей, для решения которой необходимо перемещение и
изменение размеров окна браузера, может являться разворачивание окна
браузера до размеров рабочей области графической среды или размеров
экрана (в зависимости от того, определение каких параметров поддер-
живает браузер). Пример сценария, осуществляющего это действие,
приведен в файле examples\10\ex_10_02.htm. Скрипт содержит всего
одну функцию FitWindowToScreen, осуществляющую позиционирование
окна в точку с координатами (0, 0) и изменение его размеров. Размеры
окна выбираются равными размерам рабочей области графической сре-
ды, если поддерживаются свойства объекта screen availWidth и
availHeight, иначе — разрешению монитора, если поддерживаются
свойства width и height (подробнее см. разд. "Определение параметров
дисплея" в главе 5).
function FitWindowToScreen()
{
...
// код определения значений nWidth и nHeight
...
[Link](0, 0);
[Link](nWidth, nHeight);
}
212 Часть III. Создание динамических эффектов
Простой пример, иллюстрирующий только перемещение окна браузера,
содержится в файле examples\10\ex_10_03.htm. В нем используется ме-
тод [Link]. Перемещение окна осуществляется пользователем
путем нажатия кнопок формы в документе.
Несколько более сложный пример, иллюстрирующий применение мето-
да resizeTo, приведен в файле examples\10\ex_10_04.htm. В нем осуще-
ствляется "плавное разворачивание" окна до заданного размера. Идея
состоит в том, что метод resizeTo вызывается в обработчике событий
таймера. В этом же обработчике происходит наращивание значений пе-
ременных ширины и высоты окна, контроль этих значений и остановка
таймера в случае достижения ими заданных значений:
function OnTimer()
{
[Link]([Link]([Link]),
[Link]([Link]));
[Link] += [Link];
[Link] += [Link];
...
// код контроля значений свойств [Link],
// [Link] и остановка таймера в случае
// достижения ими значений, равных
// [Link] или [Link]
...
}
Объект oParams инкапсулирует свойства, содержащие начальные, теку-
щие и конечные значения параметров, используемых при работе скрип-
та. Начальные и конечные значения задаются при создании этого объек-
та в литеральной нотации:
var oParams =
{
nTop : 10, // позиция окна по X
nLeft : 10, // позиция окна по Y
nWidthStart : 128, // начальная ширина окна
...
Изменяя эти параметры, вы можете сконфигурировать скрипт по своему
усмотрению.
Ãëàâà 10. Управление интерфейсом браузера 213
Изменение текста заголовка, строки состояния,
управление прокруткой документа
Теперь рассмотрим еще несколько примеров внесения изменений в от-
дельные части окна браузера. Начнем с заголовка окна.
Изменение текста заголовка окна браузера
Как вы знаете, в разделе HEAD гипертекстового документа может присут-
ствовать элемент TITLE, содержащий текст названия документа. Обычно
этот текст выводится в заголовке окна браузера. Значение заголовка до-
кумента доступно клиентскому сценарию для получения и изменения
через свойство [Link]. При изменении этого значения, как пра-
вило, соответствующим образом изменяется и содержимое заголовка
окна браузера. Таким образом, используя данное свойство, можно соз-
давать несложные динамические эффекты на основе периодического
изменения текста заголовка окна.
В качестве примера динамического текста в заголовке окна браузера
автор решил использовать вывод текущего времени. Скрипт, осуществ-
ляющий такой вывод, содержится в файле examples\10\ex_10_05.htm на
компакт-диске. Он крайне прост и по своей сути аналогичен скрипту
вывода времени на Web-странице, рассмотренному в разд. "Вывод те-
кущего времени на Web-странице" главы 6 (см. также пример examples\06
\ex_6_04.htm). В обработчике событий таймера в скрипте при помощи
метода Format объекта CVDate (библиотека examples\Lib\[Link]) фор-
мируется текстовое представление текущего времени, а затем это значе-
ние присваивается свойству [Link]:
function OnTimer()
{
var oDate = new CVDate();
[Link] = [Link](([Link]() % 2) ?
"%H %M" : "%H:%M");
}
[Link](OnTimer, 1000);
Изменение текста строки состояния окна браузера
Надо сказать, что изменение текста строки состояния браузера доста-
точно широко используется в скриптах на Web-страницах реально дей-
ствующих сайтов. Установить текст строки состояния можно, присвоив
строковое значение свойству [Link]. Так, часто Web-мастера
214 Часть III. Создание динамических эффектов
пытаются скрыть реальные URL ссылок рекламного характера, исполь-
зуя конструкции наподобие этой:
<A href="[Link]
onmouseover="[Link]='';return true;">
текст ссылки</A>
Как и в случае с заголовком окна браузера, периодически изменяя текст
в строке состояния, можно создавать несложные динамические эффек-
ты. В файле examples\10\ex_10_06.htm содержится пример скрипта,
осуществляющего вывод текущего времени в строку состояния. Он аб-
солютно аналогичен примеру examples\10\ex_10_05.htm, рассмотренно-
му в предыдущем разделе, только вместо свойства [Link] ис-
пользуется [Link].
Более интересным примером применения свойства [Link] явля-
ется скрипт, выводящий "бегущую строку" в строке состояния браузера.
Он представлен в файле examples\10\ex_10_07.htm. Принцип создания
"бегущей строки" такого рода состоит в периодическом выводе в одной
и той же позиции (в нашем случае — просто выводе в строку состояния)
фрагмента текста фиксированной длины, "вырезанного" из строки, со-
держащей весь "бегущий текст". Позиция, начиная с которой "вырезает-
ся" выводимая подстрока, увеличивается на 1 при каждой замене текста
и обнуляется при достижении конца выводимого текста.
В скрипте, внедренном в файл examples\10\ex_10_07.htm, вывод текста в
строку состояния и изменение значения позиции начала выводимого
фрагмента осуществляется в обработчике событий таймера:
function OnTimer()
{
[Link] = [Link](nPos, nPos + nLen);
if(++nPos > ([Link] - nLen))
nPos = 0;
}
Здесь переменная nPos содержит значение позиции начала выводимого
фрагмента текста, nLen определяет длину этого фрагмента (длину види-
мой части "бегущей строки"), а strSample содержит весь текст "бегущей
строки". Эти переменные инициализируются в начале скрипта. Далее,
переменная strSample дополняется вначале и в конце пробелами, коли-
чество которых равно nLen (для того чтобы присутствовал эффект "вы-
езжания" и "заезжания" текста), и запускается таймер.
Ãëàâà 10. Управление интерфейсом браузера 215
var str = '';
for(var i = 0; i < nLen; i++)
str += " ";
strSample = str + strSample + str;
[Link](OnTimer, 150);
Подобным образом "бегущую строку" можно реализовать и просто на
Web-странице.
Управление прокруткой документа
Последнее, что мы рассмотрим в этой главе — управление позицией
прокрутки окна браузера. Задать позицию прокрутки можно с помощью
методов объекта window:
[Link](iX, iY)
[Link](iX, iY)
[Link](idX, idY)
Методы scroll и scrollTo имеют одинаковую функциональность. Они
прокручивают документ в окне браузера до позиции, указанной абсо-
лютными значениями параметров iX и iY. Параметр iX определяет гори-
зонтальную, а параметр iY — вертикальную позицию (в пикселах) про-
крутки. Метод scrollBy прокручивает документ по горизонтали и вер-
тикали относительно текущей позиции прокрутки на количество
пикселов, указанное параметрами idX и idY.
Пожалуй, применений данным методам можно найти не так уж и много.
На некоторых форумах с их помощью организовывают автоматическую
прокрутку страниц вниз после их загрузки. В качестве другого примене-
ния этих методов можно предложить циклическую автоматическую
прокрутку документа во встроенном фрейме. Это можно использовать,
например, в качестве компактной ленты новостей. Как пример, здесь мы
рассмотрим именно циклическую автоматическую вертикальную про-
крутку документа в окне браузера.
Скрипт, осуществляющий автоматическую вертикальную прокрутку
документа, представлен в файле examples\10\ex_10_08.htm. Принцип его
работы прост. Он заключается в определении значения текущей верти-
кальной позиции прокрутки документа, его увеличении и установке но-
вой позиции прокрутки с помощью метода [Link]. Эти действия
выполняются в обработчике событий таймера.
Значение вертикальной позиции прокрутки документа возвращается
функцией GetVScrollPos. Оно определяется как значение свойства
216 Часть III. Создание динамических эффектов
[Link] или [Link] (в зависимости от
того, какое из них поддерживается браузером):
function GetVScrollPos()
{
if([Link])
return [Link];
else
return [Link];
}
Код обработчика событий таймера выглядит следующим образом:
function OnTimer()
{
var nPos = GetVScrollPos();
[Link](0, nPos + 1);
if(nPos == GetVScrollPos())
[Link](0, 0);
}
Обратите внимание на то, как определяется факт достижения макси-
мального значения прокрутки. После вызова метода [Link] про-
изводится повторное получение значения ее вертикальной координаты с
помощью GetVScrollPos, и, если вследствие вызова [Link] эта
координата не изменилась, документ прокручивается к началу. Так дос-
тигается цикличность прокрутки.
Возможно, многие рассмотренные в данной главе примеры и не обла-
дают достаточной практической значимостью, однако они неплохо ил-
люстрируют некоторые общие принципы создания динамических эф-
фектов с помощью клиентских JavaScript-сценариев.
ГЛАВА 11
Работа с формами
Как вы помните, в нескольких примерах предыдущих глав уже были
использованы формы. До сих пор мы не уделяли им особого внимания.
Но в настоящей главе формы становятся объектом всестороннего рас-
смотрения. Прежде чем непосредственно приступить к изучению прие-
мов работы с формами, давайте выясним, в чем, собственно, состоят
особенности этой работы, и почему данному вопросу посвящена от-
дельная глава книги. Для этого очертим круг задач, решаемых путем
применения клиентских сценариев в отношении форм.
Как вы знаете, основная особенность форм состоит в том, что они явля-
ются интерактивной составляющей Web-страниц. Фактически, формы
представляют собой раздел документа, в котором кроме обычного ги-
пертекста содержатся также элементы управления (текстовые поля, спи-
ски, флажки и т. д.), собственно, и обеспечивающие интерактивность.
Формы предназначены для обеспечения возможности ввода данных
пользователем и отправки их некоторому обработчику. Таким обработ-
чиком в большинстве случаев является скрипт на удаленном сервере.
Как правило, этот скрипт производит некоторые действия, зависящие от
переданных ему данных, а затем возвращает браузеру ответ в виде ги-
пертекстового документа, содержащего результаты произведенных им
действий (либо сообщение о неудачном завершении операции). Часто
невозможность выполнения обработчиком заданных действий связана с
передачей ему некорректных данных, ошибочно введенных пользовате-
лем в форму. Обычно, в этом случае (после проверки данных) скрипт-
обработчик генерирует документ, содержащий сообщение о допущен-
218 Часть III. Создание динамических эффектов
ной пользователем ошибке. Таким образом, в случае совершения оши-
бок пользователь ставится перед необходимостью повторного ввода
данных. Также, между моментом отправки пользователем формы и по-
лучением им сообщения об ошибке, сгенерированного обработчиком,
может проходить значительное время (особенно это актуально при ис-
пользовании медленных каналов связи).
С помощью применения JavaScript-сценариев становится возможным
производить контроль данных, введенных пользователем в форму на
стороне клиента (в браузере), например, непосредственно перед их от-
сылкой на удаленный сервер. В случае обнаружения ошибки, клиент-
ский скрипт может сообщить о ней и отменить отправку формы. При
этом пользователь практически мгновенно узнает об ошибке и сможет
легко скорректировать данные. Контроль корректности данных — одна
из наиболее широко распространенных задач, решаемых путем приме-
нения клиентских сценариев в отношении форм.
Изначально компания Netscape разработала и внедрила JavaScript
в свой браузер именно как средство проверки форм. Поэтому данное
применение клиентских сценариев, пожалуй, еще и наиболее естест-
венное.
Ко второму типу задач, связанных с формами и решаемых при помощи
скриптов, относятся задачи создания дружественного пользовательского
интерфейса, т. е. управления состоянием отдельных элементов формы в
процессе ввода в нее данных пользователем. Дело в том, что достаточно
часто существуют логические взаимосвязи между элементами и группа-
ми элементов форм (так, значение, установленное для некоторого эле-
мента, может определять семантику использования другого элемента
или группы элементов). Реализовать эти взаимосвязи физически, в виде
динамически изменяющихся состояний и значений элементов форм
возможно только при помощи сценариев.
Надо отметить, что задачи создания дружественного интерфейса и зада-
чи контроля корректности данных в формах очень часто пересекаются.
Конечно, реализация алгоритмов, позволяющих решить эти задачи, тре-
бует от Web-программистов дополнительных усилий, однако такой под-
ход, безусловно, намного повышает эксплуатационные характеристики
тех частей Web-сервисов, в которых используются формы. Итак, основ-
ными темами данной главы будут являться способы проверки данных и
создание дружественного пользовательского интерфейса форм.
Ãëàâà 11. Работа с формами 219
Работа с формами
средствами JavaScript
В первую очередь, давайте рассмотрим основные возможности и прин-
ципы работы с формами при помощи JavaScript-сценариев.
Для полного понимания HTML- и JavaScript-кода примеров данной
главы вы должны хорошо представлять себе принципы применения
форм в гипертекстовых документах. Получить соответствующую ин-
формацию вы можете в разделе "Формы" спецификации HTML, дос-
тупной на прилагаемом к книге компакт-диске.
Формы в объектной модели документа
Формы вводятся в гипертекстовый документ при помощи элемента
FORM, который обычно является контейнером для элементов HTML-
разметки, элементов управления и элементов группировки других эле-
ментов управления. Спецификация DOM декларирует интерфейс
HTMLFormElement, который должен реализовываться объектами дерева
документа, соответствующими элементам FORM. Для всех HTML-
элементов, определяющих элементы управления формы и их части, в
спецификации DOM также декларированы соответствующие интерфей-
сы. Табл. 11.1 описывает назначение этих элементов и их соответствие
DOM-интерфейсам.
Таблица 11.1. HTML-элементы форм
и их соответствие DOM-интерфейсам
Типы элементов управления
Элемент Интерфейс
формы
INPUT HTMLInputElement Однострочные поля для ввода тек-
ста или пароля, нажимаемые кнопки
(push buttons), графические кнопки,
кнопки-флажки (check boxes), кнопки
с зависимой фиксацией (radio
buttons), элементы для выбора фай-
лов (обычно представлены как тек-
стовое поле с кнопкой), скрытые
поля
BUTTON HTMLButtonElement Нажимаемые кнопки (push buttons)
220 Часть III. Создание динамических эффектов
Таблица 11.1 (окончание)
Типы элементов управления
Элемент Интерфейс
формы
LABEL HTMLLabelElement Текстовые метки для других эле-
ментов управления
TEXTAREA HTMLTextAreaElement Многострочные поля для ввода
текста
SELECT HTMLSelectElement Согласно спецификации HTML —
определяет меню. В современных
браузерах представляется выпа-
дающим списком (combo box) либо
многострочным списком с возмож-
ностью множественного выбора
элементов (list box)
OPTION HTMLOptionElement Определяет элемент выбора для
SELECT
OPTGROUP HTMLOptGroupElement Задает группу элементов OPTION
FIELDSET HTMLFieldSetElement Определяет элемент группировки
других элементов управления
формы
LEGEND HTMLLegendElement Задает заголовок для элемента
FIELDSET
Мы не будем подробно рассматривать здесь каждый из указанных выше
интерфейсов (полную информацию о них вы можете получить из спе-
цификаций DOM), однако далее по тексту при упоминании методов и
свойств объектов я буду также ссылаться на соответствующие атрибуты
и методы интерфейсов.
Методы доступа к объектам элементов
управления форм
Вам уже известен набор методов "общего назначения", позволяющих
получить доступ к произвольному объекту в иерархии объектов модели
документа (см. разд. "Способы доступа к объектам модели документа"
в главе 4). Обладая универсальностью, они, тем не менее, не слишком
удобны при работе с формами (например, в задачах, требующих перебо-
ра всех объектов элементов управления). Однако DOM предусматривает
Ãëàâà 11. Работа с формами 221
особый, простой и естественный способ получения ссылок на объекты
элементов управления форм.
Как уже говорилось, DOM-объект формы должен реализовывать интер-
фейс HTMLFormElement. Декларация данного интерфейса включает такие
атрибуты, как length и elements. В JavaScript они представлены как
свойства. Свойство length содержит числовое значение, указывающее
количество элементов управления в форме, а свойство elements являет-
ся коллекцией, позволяющей получать ссылку на эти элементы. Свойст-
во elements реализует декларированный в спецификации DOM уровня 1
интерфейс HTMLCollection:
interface HTMLCollection {
readonly attribute unsigned long length;
Node item(in unsigned long index);
Node namedItem(in DOMString name); };
Как видно из листинга, интерфейс HTMLCollection имеет атрибут
length. Его значением является количество элементов коллекции. Таким
образом, коллекция elements имеет свойство length, содержащее, фак-
тически, количество элементов управления формы. Методы item и
namedItem предназначены для получения доступа к элементам коллек-
ции. Метод item возвращает ссылку на элемент коллекции по его поряд-
ковому номеру, а метод namedItem — по имени элемента. Метод
namedItem работает следующим образом. Сначала среди элементов кол-
лекции ищется тот объект DOM, значение атрибута id которого совпа-
дает со значением параметра name, переданного методу. Если такой объ-
ект найден, метод namedItem возвращает ссылку на него. Если объект не
найден, поиск производится по критерию совпадения атрибута name
объектов коллекции со значением параметра name. Если объект не най-
ден, — возвращается null. Таким образом, свойство-коллекция
elements обеспечивает удобную возможность доступа к DOM-объектам
элементов управления форм.
Теперь рассмотрим примеры получения ссылок на объекты элементов
управления формы, используя коллекцию elements. В примерах будем
подразумевать, что документ содержит форму, заданную следующей
HTML-разметкой:
<FORM ID="ID_FORM">
<INPUT type="checkbox" name="oCheck1">
<INPUT type="radio" name="oRadio1" checked>
<INPUT type="radio" name="oRadio1" ID="oRadio1">
<INPUT type="radio" name="oRadio1">
</FORM>
222 Часть III. Создание динамических эффектов
В форме имеются три кнопки с зависимой фиксацией (радиокнопки, или
переключатели) и один флажок. Для всех радиокнопок указано одинако-
вое значение атрибута name (это используется для определения группы
радиокнопок — из них только одна сможет находиться в отмеченном
состоянии). В примерах кода далее подразумевается, что ссылка на
DOM-объект формы уже получена:
var oForm = [Link]('ID_FORM');
Применим метод item коллекции elements. Его обычно используют для
итерирования по множеству объектов элементов управления форм. На-
пример, следующий фрагмент кода отобразит имена HTML-элементов,
соответствующих каждому элементу управления формы:
for(var i = 0; i < [Link]; i++)
[Link]([Link](i).nodeName + "<br>");
Теперь используем метод namedItem. Его применение не вызывает про-
блем, если элемент, на который требуется получить ссылку, имеет уни-
кальное в пределах формы значение атрибута name. Например, для полу-
чения ссылки на DOM-объект флажка той же формы можно использо-
вать код:
var oCheck = [Link]("oCheck1");
Однако метод namedItem ведет себя по-разному в различных браузерах
(и, на мой взгляд, практически везде некорректно относительно специ-
фикации DOM), если в форме присутствует несколько элементов с оди-
наковым значением атрибута name, равным значению параметра, пере-
данному этому методу. В этом случае, в Internet Explorer и Opera метод
namedItem возвращает не ссылку на объект элемента управления, а мас-
сив ссылок на множество объектов элементов формы с указанным зна-
чением атрибута name. В браузерах Mozilla, Netscape возвращается
ссылка на единственный объект, однако этот объект — первый, найден-
ный в коллекции с указанным значением name (при этом не учитываются
атрибуты id элементов). Рассмотрим следующий вызов:
var oResult = [Link]("oRadio1");
В MSIE и Opera oResult будет являться массивом из 3-х элементов —
ссылок на объекты радиокнопок. В Mozilla и Netscape это будет ссылка
на объект первой радиокнопки (объект элемента INPUT с установленным
атрибутом checked). Заметьте, согласно спецификации, должна была
быть возвращена ссылка на вторую радиокнопку (элемент INPUT, для
которого атрибут id установлен в значение oRadio1).
Ãëàâà 11. Работа с формами 223
Надо сказать, что обычно элементы коллекции elements представлены
также в виде свойств этого объекта. Допускается их использование в
нотации доступа к свойствам объекта или к элементам массива (это
справедливо, как минимум, для современных версий браузеров MSIE,
Opera, Mozilla, Netscape, Firefox). Например, получить ссылку на объект
флажка нашей формы можно и такими методами:
var oCheck_ref0 = [Link][0];
var oCheck_ref1 = [Link]["oCheck1"];
var oCheck_ref2 = [Link].oCheck1;
Безусловно, эти способы более удобны в использовании (хоть и не яв-
ляются частью стандарта DOM). Также их применение имеет еще одно
преимущество. В отличие от метода namedItem, в случае, если в форме
присутствует несколько элементов с одинаковым значением атрибута
name, равным значению индекса или имени атрибута коллекции, эти
способы возвращают в разных браузерах результаты, допускающие од-
нозначное дальнейшее применение. Посмотрим, что выводят следую-
щие строки кода (они равнозначны) в различных браузерах:
[Link]([Link].oRadio1);
[Link]([Link]["oRadio1"]);
В табл. 11.2 представлен вывод, сгенерированный каждой из строк.
Таблица 11.2. Выводы, сгенерированные разными обозревателями
Браузер Результат
Internet Explorer [object]
Opera [object ElementArray]
Mozilla [object NodeList]
Netscape [object HTMLCollection]
Как видно, тип возвращаемых объектов различен в разных браузерах.
И только Mozilla и Netscape возвращают объекты, реализующие интер-
фейсы DOM. Однако, фактически, возвращаемые объекты представляют
собой коллекции, содержащие ссылки на множество объектов формы с
одинаковым значением атрибута name (в нашем случае — "oRadio1"), и
доступ к этим объектам возможен при помощи того же синтаксиса дос-
тупа к элементам массива. Например, получить ссылку на объект второй
радиокнопки в форме можно одним из следующих способов:
224 Часть III. Создание динамических эффектов
var oRadio1_ref0 = [Link].oRadio1[1]; // способ 1
var oRadio1_ref1 = [Link]["oRadio1"][1]; // способ 2
Таким образом, хотя данный метод доступа к объектам элементов
управления формы и является частью специфической объектной модели
браузеров, его применение кроссбраузерно в отличие от применения
метода namedItem.
Последний факт, о котором здесь стоит сказать, состоит в том, что объ-
екты, являющиеся элементами коллекции elements, так же, как правило,
отображаются в свойства DOM-объекта формы. То есть, обычно работа-
ет и такой код:
var oRadio1_ref0 = oForm.oRadio1[1];
var oRadio1_ref1 = oForm["oRadio1"][1];
Принципы динамической обработки форм
Ранее в этой главе были указаны основные типы задач (контроль кор-
ректности данных, создание дружественного пользовательского интер-
фейса), решаемых при помощи клиентских сценариев применительно к
формам. Характерной особенностью скриптов данного типа является
обилие различных манипуляций, производимых с элементами управле-
ния форм: получение, установка значений, изменение их состояния. Да-
лее в этой главе будут описаны основные принципы, а затем приведены
практические решения упомянутых задач. Но перед этим мы рассмот-
рим сами приемы манипулирования элементами управления форм.
Манипулирование элементами управления форм
Спецификация HTML определяет несколько типов управляющих эле-
ментов форм: однострочные, многострочные текстовые поля, кнопки
(buttons), кнопки с зависимой фиксацией (радиокнопки, или переключа-
тели, radio buttons), флажки (check boxes), меню, а также элементы вы-
бора файлов, скрытые элементы, объекты. Далее, в силу специфики об-
суждаемых в этой главе задач, имеет смысл уделять внимание только
"классическим" элементам управления (текстовым полям, кнопкам,
флажкам, радиокнопкам, меню). Скрытые элементы не являются инте-
рактивными (пользователь не может взаимодействовать с ними напря-
мую), они служат лишь для передачи некоторых значений обработчику
формы. Элементы выбора файлов обычно визуально представляются как
комбинация текстового поля и кнопки, и работа с ними почти не отлича-
ется от работы с текстовым полем. Объекты могут представлять собой
что угодно (как иметь, так и не иметь пользовательский интерфейс, а
Ãëàâà 11. Работа с формами 225
программный интерфейс взаимодействия с объектом определяется им
самим).
Элемент управления "меню" предоставляет пользователю механизм
одно- или многоальтернативного выбора. Спецификация HTML никак
не определяет аспекты визуального представления элементов управ-
ления форм, но в современных браузерах элемент одноальтернатив-
ного выбора обычно представляется выпадающим списком (combo
box), а элемент многоальтернативного выбора — списком (list box).
Далее я часто буду использовать эти термины.
Текстовые поля
Как уже было сказано, форма может содержать элементы управления
для ввода текста. Элемент INPUT с атрибутом type, установленным в
значение "text", определяет элемент управления, допускающий ввод
одной строки текста. Элемент TEXTAREA соответствует текстовому полю
для ввода текста из нескольких строк.
Элемент INPUT с атрибутом type, установленным в значение
"password", определяет элемент управления для ввода пароля. Та-
кой элемент управления ничем не отличается от обычного одностроч-
ного текстового поля за тем лишь исключением, что введенный в него
текст отображается в виде каких-либо символов-заполнителей (обыч-
но в виде звездочек *).
В контексте обсуждаемых нами задач работа с элементами управления
для ввода текста очень проста. Основными действиями, которые обычно
требуется производить с текстовыми полями при обработке формы кли-
ентским скриптом, являются получение и изменение значений введенно-
го в них текста.
В терминах DOM, объект дерева документа, соответствующий HTML-
элементу INPUT, определяющему однострочное текстовое поле, реализу-
ет интерфейс HTMLInputElement, а объект, соответствующий элементу
TEXTAREA, определяющему многострочное текстовое поле — интерфейс
HTMLTextAreaElement. Декларация этих интерфейсов содержит атрибут
value, реализующийся в JavaScript свойством value. Таким образом,
получение и изменение текста, введенного в текстовое поле, сводится к
получению и изменению значения свойства value. Интерфейсы
HTMLTextAreaElement и HTMLInputElement имеют также метод select,
предназначенный для выделения всего содержимого текстового поля.
226 Часть III. Создание динамических эффектов
Далее приведен пример (вы также можете найти его на компакт-диске в
файле examples\11\ex_11_01.htm), иллюстрирующий использование
свойства value и метода select.
<FORM>
<INPUT type="text" value="Однострочное текстовое поле"
name="oText1"><br>
<BUTTON onclick="[Link]();">
Выделить содержимое</BUTTON>
<br><br>
<TEXTAREA name="oText2" cols="20" rows="3">Многострочное
текстовое поле</TEXTAREA><br>
<BUTTON onclick="with([Link].oText2){value =
[Link]();}">
Верхний регистр</BUTTON>
</FORM>
Как можно видеть, в листинге приведена HTML-разметка формы, со-
держащей два текстовых поля (однострочное и многострочное) и две
кнопки. Обработчики событий нажатия кнопок onclick содержат не-
большие фрагменты JavaScript-кода. Код обработчика onclick первой
кнопки осуществляет выделение содержимого однострочного текстово-
го поля (его атрибут name имеет значение oText1), вызывая метод
select. Код обработчика onclick второй кнопки конвертирует все алфа-
витные символы текста, введенного в многострочное текстовое поле
(атрибут name которого равен oText2), в верхний регистр, оперируя
свойством value объекта этого текстового поля.
Здесь необходимо отметить одну особенность текстовых полей для вво-
да пароля (элемент INPUT с атрибутом type, установленным в значение
"password") и элементов управления для выбора файлов. Свойство
value DOM-объектов этих элементов также содержит текущее значение
введенного в них текста. И клиентский сценарий может получить это
значение обычным образом. Однако при попытке присваивания нового
значения свойству value в большинстве браузеров происходит "сброс"
содержимого текстового поля и значения value в пустую строку. Ис-
ключение составляют текстовые поля для ввода пароля в Internet
Explorer.
Кнопки
Согласно спецификации HTML, формы могут содержать кнопки не-
скольких типов: кнопки отправки, графические кнопки отправки, кнопки
сброса и обычные кнопки. Кнопки отправки определяются элементами
Ãëàâà 11. Работа с формами 227
INPUT и BUTTON с атрибутом type, установленным в значение submit,
графические кнопки отправки — элементом INPUT с атрибутом type,
установленным в значение image, кнопки сброса — элементами INPUT и
BUTTON с атрибутом type, установленным в значение reset, а обычные
кнопки — элементами INPUT и с атрибутом type, установленным в зна-
чение button. Таким образом, DOM-объекты элементов управления
кнопок реализуют интерфейсы HTMLInputElement (объекты, соответст-
вующие элементам INPUT) и HTMLButtonElement (объекты, соответст-
вующие элементам BUTTON).
Кнопки отправки служат для инициализации процесса передачи значе-
ний формы ее обработчику, кнопки сброса — для установки полей фор-
мы в значения по умолчанию, обычные кнопки не инициируют каких-
либо предопределенных действий, такие кнопки обычно используют для
запуска произвольных клиентских скриптов, назначая им обработчики
событий.
Необходимо отметить, что кнопки, создаваемые при помощи элемента
BUTTON, могут включать произвольное содержимое, задаваемое HTML-
разметкой либо клиентскими скриптами. Графические кнопки отправки
представляются изображением (URL этого изображения определяется
значением атрибута src элемента INPUT).
При отправке формы успешной считается кнопка, инициировавшая от-
правку. Ее значение (текущее значение свойства value) передается об-
работчику формы. В случае графических кнопок, если кнопка была
активизирована позиционирующим устройством (например, мышью),
обработчику передаются также координаты щелчка относительно изо-
бражения.
Во многих браузерах (в Mozilla и Opera, Firefox) при отсутствии в фор-
ме явно заданных кнопок отправки таковыми считаются кнопки с неус-
тановленным значением атрибута type. Поэтому, если вы хотите соз-
дать форму без кнопок отправки, но содержащую обычные кнопки, для
них необходимо явно задать атрибут type="button" в HTML-
разметке либо указать значение свойства type при помощи клиент-
ского скрипта.
В принципе, кнопки — весьма самодостаточный элемент управления.
При обработке формы клиентскими скриптами редко возникает необхо-
димость манипулирования кнопками. Однако давайте рассмотрим не-
сколько примеров работы с ними. Допустим, в документе имеется фор-
ма, заданная HTML-разметкой:
228 Часть III. Создание динамических эффектов
<FORM id="ID_FORM_1">
<INPUT type="image" ID="ID_BUTTON_0" src="./[Link]"><br>
<INPUT type="button" name="oButton1" value="Кнопка 1"><br>
<BUTTON type="button" name="oButton2">Кнопка 2</BUTTON><br>
</FORM>
Допустим, при помощи следующего кода мы получили ссылку на DOM-
объект формы:
var oForm = [Link]("ID_FORM_1");
Как видно из листинга HTML-разметки, первая кнопка в форме — гра-
фическая. Визуально кнопка представляется изображением, URL кото-
рого задается атрибутом src элемента INPUT. Возможно, вам захочется
менять это изображение динамически (например, при изменении содер-
жимого полей формы некоторым образом). Это очень просто осущест-
вить, задав новое значение URL свойству src (является реализацией ат-
рибута src интерфейса HTMLInputElement) DOM-объекта кнопки:
[Link]('ID_BUTTON_0').src = '[Link]';
Вторая и третья кнопки — обычные. С ними можно совершать действия,
общие для всех типов элементов управления (см. далее в этой главе).
Специфическим действием для них может служить, пожалуй, изменение
текста надписи на кнопке. Для кнопки, определяемой элементом INPUT,
это можно сделать путем изменения значения свойства value DOM-
объекта кнопки:
[Link] = "Button 1";
Для кнопки, определяемой элементом BUTTON, данный способ не приме-
ним, поскольку элемент BUTTON может иметь произвольное содержимое
(несколько дочерних элементов разного типа). Однако, если заведомо
известно, что кнопка, определяемая элементом BUTTON, содержит только
простую текстовую надпись (как кнопка с атрибутом name="oButton2"
в листинге, приведенном ранее), становится возможным изменение тек-
ста надписи довольно простым способом:
[Link] = "Button 0";
Здесь мы получаем ссылку на первый дочерний DOM-объект объекта
элемента кнопки и, полагаясь на то, что этот объект является текстовым
узлом дерева документа (его свойство NodeType должно быть рав-
но TEXT_NODE), устанавливаем значение его содержимого (свойство
nodeValue).
Из гораздо менее часто встречающихся на практике задач можно выде-
лить динамическое изменение поведения кнопки. Например, можно сде-
Ãëàâà 11. Работа с формами 229
лать обычную кнопку кнопкой отправки, присвоив атрибуту type объек-
та кнопки соответствующее значение:
[Link] = "submit";
Однако я с трудом могу представить себе столь экзотичную задачу, для
решения которой потребуется изменение поведения кнопки. В общем
случае такие изменения будут противоречить концепции построения
дружественного пользовательского интерфейса.
Основным назначением кнопки является инициализация связанного с
ней события вследствие активизации кнопки. Обычно кнопку активиру-
ет пользователь. Однако в некоторых случаях может потребоваться про-
граммная активизация. Она производится при помощи вызова метода
click DOM-объекта кнопки:
[Link]();
При этом вызывается обработчик события щелчка по кнопке (если он
установлен). Применение метода click оказывается удобным во многих
случаях.
Согласно спецификации DOM, интерфейс HTMLInputElement имеет
метод click, а интерфейс HTMLButtonElement — нет. Таким обра-
зом, возможность программной активизации кнопок, определяемых
элементом BUTTON, может отсутствовать. Однако большинство брау-
зеров поддерживают метод click объектов элементов BUTTON как
часть специфической модели документа.
Радиокнопки, флажки
По сути, радиокнопки (или переключатели) и флажки обладают почти
одинаковой функциональностью. Элементы управления этих типов мо-
гут находиться в отмеченном (checked) и не отмеченном (сброшенном)
состоянии.
При отправке формы успешными считаются только отмеченные радио-
кнопки и флажки (только их значения передаются обработчику формы).
Принципиальное отличие радиокнопок от флажков состоит в том, что
радиокнопки могут быть объединены в группу. В группе радиокнопок
только одна может находиться в отмеченном состоянии. При активиза-
ции пользователем одной из радиокнопок, объединенных в группу, она
автоматически переходит в отмеченное состояние, а радиокнопка, нахо-
дившаяся в отмеченном состоянии до этого — в неотмеченное. При ак-
230 Часть III. Создание динамических эффектов
тивизации флажка он попеременно переходит то в отмеченное, то в
сброшенное состояние.
В HTML-разметке радиокнопки задаются элементами INPUT с атрибутом
type, установленным в значение radio, флажки — элементами INPUT с
атрибутом type, установленным в значение checkbox. Соответственно,
DOM-объекты этих элементов управления реализуют один и тот же ин-
терфейс HTMLInputElement, уже знакомый нам из предыдущих разделов
(см. спецификацию DOM уровня 1 или DOM уровня 2 на компакт-
диске). Поскольку основной характеристикой радиокнопок и флажков
является их состояние (отмеченное или сброшенное), здесь нас будет
интересовать вопрос программного управления этим состоянием. Рас-
смотрим фрагмент HTML-разметки:
<FORM id="ID_FORM_1">
<INPUT type="radio" name="oRadio1" value="1"
ID="oRadio11" checked><br>
<INPUT type="radio" name="oRadio1" value="2"
ID="oRadio12"><br>
<INPUT type="radio" name="oRadio1" value="3"
ID="oRadio13"><br>
<INPUT type="checkbox" name="Check1" value="1" checked>
<INPUT type="checkbox" name="Check2" value="2">
</FORM>
Данная разметка описывает форму, содержащую три радиокнопки, объ-
единенные в группу, и два флажка. Объединение нескольких радиокно-
пок в группу производится при помощи задания им одинакового значе-
ния атрибута name. Обратите внимание, что это делает невозможным
доступ к отдельному объекту радиокнопки через ссылку на объект фор-
мы с указанием имени элемента управления. Для решения этой пробле-
мы в HTML-разметке у элементов радиокнопок определены атрибуты
ID. Как видно из листинга, у элемента первой радиокнопки и одного из
флажков установлен атрибут checked. Он определяет, находится ли по
умолчанию элемент управления в отмеченном состоянии.
Интерфейс HTMLInputElement имеет логический атрибут checked, пред-
назначенный для определения и управления состоянием элемента ра-
диокнопки или флажка. В JavaScript этот атрибут реализуется свойством
checked DOM-объектов, реализующих интерфейс HTMLInputElement.
Для DOM-объектов, соответствующих элементам управления, не яв-
ляющихся радиокнопкой или флажком, это свойство не имеет смысла.
Для объектов флажков и радиокнопок свойство checked содержит зна-
Ãëàâà 11. Работа с формами 231
чение true, если элемент управления находится в отмеченном состоя-
нии, и false в противном случае. Значение свойства изменяется при
изменении состояния элемента управления (например, вследствие дей-
ствий пользователя). Также изменение значения свойства приводит к
изменению состояния элемента управления. Таким образом, манипули-
рование состоянием радиокнопок и флажков сводится к изменению зна-
чения свойства checked. Например, следующий код проверяет, находит-
ся ли вторая радиокнопка в группе (см. листинг, приведенный ранее) в
отмеченном состоянии, и если это так, снимает отметку с флажка
Check1:
var oForm = [Link]("ID_FORM_1");
if([Link]) // если вторая радиокнопка отмечена
[Link] = false; // снять отметку
// с флажка
Стоит отметить, что интерфейс HTMLInputElement имеет также атрибут
defaultChecked логического типа (в JavaScript ему соответствует свой-
ство defaultChecked). Свойство defaultChecked содержит значение со-
стояния радиокнопки или флажка по умолчанию (определяемое в
HTML-разметке свойством checked). При изменении состояния элемен-
та управления значение этого свойства не изменяется.
Списки и выпадающие списки. Элемент выбора SELECT
Как уже упоминалось, элемент SELECT определяет меню — элемент
управления одноальтернативного либо многоальтернативного выбора.
Визуально, элемент одноальтернативного выбора браузеры обычно
представляют в виде выпадающего списка (combo box), а элемент мно-
гоальтернативного выбора — в виде списка (list box). Пункты выбора,
содержащиеся в элементе SELECT, определяются HTML-элементами
OPTION. Элементы OPTION могут быть объединены в группы путем
помещения в элемент OPTGROUP. Подробную информацию об исполь-
зовании элементов SELECT, OPTION, OPTGROUP в HTML можно полу-
чить в соответствующем разделе спецификации (например, в файле
w3c\html\4.01\rus\[Link] на компакт-диске). Мы же перейдем к изу-
чению принципов манипулирования элементами выбора с помощью
JavaScript. Будем рассматривать их на примере формы, заданной сле-
дующей HTML-разметкой:
<form ID="ID_FORM">
<select name="oSelect" multiple>
<option value="1">Пункт 1</option>
232 Часть III. Создание динамических эффектов
<option value="2" id="ID_PUNKT2">Пункт 2</option>
<optgroup label="Группа 1">
<option value="21">Пункт 2.1</option>
<option value="22">Пункт 2.2</option>
</optgroup>
<option value="3">Пункт 3</option>
</select>
</form>
Форма содержит элемент многоальтернативного выбора, содержащий
пять пунктов, два из которых объединены в группу.
Согласно спецификации DOM, объект дерева документа, соответствующий
элементу SELECT, должен реализовывать интерфейс HTMLSelectElement,
объект, соответствующий элементу OPTION — интерфейс HTMLOptionElement,
а объект, соответствующий элементу OPTGROUP — интерфейс HTMLOpt-
GroupElement. Далее в примерах кода для краткости будем полагать, что
мы уже получили ссылку на DOM-объект элемента SELECT, например,
следующим образом:
var oList = [Link]("ID_FORM").oSelect;
Поскольку основными показателями, характеризующими элементы
управления одно- и многоальтернативного выбора являются перечень,
структура расположения и состояние пунктов выбора, содержащихся
в них, естественно, что большинство задач манипулирования элемента-
ми выбора заключается в манипулировании этими пунктами. Поэтому в
первую очередь мы рассмотрим методы доступа к пунктам элемента
SELECT (DOM-объектам элементов OPTION и OPTGROUP).
Согласно спецификации DOM, интерфейс HTMLSelectElement содержит
атрибут options. В JavaScript он представлен свойством options DOM-
объекта элемента SELECT, являющимся объектом-коллекцией, реали-
зующим DOM-интерфейс HTMLOptionsCollection. Данная коллекция
содержит ссылки на DOM-объекты элементов OPTION, содержащихся в
элементе SELECT, и позволяет получать к ним доступ.
Интерфейс HTMLOptionsCollection имеет атрибут length, содержащий
текущее количество элементов коллекции, а также методы item и
namedItem. Метод item возвращает ссылку на элемент коллекции по его
порядковому номеру, а namedItem — по значению атрибута id либо name
элемента. Так, в нашем случае получить количество элементов списка
выбора можно следующим образом:
var nCount = [Link];
Ãëàâà 11. Работа с формами 233
Получить ссылку на первый элемент списка (DOM-объект первого эле-
мента OPTION) можно так:
var oOption2 = [Link](0);
А ссылку на второй элемент — следующими способами:
var oOption2 = [Link](1);
var oOption2 = [Link]("ID_PUNKT2");
К слову, во всех популярных браузерах поддерживается доступ к эле-
ментам коллекции options в нотации обращения к элементам массива.
То есть, получать ссылку на второй элемент OPTION списка выбора мож-
но так:
var oOption2 = [Link][1];
var oOption2 = [Link]["ID_PUNKT2"];
Второй вариант, однако, не поддерживается в Internet Explorer.
Теперь давайте поговорим о модификации списка пунктов элемента
SELECT. Типичными задачами здесь могут являться: добавление новых
пунктов в список, удаление пунктов, изменение их позиции.
Добавление пунктов в элемент выбора производится путем создания
новых элементов OPTION (например, при помощи метода createElement
объекта document) и вставки их в дерево документа как дочерних узлов
элемента SELECT. Вставку созданных элементов OPTION можно произво-
дить "классическим" способом — при помощи методов insertBefore,
appendChild DOM-объекта элемента SELECT (см. декларацию интерфей-
са Node в спецификации DOM). Однако в интерфейсе HTMLSelectElement
предусмотрен метод add специально для добавления новых пунктов и
элементов группировки:
void add(in HTMLElement element, in HTMLElement before)
Фактически, этот метод аналогичен методу appendChild, если в качестве
параметра before передается значение null, и методу insertBefore,
если передается ссылка на DOM-объект элемента OPTION или OPTGROUP.
К сожалению, на данный момент метод add не работает корректно в
Internet Explorer.
При вставке новых пунктов в элемент выбора удобно было бы указывать
числовое значение позиции добавляемого элемента. Однако методы
insertBefore и add не позволяют этого сделать. Поэтому логично реа-
234 Часть III. Создание динамических эффектов
лизовать функцию вставки нового пункта в список по порядковому но-
меру. Она может выглядеть, например, так:
function Select_InsertItem(oList, nItemBefore, strText)
{
var oNewOption = [Link]("OPTION");
[Link]([Link](strText));
[Link](oNewOption, Select_GetItem(oList,
nItemBefore));
return oNewOption;
}
Как видно из приведенного листинга, данная функция использует вспо-
могательную функцию Select_GetItem, возвращающую ссылку на
DOM-объект элемента OPTION, содержащегося в элементе SELECT (ссыл-
ка на него передается первым параметром), по его порядковому номеру
либо значение null, если индекс (передается вторым параметром) выхо-
дит за границы допустимого диапазона индексов. Это сделано в целях
обеспечения совместимости с различными браузерами. Код этой функ-
ции таков:
function Select_GetItem(oList, nItem)
{
return (((nItem >= 0) && (nItem < [Link])) ?
[Link][nItem] : null);
}
Функция Select_InsertItem принимает первым параметром ссылку на
DOM-объект элемента SELECT, вторым — позицию в списке, в которую
будет добавлен новый пункт. Если позиция отрицательна, либо ее зна-
чение больше значения позиции последнего элемента списка, новый
пункт будет добавлен в конец списка. Третий параметр функции — тек-
стовое содержимое добавляемого пункта. Функция Select_InsertItem
возвращает ссылку на DOM-объект созданного элемента OPTION (это
удобно для дальнейшей модификации добавленного элемента).
Возможно, при написании скриптов, у вас возникнет необходимость в
динамическом изменении порядка пунктов в элементах выбора. Такие
изменения достаточно просто осуществить при помощи функции
Select_MoveItem, код которой приведен далее.
function Select_MoveItem(oList, nItem, nNewPos)
{
var oItem = Select_GetItem(oList, nItem);
Ãëàâà 11. Работа с формами 235
if(oItem && (nItem != nNewPos))
{
var oItemBefore = Select_GetItem(oList, nNewPos);
[Link](oItem);
[Link](oItem, oItemBefore);
return true;
}
return false;
}
Как и в предыдущем случае, первым параметром функция получает
ссылку на DOM-объект элемента SELECT, в котором требуется произве-
сти изменения. Второй параметр — позиция перемещаемого пункта,
третий параметр — позиция, в которую будет перемещен этот пункт.
Возвращаемое значение функции — true, если пункт был перемещен, и
false в противном случае. Как видно из приведенного листинга, изме-
нение позиции пункта производится путем удаления DOM-объекта эле-
мента OPTION из дерева документа (при помощи метода removeChild) с
последующим его добавлением в новую позицию с помощью метода
insertBefore.
В отличие от добавления и перемещения пунктов списка выбора эле-
мента SELECT, операция удаления производится крайне просто. Интер-
фейс HTMLSelectElement (и, соответственно, любой DOM-объект эле-
мента SELECT) имеет метод remove, производящий удаление пункта в
позиции, указанной числовым индексом (с отсчетом от нуля). Так, в на-
шем случае, удалить первый пункт в списке можно следующим образом:
[Link](0);
К слову, удалить пункт можно и при помощи метода removeChild (как
это было сделано ранее — например, при реализации функции
Select_MoveItem), но для этого необходимо получить ссылку на соот-
ветствующий объект элемента OPTION.
Кроме модификации структуры списка пунктов элементов выбора, при
написании скриптов может потребоваться динамическое изменение со-
стояния и текстового содержимого пунктов. Изменить текстовое содер-
жимое можно, удалив все дочерние узлы дерева документа DOM-
объекта элемента OPTION, соответствующего изменяемому пункту, а за-
тем добавив новый дочерний текстовый узел с необходимым содержи-
мым. Это делает следующая функция.
236 Часть III. Создание динамических эффектов
function Select_SetItemText(oList, nItem, strText)
{
var oItem = Select_GetItem(oList, nItem);
if(oItem)
{
while([Link])
[Link]([Link]);
[Link]([Link](strText));
}
}
Первым параметром эта функция принимает ссылку на DOM-объект
элемента SELECT, вторым — индекс изменяемого пункта, третьим —
строковое значение устанавливаемого для пункта текста.
Пункты выбора элемента SELECT могут находиться в отмеченном либо
неотмеченном состоянии. Также они могут быть отключены — в этом
состоянии пункт недоступен для выбора. Свойства логического типа
selected и disabled DOM-объектов элементов OPTION (JavaScript-
реализации одноименных атрибутов интерфейса HTMLOptionElement)
позволяют получать и устанавливать состояния "отмеченности" и недос-
тупности пунктов выбора элемента SELECT. Далее приведен код функ-
ций, осуществляющих изменение указанных состояний пунктов элемен-
та SELECT по их порядковому номеру.
function Select_SetItemSelected(oList, nItem, bSelected)
{
var oItem = Select_GetItem(oList, nItem);
if(oItem) [Link] = bSelected;
}
function Select_SetItemEnabled(oList, nItem, bEnabled)
{
var oItem = Select_GetItem(oList, nItem);
if(oItem) [Link] = !bEnabled;
}
Первые два параметра этих функций имеют те же значения, что и в пре-
дыдущих примерах. Их третий параметр — логическое значение уста-
навливаемого состояния.
Исходный код всех приведенных функций можно найти в файле
examples\11\[Link] на компакт-диске.
Ãëàâà 11. Работа с формами 237
Манипуляции общего типа
элементами управления форм
Ранее мы рассмотрели типичные операции, характерные для определен-
ных типов элементов управления. Однако существует несколько опера-
ций, которые можно производить со всеми типами элементов управле-
ния. Это операции перемещения фокуса ввода между элементами, а
также операция изменения состояния доступности элемента для приня-
тия пользовательского ввода. Использование этих операций в основном
связано с улучшением эксплуатационных характеристик пользователь-
ского интерфейса форм.
Итак. Управление фокусом ввода осуществляется при помощи методов
focus и blur DOM-объектов элементов управления. Метод focus пере-
мещает фокус ввода на элемент управления, которому соответствует
DOM-объект, инкапсулирующий вызываемый метод. Аналогично, метод
blur убирает фокус ввода с элемента управления. Стоит отметить, что
для DOM-объектов, реализующих интерфейсы HTMLInputElement и
HTMLTextAreaElement (т. е. для DOM-объектов, соответствующих радио-
кнопкам, флажкам, однострочным и многострочным полям ввода,
элементам выбора файлов, а также всем типам кнопок, создаваемых
при помощи элемента INPUT), методы focus и blur определены спе-
цификацией DOM (декларации интерфейсов HTMLInputElement и
HTMLTextAreaElement содержат эти методы). А для DOM-объектов, со-
ответствующих кнопкам, определяемых элементом BUTTON, — нет (дек-
ларация интерфейса HTMLButtonElement не содержит этих методов). Од-
нако большинство популярных браузеров реализуют эти методы для
DOM-объектов, элементов BUTTON как часть специфической объектной
модели документа. Таким образом, при помощи вызовов методов focus
и blur можно управлять перемещением фокуса ввода между элементами
управления. Далее в этой главе мы будем использовать метод focus
в примерах, посвященных автоматизации действий пользователя.
Среди прочих методов и атрибутов, декларации интерфейсов
HTMLInputElement, HTMLButtonElement и HTMLTextAreaElement содержат
атрибут disabled логического типа, реализуемый в JavaScript свойством
disabled. Свойство disabled DOM-объектов, реализующих вышеупо-
мянутые интерфейсы, содержит значение состояния доступности эле-
мента для пользовательского ввода. Недоступные (более точно было бы
сказать — заблокированные) элементы управления не могут принимать
пользовательский ввод, т. е. никак не реагируют на действия пользова-
теля (не изменяют своего состояния), даже если находятся в фокусе вво-
238 Часть III. Создание динамических эффектов
да. Если свойство disabled содержит значение true, то элемент являет-
ся заблокированным, если false — доступным для ввода. Изменение
значения этого свойства приводит к изменению состояния доступности
элемента управления (кстати, в большинстве браузеров заблокирован-
ные элементы управления визуально отличаются от незаблокирован-
ных). Далее в примерах этой главы свойство disabled также будет ис-
пользоваться.
Улучшение эксплуатационных
характеристик пользовательского
интерфейса форм
средствами JavaScript
Как упоминалось в начале данной главы, большинство случаев приме-
нения клиентских сценариев для обработки форм в той или иной мере
решают задачи контроля корректности вводимых в форму данных либо
изменения поведения пользовательского интерфейса форм с целью
улучшения его эксплуатационных характеристик, т. е. создания друже-
ственного пользовательского интерфейса.
Можно привести достаточно много типичных примеров усовершенство-
вания пользовательского интерфейса. Часть из них преследует цели
поддержки логической целостности состояний элементов управления
формы (когда состояние одной части интерфейса должно логически за-
висеть от другой). Например, из нескольких текстовых полей для ввода
должно быть доступно только одно, в зависимости от выбранной поль-
зователем радиокнопки. Другая часть изменений может быть направле-
на на улучшение поведения интерфейса форм или отдельных элементов
управления (автоматический переход фокуса между элементами управ-
ления при совершении пользователем определенных действий, фильтра-
ция ввода в текстовые поля и т. д.). К слову, контроль корректности
данных формы, производимый динамически, также может служить ос-
новой для улучшения характеристик пользовательского интерфейса.
Примером может служить динамическое отключение/включение кнопки
отправки формы при вводе данных пользователем в зависимости от
корректности этих данных.
Далее мы обсудим основные подходы к совершенствованию пользова-
тельского интерфейса и принципы их реализации.
Ãëàâà 11. Работа с формами 239
Принципы работы с формами при реализации
дружественного пользовательского
интерфейса
Мы уже рассмотрели приемы работы с элементами управления форм.
Возникает вопрос: какие же существуют особенности в написании
скриптов для решения задач реализации дружественного пользователь-
ского интерфейса? Действительно, основные принципы работы с фор-
мами остаются неизменными. Но основными аспектами, которым необ-
ходимо уделить внимание, являются схемы обработки событий форм и
их элементов управления. Схемы обработки событий в основном зави-
сят от реализуемой модели поведения пользовательского интерфейса.
При реализации достаточно сложных моделей существует несколько
тонких моментов в обработке событий. Рассмотрим несколько типич-
ных моделей, касающихся преимущественно контроля корректности
данных форм.
Модель с проверкой данных перед отправкой формы. Это самая про-
стая модель, именно потому, что проверка данных осуществляется
только при возникновении события отправки формы. В случае суще-
ствования некорректных данных отправка формы отменяется, поль-
зователю выдается соответствующее сообщение, и фокус ввода уста-
навливается на первый элемент формы, содержащий некорректные
данные. Реализация данной модели не представляет сложности —
требуется обработка всего одного события onsubmit формы. В коде
обработчика можно реализовать все необходимые действия. Такая
модель не требует каких-то особых подходов к обработке формы.
Модель с динамической проверкой при изменении данных формы.
В этом случае проверка корректности данных производится после
каждого изменения формы пользователем. Если данные формы не
корректны, кнопка отправки блокируется. Можно также реализовать
индикацию, оповещающую пользователя о причинах невозможности
отправки формы (например, в виде флажков или меток рядом с эле-
ментами управления, изменяющих свое состояние в зависимости от
корректности введенных в эти элементы управления данных, или от-
дельного текстового поля, в которое выводятся сообщения, пояс-
няющие ошибки пользователя). Такая модель поведения пользова-
тельского интерфейса, безусловно, более удобна для пользователя.
Но для ее реализации требуется перехватывать любое изменение
данных в форме, вызывая при этом процедуру проверки корректно-
сти данных.
240 Часть III. Создание динамических эффектов
Модель с динамической проверкой данных и фильтрацией ввода.
Данная модель может рассматриваться как расширение предыдущей
модели. Она предполагает как контроль данных, так и фильтрацию
пользовательского ввода. В данном случае должен быть предотвра-
щен ввод заведомо некорректных данных (например, букв в поля,
предназначенных для чисел), однако отправка формы должна стано-
виться возможной только в случае соответствия всех введенных дан-
ных критериям корректности. При реализации этой модели требуется
индивидуально обрабатывать события изменения данных элементов
управления для фильтрации ввода, а затем вызывать общую про-
цедуру проверки. Поэтому, наряду с тем, что такая модель предпола-
гает наиболее интеллектуальный пользовательский интерфейс, она
еще и наиболее сложна в реализации.
Как видите, для реализации второй и третьей моделей поведения поль-
зовательского интерфейса форм требуется построение некоторой схемы
обработки событий в основном для целей отслеживания изменений со-
стояния и данных элементов управления. Вот тут и стоит обрисовать
несколько тонких аспектов обработки этих событий.
Все типы элементов управления, в которых может потребоваться отсле-
живание изменения данных, определяются HTML-элементами INPUT,
SELECT и TEXTAREA. Для всех этих элементов допустимо назначение об-
работчика событий onchange, код которого исполняется после измене-
ния данных элемента управления. Однако, согласно спецификации
HTML, onchange вызывается при потере элементом управления фокуса.
То есть, назначение обработчика onchange не применимо для решения
задач динамического контроля данных формы. Очевидно, для решения
подобных задач необходимо назначить обработчики событий других
типов (обработчики событий, после которых происходит изменение со-
стояния элементов управления). Такими событиями являются щелчок
мышью и нажатие клавиши.
Как вы знаете, событие "щелчок мышью" обрабатывается при помощи
установки обработчика onclick, а событие "нажатие клавиши" — при
помощи onkeypress. Для обработки событий нескольких элементов
управления можно назначить соответствующие обработчики каждому
их них. Однако при решении многих задач, в которых требуется центра-
лизованная обработка событий от нескольких элементов управления
(например, при реализации описанной выше второй модели поведения
пользовательского интерфейса), целесообразнее назначить обработчики
этих событий для всей формы (вспомните о продвижении событий в
дереве документа). То есть, решить задачу динамического контроля
Ãëàâà 11. Работа с формами 241
данных формы представляется возможным при помощи установки об-
работчиков onclick и onkeypress для формы, из которых вызывается
общая процедура контроля данных. Однако в общем случае такая схема
работать не будет.
Причина, по которой вышеописанная схема оказывается неработоспо-
собной, состоит в том, что состояние элементов управления в момент
обработки событий зависит от деталей реализации конкретной модели
документа, т. е., фактически, от конкретного браузера. Так, на момент
вызова обработчиков onclick, onkeypress свойство checked флажков и
радиокнопок может быть как уже измененным, так и нет. То же самое
касается значений свойств value, selected. Еще один нюанс касается
обработки событий клавиатуры. Дело в том, что в Internet Explorer не
вызывается обработчик onkeypress формы при нажатии клавиши на
элементе управления в этой форме. Поэтому вместо обработчика
onkeypress лучше использовать обработчик onkeyup. Использование
onkeyup целесообразно еще и потому, что он будет вызываться только
один раз при окончании изменения элемента управления пользователем
(например, в случае нажатия и удержания клавиши в текстовом поле его
обработчик onkeypress вызывается многократно, а onkeyup вызовется
только при отпускании клавиши).
Таким образом, во-первых, для обработки событий клавиатуры необхо-
димо использовать установку обработчиков onkeyup, а во-вторых, кон-
троль корректности данных формы стоит производить не при обработке
событий изменения формы, а сразу после них. Наиболее просто это реа-
лизуется путем установки таймера с интервалом 0 при изменении фор-
мы, из обработчика событий которого и вызывается процедура проверки
корректности данных.
Элементы создания дружественного
пользовательского интерфейса
Теперь разберем несколько примеров реализации элементов дружест-
венного интерфейса.
Управление состоянием
взаимосвязанных элементов управления
Пример, находящийся в файле examples\11\ex_11_02.htm на компакт-
диске, демонстрирует принципы создания скриптов, обеспечивающих
управление состоянием логически взаимосвязанных элементов управле-
242 Часть III. Создание динамических эффектов
ния формы. Файл ex_11_02.htm содержит HTML-разметку, определяю-
щую четыре формы. Подразумевается, что в каждой из них состояние
одних элементов управления должно зависеть от других. Файл ex_11_02.htm
содержит также скрипты, осуществляющие изменение состояний эле-
ментов управления. Во всех четырех случаях применяется схема обра-
ботки событий, описанная в предыдущем разделе.
Первая форма содержит несколько флажков и такое же количество тек-
стовых полей. Предполагается, что каждому флажку соответствует свое
текстовое поле. При отмеченном флажке соответствующее текстовое
поле должно быть доступно для ввода, при сброшенном — заблокиро-
вано. Значения атрибутов name флажков равно oCheck1, oCheck2 и
oCheck3, а соответствующих им текстовых полей — oText1, oText2 и
oText3 (см. файл ex_11_02.htm на компакт-диске). Обновление элемен-
тов управления первой формы производится функцией OnUpdateForm1,
которая вызывается как обработчик события таймера, инициализируе-
мого в функции OnNeedUpdateForm1.
function OnNeedUpdateForm1()
{
setTimeout(OnUpdateForm1, 0);
}
function OnUpdateForm1()
{
var oForm = [Link]("ID_FORM_1");
with(oForm)
{
[Link] = ![Link];
[Link] = ![Link];
[Link] = ![Link];
}
}
Функция OnNeedUpdateForm1 вызывается из обработчика onclick фор-
мы, определенного в HTML-разметке:
<FORM action="javascript:void(0);" onsubmit="return false;"
id="ID_FORM_1" onclick="OnNeedUpdateForm1();">
...
Также, функция OnUpdateForm1 вызывается непосредственно после реа-
лизаций функций OnNeedUpdateForm1 и OnUpdateForm1 для начальной
Ãëàâà 11. Работа с формами 243
инициализации состояний элементов управления. Стоит отметить, что
мы назначили для формы только обработчик событий onclick. В дан-
ном случае этого вполне хватит, т. к. нам необходимо отслеживать толь-
ко изменения состояний флажков, а при изменении состояния флажка
как с помощью мыши, так и с помощью клавиатуры генерируется собы-
тие щелчка по флажку, и вызывается обработчик onclick.
Вторая форма вместо флажков содержит радиокнопки. В зависимости
от выбранной радиокнопки становится доступным то или иное тексто-
вое поле. Для второй формы также задан обработчик onclick, вызы-
вающий функцию OnNeedUpdateForm2, которая запускает таймер, обра-
ботчиком которого является функция OnUpdateForm2, производящая об-
новление состояния текстовых полей.
function OnUpdateForm2()
{
var oForm = [Link]("ID_FORM_2");
for(var i = 1; i < 6; i++)
oForm['oText' + [Link](10)].disabled =
).checked;
}
Как видно из листинга, доступ к DOM-объектам радиокнопок осуществ-
ляется по значению их атрибута ID. Свойству disabled DOM-объектов
текстовых полей просто присваивается инвертированное значение свой-
ства checked DOM-объектов радиокнопок.
В третьей форме состояние текстовых полей зависит от пункта, выбран-
ного в выпадающем списке (значение атрибута name HTML-элемента
списка равно oCombo1). Здесь применяется все та же схема обработки
событий, только обработчики событий назначены для элемента выпа-
дающего списка.
<SELECT name="oCombo1" onchange="OnUpdateForm3();"
onkeyup="OnUpdateForm3();">
<OPTION value="1" selected>Поле 1</OPTION>
...
Используются обработчики onchange и onkeyup (для случая, когда выбор
элемента списка производится с клавиатуры). Также из этих обработчи-
ков вызывается функция OnNeedUpdateForm3, запускающая таймер, об-
работчиком событий которого является функция OnUpdateForm3, обнов-
ляющая состояние элементов управления.
244 Часть III. Создание динамических эффектов
function OnUpdateForm3()
{
var oForm = [Link]("ID_FORM_3");
var nSelItem = [Link];
for(var i = 0; i < 3; i++)
oForm['oText' + [Link](10)].disabled =
(i != nSelItem);
}
В четвертой форме содержится один выпадающий список и одно тек-
стовое поле. Список содержит элементы с названиями цветов.
<SELECT name="oCombo1" onchange="OnNeedUpdateForm4();"
onkeyup="OnNeedUpdateForm4();">
<OPTION value="#FF0000" style="background: #FF0000"
selected>Красный</OPTION>
<OPTION value="#FF9900"
style="background:#FF9900">Оранжевый</OPTION>
...
Значения value элементов OPTION, задающих пункты списка, содержат
шестнадцатеричное RGB-представление соответствующего цвета. После
выбора пункта в списке в текстовом поле изменяется цвет фона (соот-
ветствует цвету, выбранному в списке). Обновление текстового поля
производится в функции OnUpdateForm4.
function OnUpdateForm4()
{
var oForm = [Link]("ID_FORM_4");
[Link] =
[Link][[Link]].value;
}
Примеры автоматизации действий пользователя
Пример, находящийся в файле examples\11\ex_11_03.htm, демонстрирует
одну из возможностей автоматизации действий пользователя при его
работе с формой. Форма, заданная HTML-разметкой в файле
ex_11_03.htm, содержит два однострочных и одно многострочное тек-
стовое поле. При переходе фокуса ввода на любое из этих полей, текст в
нем автоматически выделяется. Это дает пользователю возможность
немедленно очистить содержимое поля, просто начав набирать новый
текст. Нажав же какую-либо из управляющих клавиш, можно продол-
жить редактирование содержащегося в поле текста. Скрипт, позволяю-
щий реализовать описанный эффект, состоит всего из нескольких строк:
Ãëàâà 11. Работа с формами 245
var oForm = [Link]("ID_FORM");
var oHandler = new Function("[Link]();");
[Link] = oHandler;
[Link] = oHandler;
[Link] = oHandler;
Он создает объект функции oHandler и назначает его как обработчик
события получения фокуса onfocus всем трем текстовым полям формы.
В обработчике вызывается метод select объекта, которому назначен
обработчик. Такой прием помогает избежать дублирования кода обра-
ботчика события.
Файл examples\11\ex_11_04.htm также содержит скрипты, автоматизи-
рующие действия пользователя. Он содержит две формы. В первой
форме осуществляется автоматический циклический переход между
текстовыми полями при нажатии в них клавиши <Enter>. Во второй
форме переход между полями осуществляется при достижении текстом
в поле длины в четыре символа, к тому же производится фильтрация
ввода в эти поля, ограничивающая диапазон вводимых символов только
цифрами. Такая форма может использоваться для ввода серийного но-
мера.
Итак, первая форма содержит десять однострочных текстовых полей,
значения атрибутов name которых равны oText0, oText1, ..., oText9. При
помощи скрипта для каждого из этих полей генерируется и назначается
обработчик события нажатия клавиши onkeydown, вызывающий общую
для всех полей процедуру обработки клавиатурного ввода
OnForm1KeyDown. Вот весь код скрипта:
var oForm1 = [Link]("ID_FORM_1");
function OnForm1KeyDown(e, nControl)
{
if(typeof(e) == 'undefined')
{
if(typeof([Link]) != 'undefined')
e = [Link];
}
if(typeof(e) != 'undefined')
{
if([Link] == 13)
{
nControl++;
246 Часть III. Создание динамических эффектов
if(nControl > 9)
nControl = 0;
oForm1['oText' + [Link](10)].focus();
}
}
}
for(var i = 0; i < 10; i++)
oForm1['oText' + [Link](10)].onkeydown =
new Function("e",
"OnForm1KeyDown(e, " + [Link](10) + ");");
Как видно из листинга, в функцию OnForm1KeyDown из обработчиков
onkeydown передается объект события (e) и номер текстового поля
(nControl), к которому должен перейти фокус ввода в случае, если была
нажата клавиша <Enter>. Функция OnForm1KeyDown анализирует код на-
жатой клавиши (свойство keyCode объекта события). И если код равен
13 (клавиша <Enter>), совершается передача фокуса следующему полю.
Обратите внимание на код в начале функции:
if(typeof(e) == 'undefined')
{
if(typeof([Link]) != 'undefined')
e = [Link];
В Internet Explorer объект события не передается в обработчик события,
а является глобальным — свойством event объекта window. Поэтому для
обеспечения кроссбраузерной совместимости мы должны анализировать
передаваемое значение e и использовать объект [Link], если объ-
ект события не был передан из обработчика.
Принцип работы второго скрипта в примере ex_11_04.htm почти не от-
личается от предыдущего. Вторая форма содержит пять текстовых по-
лей. Для них также назначаются обработчики события нажатия клави-
ши. И также из обработчиков вызывается общая процедура
OnForm2KeyDown, осуществляющая фильтрацию ввода и, если требуется,
перемещение фокуса. Вот несколько сокращенный код скрипта.
var oForm2 = [Link]("ID_FORM_2");
function OnForm2KeyDown(e, oControl, nNext)
{
...
if(typeof(e) != 'undefined')
Ãëàâà 11. Работа с формами 247
{
if([Link] > 47)
{
if(([Link] != 127) && ([Link] > 57))
return false;
if(([Link] > 3) && (nNext < 5))
oForm2['oText' + [Link](10)].focus();
}
}
return true;
}
for(var i = 0; i < 5; i++)
oForm2['oText' + [Link](10)].onkeydown =
new Function("e", "return OnForm2KeyDown(e, this," +
(i + 1).toString(10) + ");");
Как видите, фильтрация ввода осуществляется просто возвращением
значения false функцией OnForm2KeyDown, а следовательно, и обработ-
чиком события (см. код обработчика) при нажатии пользователем кла-
виш, клавиатурные коды которых не попадают в диапазон кодов цифро-
вых и управляющих клавиш. Также функция проверяет длину текста
(свойство value), вводимого в текущее текстовое поле. И, при превыше-
нии им заданной длины, производит перемещение фокуса.
Пример формы с автокалькуляцией
Файл examples\11\ex_11_05.htm представляет собой еще один пример
построения дружественного пользовательского интерфейса. В нем реа-
лизован калькулятор шестнадцатеричного представления цвета в форма-
те #rrggbb на основе десятичных значений интенсивности света красно-
го, зеленого и синего цветовых каналов, вводимых в соответствующие
поля формы. Вычисление результирующего значения производится ав-
томатически при изменении значений в полях для ввода данных. Рядом
с полями для ввода данных находятся индикаторы в виде символов *,
отображающие корректность данных, введенных в текстовое поле путем
изменения цвета. Также при вводе некорректных данных в текстовом
поле для вывода результата отображается сообщение об ошибке.
Значения атрибутов name полей для ввода данных равны oTextRed,
oTextGreen и oTextBlue, а поля для отображения результата — oTextHEX.
Обновление формы, как и в предыдущих примерах, производится при
248 Часть III. Создание динамических эффектов
возникновении события таймера с интервалом 0. Таймер запускается
функцией NeedUpdateForm, вызываемой из обработчиков событий, уста-
новленных в HTML-разметке.
В этом примере для отслеживания изменений данных используются об-
работчики событий onkeydown и onkeyup, установленные для формы:
<FORM action="javascript:void(0);" onsubmit="return false;"
id="ID_FORM"
onkeydown="NeedUpdateForm();" onkeyup="NeedUpdateForm();">
...
А также обработчики onchange, установленные для каждого текстового
поля для ввода данных:
<INPUT type="text" name="oTextRed" value="0"
onchange="NeedUpdateForm();"> <SPAN
ID="ID_SPAN_CHECK_RED">*</SPAN>
...
Обновление формы производится функцией OnUpdateForm (она вызыва-
ется как обработчик событий таймера).
function OnUpdateForm()
{
var nRed = GetValueAndUpdate("oTextRed",
"ID_SPAN_CHECK_RED");
var nGreen = GetValueAndUpdate("oTextGreen",
"ID_SPAN_CHECK_GREEN");
var nBlue = GetValueAndUpdate("oTextBlue",
"ID_SPAN_CHECK_BLUE");
[Link]("ID_FORM").[Link] =
((nRed >= 0) && (nGreen >= 0) && (nBlue >= 0)) ?
'#' + FormatH2(nRed) + FormatH2(nGreen) +
FormatH2(nBlue) :
'Введены некорректные значения';
}
При помощи функции GetValueAndUpdate производится получение чи-
словых значений на основе строк, введенных в текстовые поля с одно-
временным обновлением цвета индикаторов.
function GetValueAndUpdate(strInputName, strIndicatorID)
{
var oSpan = [Link](strIndicatorID);
Ãëàâà 11. Работа с формами 249
var nResult =
parseInt([Link]("ID_FORM")[strInputName].
value);
if(isNaN(nResult) || (nResult < 0) || (nResult > 255))
nResult = -1;
[Link] = (nResult < 0) ? "#FF0000" : "#00FF00";
return nResult;
}
Функция GetValueAndUpdate использует parseInt для преобразования
текстовых значений в числа. Если преобразование не удается (функция
isNaN возвращает true) — в текстовое поле введено отрицательное зна-
чение, либо значение, большее 255 — GetValueAndUpdate возвращает
–1. Здесь же, согласно результату преобразования, изменяется цвет ин-
дикатора, соответствующего текстовому полю, из которого выбирается
значение. Цвет индикатора изменяется путем установки значения CSS-
свойства color для DOM-объекта элемента SPAN, представляющего со-
бой индикатор.
Далее, в функции OnUpdateForm производится анализ полученных значе-
ний, вычисление и установка результата (либо установка сообщения об
ошибке, если при каком-либо вызове GetValueAndUpdate было получено
отрицательное значение).
Пример формы обратной связи
Два последних примера иллюстрируют принципы проверки корректно-
сти данных на примере форм обратной связи. Такие формы часто ис-
пользуются на сайтах вместо публикации контактных адресов электрон-
ной почты во избежание получения на эти адреса большого количества
незапрошенных рекламных сообщений (спама). Пример, находящийся в
файле examples\11\ex_11_06.htm, реализует первую модель поведения
пользовательского интерфейса из моделей, описанных в предыдущем
разделе. В этом файле находится HTML-разметка формы, содержащей
поля для ввода имени, контактного адреса электронной почты, номера
пользователя интернет-пейджера ICQ, контактного телефона и, собст-
венно, текстового сообщения, отсылаемого через эту форму. Предпола-
гается, что поля ввода имени, e-mail и текстового сообщения обязатель-
ны для заполнения.
Скрипт проверки корректности данных формы очень прост. Согласно
первой модели поведения пользовательского интерфейса, корректность
250 Часть III. Создание динамических эффектов
данных проверяется только перед отправкой формы. В данном случае из
обработчика onsubmit формы вызывается функция ValidateForm.
<FORM action="javascript:void(0);"
onsubmit="return ValidateForm();"
id="ID_FORM">
Обработчик onsubmit возвращает значение, переданное функцией
ValidateForm. Таким образом, если ValidateForm возвращает false,
отправка формы отменяется. Код функции ValidateForm таков:
function ValidateForm()
{
var oForm = [Link]("ID_FORM");
with(oForm)
{
return ValidateText(oTextName) &&
ValidateEMail(oTextEMail) &&
ValidateICQ(oTextICQ) &&
ValidatePhone(oTextPhone) &&
ValidateText(oTextMessage);
}
}
Как видно из листинга, просто вызываются отдельные функции провер-
ки (ValidateText, ValidateEMail и т. д.), получающие в качестве пара-
метров ссылки на объекты текстовых полей, значения которых следует
контролировать. Код этих вспомогательных функций здесь не публику-
ется. Вы можете просто посмотреть его в файле examples\11
\ex_11_06.htm на компакт-диске.
Усложненный пример формы обратной связи
Файл examples\11\ex_11_07.htm содержит итоговый пример формы об-
ратной связи. Он реализует вторую модель поведения пользовательского
интерфейса (кнопка отправки блокируется при некорректно введенных
данных, рядом с полями расположены индикаторы, отображающие кор-
ректность данных в конкретных полях, при нажатии клавиши <Enter>
фокус ввода переходит к следующему полю). Кроме того, введенные в
форму значения (кроме текста сообщения) запоминаются в cookie (ис-
пользуется библиотека [Link], описанная в главе 7). Форма содержит
все те же поля, что и в предыдущем примере, и выпадающий список,
обеспечивающий выбор получателя сообщения (он был добавлен просто
Ãëàâà 11. Работа с формами 251
для демонстрации возможности запоминания выбранного пункта в
cookie).
При работе скрипта в процессе загрузки страницы производится про-
граммная установка обработчиков событий onkeydown объектам боль-
шинства элементов управления формы для обеспечения перехода фоку-
са ввода при нажатии клавиши <Enter>, а также получение данных
cookie и установка их в соответствующие поля. Вот сокращенный лис-
тинг кода инициализации:
with(oForm)
{
var aTbCtls =
[
"oComboTarget",
...
];
for(var i = 0; i < [Link]; i++)
{
if(i < ([Link] - 1))
oForm[aTbCtls[i]].onkeydown = new Function("e",
"OnForm1KeyDown(e, '" + aTbCtls[i + 1] + "');");
}
var _tmp;
...
[Link] = (_tmp = GetCookie("oTextName")) ?
_tmp : "";
[Link] = (_tmp = GetCookie("oTextEMail")) ?
_tmp : "";
...
}
Функция OnForm1KeyDown, вызываемая из обработчиков onkeydown эле-
ментов управления, производит анализ кода нажатой клавиши и переда-
чу фокуса в случае, если была нажата клавиша <Enter>. Аналогичный
код уже использовался в предыдущих примерах.
Обновление индикаторов корректности данных и состояния кнопки от-
правки, а также занесение новых значений в cookie (для этого вызывает-
ся UpdateCookie) производится функцией OnUpdateForm, вызываемой по
таймеру, который запускается при вызовах функции NeedUpdateForm из
обработчиков onkeydown и onkeyup формы, а также из обработчика
onchange выпадающего списка.
252 Часть III. Создание динамических эффектов
Вот код этой функции:
function OnUpdateForm()
{
var bSuccess = true;
for(var i = 0; i < [Link]; i++)
{
var oTD = [Link](aCtlTDRel[i][0]);
var nRes = aCtlTDRel[i][2]();
[Link] = nRes ? ((nRes > 0) ?
"green" : (aCtlTDRel[i][1] ?
"red" : "gray")) : "red";
bSuccess = bSuccess && nRes && !((nRes < 0) &&
aCtlTDRel[i][1])
}
[Link] = !bSuccess;
UpdateCookie();
}
Функция меняет картинки-индикаторы путем изменения имени CSS-
класса ячейки таблицы, в которых содержатся эти изображения.
OnUpdateForm вызывает функции проверки корректности данных для
элементов управления. Ссылки на эти функции содержатся в глобально
определенном массиве aCtlTDRel:
var aCtlTDRel =
[
["ID_TD_VALID_NAME",1,function()
{ return ValidText([Link]);}],
...
Каждый элемент этого массива соответствует одному элементу управ-
ления формы. Он также является массивом и содержит: значение атри-
бута ID ячейки таблицы, в которой находится изображение-индикатор
корректности данных соответствующего элемента управления, признак
обязательного заполнения поля и, собственно, объект-функцию контро-
ля корректности данных. Функции, содержащиеся в массиве aCtlTDRel,
просто вызывают функции проверки значений определенного типа
(ValidText, ValidEMail и т. д.), практически идентичные аналогичным
функциям, использовавшимся в предыдущем примере, передавая им в
качестве параметров ссылки на элементы управления, данные которых
требуется контролировать.
ГЛАВА 12
Работа с изображениями
Как и в любой другой нише производства программного обеспечения
для различных сред и платформ, разработка в области создания клиент-
ских сценариев, предназначенных для работы в исполняющей среде
браузеров, имеет свои, ярко выраженные особенности. Так, один из ас-
пектов специфики создания клиентских сценариев для браузеров состо-
ит в отсутствии средств свободного графического вывода. Действитель-
но, в отличие от разработчика "классических" прикладных приложений
(desktop applications), которому обычно доступен широкий набор
средств вывода графической информации, Web-разработчик ограничен
возможностями отображения браузером гипертекстовых документов.
Иными словами, на данный момент в общем случае (не считая примене-
ния специальных плагинов, внедренных в документ элементов ActiveX,
Java-апплетов и недокументированных возможностей javascript-прото-
кола) свободное рисование в гипертекстовом документе средствами
клиентских сценариев невозможно. Также невозможен вывод произ-
вольной анимации без внедрения в документ внешних объектов и при-
менения протокола javascript:.
Однако в некоторых задачах презентационная составляющая гипертек-
стового документа имеет весьма важное значение. Поэтому для созда-
ния различного рода динамических эффектов (часто имеющих характер
анимации) очень распространено применение готовых изображений.
Конечно, в этом случае разработчик сильно ограничен средствами, пре-
доставляемыми исполняющей средой (лишь возможностью манипули-
рования структурой документа при помощи интерфейсов DOM и управ-
ления выводом готовых изображений). Но даже при наличии подобных
254 Часть III. Создание динамических эффектов
ограничений посредством умелого применения изображений становится
возможным создание ярких и красочных эффектов на Web-страницах.
В данном случае невозможность использования средств формирования
графических образов окупается чрезвычайной простотой манипулиро-
вания изображениями. В чем-то это похоже на применение спрайтовой
графики в прикладном программном обеспечении. Итак, в этой главе
речь пойдет об изображениях и приемах работы с ними при помощи
JavaScript.
Средства
и приемы работы с изображениями
в гипертекстовом документе
Как вам известно, внедрение изображений в гипертекстовый документ
при помощи HTML-разметки осуществляется посредством исполь-
зования HTML-элемента IMG. В иерархии дерева документа элемен-
ты IMG представлены объектами, реализующими DOM-интерфейс
HTMLImageElement. С помощью обычных средств DOM можно произво-
дить широкий спектр манипуляций изображениями. Однако специфиче-
ские объектные модели браузеров также содержат средства доступа и
работы с изображениями в виде объектов Image и коллекции images
объекта document. Давайте более подробно рассмотрим эти средства
работы с изображениями.
Изображения
в объектной модели документа
Как уже было сказано, объекты изображений в DOM-модели реализуют
интерфейс HTMLImageElement. Согласно спецификации DOM уровня 2,
он содержит ряд атрибутов и не содержит методов (однако в рамках
специфических моделей браузеров объекты изображений содержат ме-
тоды, а также дополнительные свойства — за информацией о них следу-
ет обращаться к документации JavaScript конкретного браузера; для
Internet Explorer подобная информация может быть получена из MSDN).
Эти атрибуты реализуются одноименными свойствами соответствую-
щих объектов в JavaScript. В табл. 12.1 приведен перечень и описание
этих свойств согласно декларации интерфейса HTMLImageElement в спе-
цификации DOM уровня 2.
Ãëàâà 12. Работа с изображениями 255
Таблица 12.1. Свойства DOM-объектов изображений
Свойство Тип Описание
name Строка Имя элемента
align Строка Выравнивание относительно содержимого. См.
значение атрибута align в спецификации
HTML
alt Строка Альтернативный текст для изображения. См.
значение атрибута alt в спецификации HTML
border Строка Ширина бордюра вокруг изображения. См.
значение атрибута border в спецификации
HTML
height Число Высота изображения в пикселах. См. значение
атрибута alt в спецификации HTML
hspace Число Отступ слева и справа от изображения в пик-
селах. См. значение атрибута height в спе-
цификации HTML
isMap Логический Признак использования серверной карты. См.
значение атрибута ismap в спецификации
HTML
longDesc Строка URI, указывающий длинное описание изобра-
жения. См. значение атрибута longdesc в спе-
цификации HTML
src Строка URI источника данных изображения. См. зна-
чение атрибута src в спецификации HTML
useMap Строка Значение, равное значению name используе-
мой клиентской навигационной карты. См. зна-
чение атрибута usemap в спецификации HTML
vspace Число Отступ сверху и снизу от изображения в пик-
селах. См. значение атрибута vspace в спе-
цификации HTML
width Число Ширина изображения в пикселах. См. значение
атрибута width в спецификации HTML
Как видите, используя свойства соответствующих DOM-объектов, мож-
но производить достаточно широкий спектр манипуляций изображе-
ниями. Например, изменять их размеры, отступы, назначать новый
альтернативный текст. К слову, гораздо более универсальным и эффек-
256 Часть III. Создание динамических эффектов
тивным способом изменения параметров отображения элементов изо-
бражений (как, впрочем, и любых других элементов документа) является
изменение встроенной информации о стиле с помощью свойства style
DOM-объекта изображения.
Среди всех перечисленных выше свойств src наиболее интересно для
рассмотрения. Оно определяет URI источника данных изображения.
Если элемент изображения был задан при помощи HTML-разметки, то
свойство src будет содержать значение атрибута src, указанное в раз-
метке. Однако практическое значение свойства src состоит в том, что
при изменении его значения инициируется загрузка нового изображения
в элемент, соответствующий DOM-объекту, которому принадлежит из-
мененное свойство. Таким образом, можно организовать динамическую
смену картинок на Web-странице через заданный интервал времени без
перезагрузки самой страницы. Кроме того, представляется возможной
организация процесса предварительной загрузки изображений, необхо-
димых в дальнейшем для работы скрипта. Для этого можно создать не-
обходимое количество DOM-объектов элементов IMG, установить им
требуемые значения свойств src, а после окончания загрузки изображе-
ний в нужное время добавить их в дерево документа и использовать.
При этом загрузка изображений будет производиться "в фоне" (т. к.
элементы изображения не добавлены в дерево документа, пользователь
не будет видеть сам процесс загрузки). Так, в следующем фрагменте
кода создаются два DOM-объекта элементов IMG, и инициируется за-
грузка изображений:
var oImg1 = [Link]("IMG");
var oImg2 = [Link]("IMG");
[Link] = "[Link]";
[Link] = "[Link]";
В организации процесса предварительной загрузки изображений суще-
ствуют свои нюансы. Они будут описаны в разд. "Общие принципы ра-
боты с изображениями" далее в этой главе.
Объект Image и коллекция images
Как уже упоминалось, специфическая объектная модель браузеров так-
же содержит средства работы с изображениями. Речь идет, прежде все-
го, об объектах типа Image.
Возможность использования объектов типа Image впервые была пред-
ставлена в браузере Netscape 3.0. Эти объекты предназначались для ди-
Ãëàâà 12. Работа с изображениями 257
намической программной замены изображений. В других браузерах
поддержка объектов типа Image также была введена, начиная с ранних
версий. Фактически объекты Image являются аналогом объектов, реали-
зующих интерфейс HTMLImageElement в DOM. Однако они относятся
именно к специфической объектной модели. Так, для создания нового
объекта типа Image не используются методы объектной модели доку-
мента. Ядро исполнения сценариев браузеров содержит конструктор
объектов данного типа, и создать новый объект Image можно, просто
используя оператор new.
var oImg = new Image();
Для возвращения ссылок на объекты типа Image, сопоставленные суще-
ствующим в документе изображениям, в объектную модель браузеров
была введена коллекция images, являющаяся свойством объекта
document. Так, получить ссылку на объект первого по порядку изобра-
жения в документе можно следующим образом:
var oImage = [Link][0];
В настоящее время создание объектов типа Image и использование кол-
лекции images поддерживается в целях обратной совместимости. Одна-
ко в современных браузерах ссылки на объекты изображений, получае-
мые при помощи коллекции images, фактически являются ссылками на
DOM-объекты. Об этом косвенно свидетельствуют списки поддержи-
ваемых ими свойств и методов. Вы можете убедиться в этом сами, про-
анализировав вывод, сгенерированный при помощи следующего скрипта:
var oImg1 = new Image();
var oImg2 = [Link]("IMG");
[Link]("Свойства объекта Image:<br>");
for(name in oImg1)
[Link](name + "<br>");
[Link]("<br>Свойства DOM-объекта изображения:<br>");
for(name in oImg2)
[Link](name + "<br>");
Конечно, в виду возможности полноценного применения методов объ-
ектной модели документа в современных браузерах целесообразность
употребления объектов типа Image весьма сомнительна. Однако просто-
та их создания и семантическая независимость от DOM делают весьма
удобным их использование при решении ряда задач (например, для ор-
258 Часть III. Создание динамических эффектов
ганизации кэширования изображений, о чем будет рассказано в сле-
дующем разделе).
Общие принципы работы с изображениями
Необходимо сразу отметить, что приемы манипулирования элементами
изображений в документе ничем не отличаются от приемов манипули-
рования любыми другими элементами: их можно также создавать, по-
мещать в дерево документа, изменять параметры из представления (на-
пример, размеры, схему позиционирования, координаты), назначать им
обработчики событий, общие для всех элементов (onclick, onmousemove
и т. д.), создавая тем самым различные эффекты. Однако специфика
элементов изображений состоит в том, что их визуальное представление
непосредственно связано с данными внедренного в документ объекта,
получаемыми из внешнего источника. Я уже упоминал о возможности
организации процесса предварительной загрузки изображений, т. е. по-
строения такой схемы работы клиентского скрипта, при которой работа
алгоритмов, обеспечивающих основную его функциональность, не будет
возможна до момента окончательного получения всех данных изобра-
жений, используемых этими алгоритмами. Подобные механизмы работы
клиентских скриптов оказываются весьма важными в случаях эксплуа-
тации Web-сервисов, использующих эти скрипты в условиях наличия
медленных каналов связи у конечных пользователей, передача данных
изображений по которым занимает значительное время. Поэтому давай-
те разберемся в нюансах предварительной загрузки и эффективного ис-
пользования изображений клиентскими сценариями.
Мы уже говорили о том, что программная инициализация загрузки изо-
бражений производится при помощи установки значения URI свойству
src объекту Image либо DOM-объекту изображения. Основной пробле-
мой при решении задачи предварительной загрузки является определе-
ние момента окончательного получения браузером данных загружаемых
изображений. Все дело в том, что современные браузеры производят
асинхронную загрузку внешних объектов. То есть, после изменения
свойства src поток управления исполняющей среды незамедлительно
переходит к выполнению следующих инструкций.
Вышеописанная проблема решается двумя способами: назначением
объектам изображений обработчиков событий onload и onerror, анали-
зом свойства complete объекта изображения либо комбинацией этих
способов. Рассмотрим использование этих обработчиков и свойства
complete.
Ãëàâà 12. Работа с изображениями 259
Обработчики событий onload и onerror изображений.
Свойство complete
Обработчик событий onload определен в спецификации HTML. Соглас-
но спецификации, допустимо его использование с элементами BODY и
FRAMESET (если данный обработчик назначен для этих элементов, его код
вызывается по окончании загрузки документа либо всех фреймов доку-
мента соответственно). Однако браузеры поддерживают назначение это-
го обработчика и для внедряемых в документ объектов (изображений,
скриптов и т. д.). Установка обработчика onload позволяет отследить
момент окончания загрузки изображений для объектов, создаваемых как
при помощи методов DOM, так и для объектов типа Image.
var oImg1 = [Link]("IMG");
var oImg2 = new Image();
[Link] = new Function("alert('Загружено [Link]');");
[Link] = new Function("alert('Загружено [Link]');");
[Link] = "[Link]";
[Link] = "[Link]";
Обратите внимание, что обработчик onload необходимо установить до
изменения значения свойства src для того, чтобы он гарантированно
был вызван (при работе ядра исполнения сценариев браузера в условиях
современных многопоточных операционных систем не является фактом
то, что после изменения свойства src изображение не будет полностью
загружено до момента исполнения следующей инструкции скрипта).
В браузерах семейства Mozilla существует неприятная особенность,
связанная с обработчиком событий onload. Суть этой особенности
состоит в том, что при добавлении в дерево документа DOM-объекта
элемента IMG, в который уже загружено изображение, его обработчик
onload вызывается еще раз. Эту особенность необходимо учитывать
при написании скриптов, реализующих предварительную загрузку
изображений.
Еще одним средством контроля окончания загрузки изображений явля-
ется свойство complete объектов изображений (как объектов типа Image,
так и создаваемых при помощи методов DOM), значение которого
должно быть равно false, если объект не загружен, и true, если загру-
жен. Однако при использовании свойства complete необходимо учиты-
вать особенности конкретных браузеров, делающие в общем случае за-
труднительным его использование. В первую очередь речь идет о на-
260 Часть III. Создание динамических эффектов
чальных значениях этого свойства (значениях свойства объектов изо-
бражений, для которых не было задано свойство src). Так, для объектов
типа Image в браузерах Internet Explorer и Opera оно равно false, а в
браузерах семейства Mozilla — true. Для объектов элементов изображе-
ний, созданных при помощи метода createElement объекта document,
начальное значение этого свойства равно true как в браузерах семейства
Mozilla, так и в Opera (в Internet Explorer оно равно false в обоих случа-
ях). Таким образом, производить анализ данного свойства имеет смысл
только после инициации загрузки изображения. Однако и тут существу-
ют некоторые проблемы — браузеры семейства Mozilla устанавливают
значение свойства complete в true при ошибках загрузки (например,
если изображение не удалось получить с удаленного сервера). Хотя, эту
проблему можно обойти с помощью установки обработчика onerror.
Обработчик onerror предназначен для отслеживания ошибок загрузки.
Его код, в частности, вызывается в тех случаях, когда браузеру не удает-
ся получить данные изображения. Например, при исполнении следую-
щего фрагмента кода, если URI, устанавливаемый в качестве значения
свойства src, указывает на несуществующий ресурс изображения, будет
выведено сообщение об ошибке.
var oImg1 = new Image();
[Link] = new Function(
"alert('Ошибка загрузки изображения');");
[Link] = "[Link]
Следует отметить одну особенность использования обработчика
onerror: если объект изображения создан при помощи метода
createElement и для него инициализируется загрузка изображения по
URI, указывающему на несуществующее изображение на локальной
машине, то в браузере Mozilla обработчик onerror не вызывается. Если
же производится попытка загрузки несуществующего изображения на
локальной машине для объекта типа Image, то onerror не вызывается ни
в Mozilla, ни в Internet Explorer, ни в Opera.
Несколько рекомендаций по работе с изображениями
Ранее в этой главе были описаны средства манипулирования изображе-
ниями, а также специфические для объектов изображений обработчики
событий и свойства. Используя их, можно создавать скрипты, реали-
зующие различные динамические эффекты. Важным аспектом разработ-
ки подобных скриптов является применение таких алгоритмов и схем
работы, которые помогают минимизировать или избежать влияния спе-
Ãëàâà 12. Работа с изображениями 261
цифических особенностей конкретных браузеров на их эксплуатацион-
ные характеристики. Здесь мы рассмотрим несколько вопросов эффек-
тивной работы с изображениями средствами JavaScript. Для простоты,
вначале я изложу основные положения в виде рекомендаций, а затем
дам более подробные пояснения по некоторым из них.
1. При написании скриптов, работающих с изображениями, где это
возможно, избегайте использования устаревших объектов типа Image
и коллекции images. Применяйте вместо них объекты и методы
DOM. Этим вы достигнете унификации исходного кода и обеспечите
своим скриптам большую переносимость и независимость от специ-
фических особенностей конкретных браузеров.
2. При создании скриптов, реализующих предварительную загрузку
изображений, предусмотрите обработку ошибок загрузки, используя
установку обработчиков onerror.
3. При реализации предварительной загрузки изображений помните об
особенностях вызова обработчика onload в браузерах семейства
Mozilla (как после загрузки изображения, так и после добавления
объекта изображения в дерево документа). В некоторых случаях, на-
пример, когда из обработчика onload одного изображения иницииру-
ется загрузка следующего, а по окончании загрузки изображения до-
бавляются в документ, подобное поведение может приводить к бес-
конечной циклической загрузке одних и тех же изображений.
4. При реализации предварительной загрузки изображений в реальных
проектах имеет смысл ввести контроль времени загрузки каждого
изображения. Это поможет избежать "зависания" скрипта в случае
возникновения недопустимо больших задержек при передаче данных
по каналам связи, а также в том случае, если в браузере пользователя
отключена загрузка изображений.
5. Будьте осторожны при реализации кэширования изображений.
Первые три пункта этих рекомендаций вполне прозрачны, а по четвер-
тому и пятому необходимо дать некоторые пояснения.
Итак, пункт четвертый. В нем говорится о возможности организации
ограничений на время загрузки изображений. Целесообразность реали-
зации подобных ограничений вытекает все из тех же понятий приемле-
мых эксплуатационных характеристик Web-ориентированных систем.
Одним из требований к техническим характеристикам скриптов, разра-
батываемых для различных Web-сервисов, может являться разумное
время подготовки к работе этих скриптов (загрузки необходимых дан-
262 Часть III. Создание динамических эффектов
ных, инициализации скрипта). Это особо актуально для скриптов, за-
гружающих изображения. Из-за недостаточной пропускной способности
каналов связи либо слишком медленной выдачи данных удаленным сер-
вером, загрузка изображений скриптом может затянуться на неопреде-
ленное время. Если же в браузере пользователя отключена загрузка изо-
бражений, скрипт вообще может "зависнуть", уйдя в процесс бесконеч-
ного ожидания получения данных (в этом случае не будут вызваны ни
обработчик onload, ни onerror объектов изображений).
Ввиду возможности возникновения вышеописанных проблем, необхо-
димость контроля времени загрузки изображений становится весьма
очевидной. Ввести подобный контроль наиболее просто в скриптах, ис-
пользующих последовательную предварительную загрузку изображений
(в начале работы скрипта инициализируется загрузка только одного изо-
бражения, по окончании его загрузки — следующего, и т. д.). В этом
случае можно предложить следующую схему работы скрипта предвари-
тельной загрузки.
После инициации загрузки изображения запускается таймер (при
помощи [Link]) с величиной интервала задержки, рав-
ной максимально допустимому времени загрузки изображения (его
имеет смысл определять индивидуально для каждого конкретного
случая, в зависимости от предполагаемых объемов данных изобра-
жений, которыми будет оперировать скрипт).
При возникновении события onload таймер останавливается
([Link]), инициализируется загрузка следующего
изображения, и таймер запускается снова.
При возникновении события таймера останавливается процесс за-
грузки изображений, и пользователю выводится сообщение об ошибке.
Данная схема работы используется, в частности, в скрипте фотогалереи,
который будет рассмотрен далее в этой главе.
В пятом пункте приведенных выше рекомендаций говорится о кэширо-
вании изображений. Поясню, что я понимаю под кэшированием и поче-
му советую применять его с осторожностью.
Часто в скриптах, манипулирующих изображениями (клиентских скрип-
тах фотогалерей и т. д.) для обеспечения возможности отображения од-
нажды загруженных изображений без их повторной загрузки, использу-
ется некий прием. Его суть состоит в следующем. Для инициализации
загрузки изображения (в случае предварительной загрузки) либо после
загрузки изображения в элемент IMG (заданный HTML-разметкой или
Ãëàâà 12. Работа с изображениями 263
при помощи методов DOM) создается объект типа Image, его свойству
src присваивается URI целевого изображения. После окончания исполь-
зования изображения (при отсутствии необходимости отображения его в
документе) элемент IMG изображения уничтожается, либо в него загру-
жается другое изображение. При этом сохраняется ссылка на объект
типа Image, свойство src которого по-прежнему хранит URI использо-
вавшегося изображения. Если необходимость отображения в документе
уже использовавшегося изображения возникает вновь, то значению src
DOM-объекта элемента IMG, в котором оно должно быть выведено, про-
сто присваивается значение свойства src упоминавшегося ранее объекта
типа Image. Следующий фрагмент кода иллюстрирует данный принцип:
var oImg0 = new Image();
[Link] = "[Link]";
...
// код ожидания полной загрузки изображения [Link]
...
var oImg1 = [Link]("IMG");
var oImg2 = [Link]("IMG");
[Link] = [Link];
[Link] = [Link];
// добавляем oImg1 и oImg2 в документ и как-то используем
Считается, что браузер должен сохранять (кэшировать) данные тех изо-
бражений, для которых существуют объекты типа Image, значение свой-
ства src которых равно URI изображения. А при установке свойству src
других объектов изображений значения URI изображения, сохраненного
браузером, данные изображения должны браться из кэша. Надо отме-
тить, что очень часто именно это и происходит, но в общем случае это
не так. Действительно, в настоящее время браузеры применяют интен-
сивное кэширование, причем не только изображений, а всех внедряемых
в документ объектов, а также самих документов. Давайте разберемся
с тем, какие обстоятельства влияют на сохранение данных браузером
в кэше.
Прежде всего, следует отметить, что существуют два вида кэша: кэш в
оперативной памяти и файловый кэш. В файловый кэш (файлы на ма-
шине пользователя) браузеры могут сохранять большинство получен-
ных ими данных. Эти данные могут использоваться не только во время
текущей сессии, но и через длительный интервал времени, при после-
дующих запусках браузера. Но нас интересует, прежде всего, кэш в опе-
ративной памяти. Поскольку объем оперативной памяти ограничен,
объем такого кэша также ограничен. Мало того, браузеры (один из
264 Часть III. Создание динамических эффектов
них — Opera) могут предлагать пользователю возможность настройки
объема кэша (как в оперативной памяти, так и на диске) вплоть до его
(кэширования) отключения. В этом случае описанный выше трюк с со-
хранением в памяти объектов типа Image перестанет работать.
Вторым фактором, влияющим на сохранение браузером изображений в
кэше, являются заголовки HTTP-ответов, отдаваемые удаленным серве-
ром. Во-первых, в них могут содержаться поля, напрямую запрещающие
кэширование передаваемых данных. Во-вторых, в заголовке HTTP-
ответа обычно передается информация о дате/времени модификации
загружаемого объекта и часто — о дате/времени его "устаревания".
В случае, если время модификации не передается, либо передаваемое
значение времени "устаревания" данных заведомо меньше значения те-
кущего момента времени, браузер также (скорее всего) не будет поме-
щать изображение в кэш.
Также, кэширование изображений напрямую зависит от особенностей
конкретных браузеров. Например, некоторые версии Internet Explorer
запрашивают изображения с сервера вне зависимости от настроек кэша
и того, загружалось ли раньше данное изображение. И, наконец, стоит
помнить о возможности очистки кэша пользователем, предусмотренной
во многих браузерах, во время работы скрипта.
Таким образом, ввиду изложенных обстоятельств, способ реализации
кэширования изображений с помощью объектов типа Image не выглядит
надежным. Более правильным решением при реализации скриптов, тре-
бующих некоторого перечня загруженных и готовых к немедленному
использованию изображений (например, скриптов, осуществляющих
анимацию), представляется создание необходимого количества DOM-
объектов элементов IMG, добавления их в дерево документа, скрытия
(при надобности) путем изменения стилевой информации, загрузки в
них изображений и дальнейшего использования таким образом, чтобы
избежать изменения значений их свойств src (например, отображение и
скрытие изображений можно осуществлять путем изменения встроенной
информации о стиле, без уничтожения объектов изображений или их
исключения из дерева документа).
Практические примеры работы
с изображениями
Далее мы рассмотрим несколько примеров, иллюстрирующих работу
с изображениями средствами клиентских JavaScript-сценариев.
Ãëàâà 12. Работа с изображениями 265
Предварительная загрузка изображений
с обработкой ошибок загрузки
В файле examples\12\ex_12_01.htm на компакт-диске содержится пример
скрипта, осуществляющего фоновую загрузку (с обработкой ошибок), а
затем одновременное отображение нескольких изображений. В скрипте
предусмотрена индикация состояния процесса загрузки (до момента
окончательной загрузки всех изображений, на странице отображаются
несколько элементов DIV, эмулирующих элемент управления progress
control). Рассмотрим его работу.
Перечень загружаемых изображений задается массивом aImgNames, ини-
циализируемым в начале скрипта. Он содержит имена файлов изобра-
жений. Базовый URL для всех файлов определяется переменной
strRootUrl. Количество изображений сохраняется в переменной
nImgCount.
var strRootUrl = "img/";
var aImgNames =
[
"[Link]",
"[Link]",
...
];
var nImgCount = [Link];
Скрипт загружает изображения последовательно, т. е. процесс загрузки
очередного изображения инициализируется после завершения загрузки
предыдущего. Переменная nLoaded содержит порядковый номер загру-
жаемого в данный момент изображения. Ссылки на DOM-объекты изо-
бражений хранятся в массиве aImages. В скрипте предусмотрено огра-
ничение времени загрузки каждого изображения. При этом используется
таймер. Идентификатор таймера сохраняется в переменной nTimerID.
После окончания загрузки все изображения добавляются в дерево доку-
мента. Родительским узлом при этом служит DOM-объект элемента DIV
(заданного в HTML-разметке) со значением атрибута ID, равным
ID_CONTAINER. Ссылка на данный объект сохраняется в переменной
oContainer. Все вышеперечисленные переменные также инициализиру-
ются в начале скрипта.
var nLoaded = 0;
var nTimerID = 0;
266 Часть III. Создание динамических эффектов
var aImages = new Array();
var oContainer = [Link]("ID_CONTAINER");
Все DOM-объекты изображений создаются одновременно, при инициа-
лизации скрипта. Им тут же назначаются обработчики событий оконча-
ния загрузки (функция OnImageLoadComplete) и ошибки загрузки (функ-
ция OnImageLoadError). Затем ссылки на эти объекты добавляются
в массив aImages.
for(var i = 0; i < nImgCount; i++)
{
var oImg = [Link]("IMG");
[Link] = OnImageLoadComplete;
[Link] = OnImageLoadError;
aImages[i] = oImg;
}
Загрузка каждого изображения инициализируется в функции LoadImage,
первым параметром которой передается порядковый номер этого изо-
бражения.
function LoadImage(nImage)
{
ResetTimer();
aImages[nImage].src = strRootUrl + aImgNames[nImage];
}
Как видно из листинга, функция LoadImage вызывает функцию
ResetTimer. ResetTimer производит запуск (или перезапуск) таймера для
отслеживания момента истечения времени, отведенного для загрузки
каждого изображения. Остановка таймера производится функцией
KillTimer. Листинг функций ResetTimer и KillTimer приведен далее.
function KillTimer()
{
if(nTimerID)
{
clearInterval(nTimerID);
nTimerID = 0;
}
}
function ResetTimer()
Ãëàâà 12. Работа с изображениями 267
{
KillTimer();
nTimerID = setInterval("OnImageLoadError();", 10000);
}
Как видите, из обработчика событий таймера просто производится вы-
зов функции-обработчика событий ошибок загрузки изображений. Ве-
личина задержки таймера составляет десять секунд. При эксплуатации
скрипта в реальных условиях разумно установить большую величину
задержки. Теперь давайте рассмотрим работу обработчиков
OnImageLoadError и OnImageLoadComplete. Их листинг приведен далее.
function OnImageLoadError()
{
KillTimer();
for(var i = 0; i < nImgCount; i++)
delete aImages[i];
[Link]("ID_DIV_PROGRESS").[Link] =
"none";
alert("Ошибка загрузки изображений");
}
function OnImageLoadComplete()
{
KillTimer();
if(nLoaded > -1)
{
nLoaded++;
[Link]("ID_DIV_INDICATOR").[Link] =
(nLoaded * 30).toString(10) + "px";
if(nLoaded < nImgCount)
LoadImage(nLoaded);
else
{
nLoaded = -1;
LoadComplete();
}
}
}
268 Часть III. Создание динамических эффектов
Как уже говорилось, обработчик OnImageLoadError вызывается либо при
возникновении ошибок при загрузке очередного изображения, либо при
возникновении события таймера вследствие истечения интервала допус-
тимого времени загрузки. Из листинга видно, что данный обработчик
останавливает таймер, удаляет все DOM-объекты изображений, скрыва-
ет индикатор процесса загрузки и выводит сообщение об ошибке. Таким
образом, при вызове этого обработчика процесс загрузки полностью
прерывается.
Обработчик OnImageLoadComplete вызывается после окончания загрузки
очередного изображения. Он также останавливает таймер. Далее анали-
зируется значение переменной nLoaded. Это сделано для обхода особен-
ности браузеров семейства Mozilla (вызов обработчика onload изобра-
жений как при окончании загрузки, так и при добавлении DOM-объекта
изображения в дерево документа) для того, чтобы избежать циклической
загрузки изображений. Если значение счетчика nLoaded больше –1, оно
увеличивается и, если полученное значение меньше количества изобра-
жений (nImgCount), при помощи вызова LoadImage инициализируется
процесс загрузки следующего изображения. Если же обработчик
OnImageLoadComplete был вызван вследствие окончания загрузки по-
следнего изображения (не выполняется условие nLoaded<nImgCount),
счетчик nLoaded устанавливается в значение –1 и вызывается функция
LoadComplete.
Назначение функции LoadComplete состоит в скрытии индикатора про-
гресса загрузки, добавлении элементов изображений в дерево документа
и их отображении. Далее приведен листинг этой функции.
function LoadComplete()
{
[Link]("ID_DIV_PROGRESS").[Link] =
"none";
for(var i = 0; i < nImgCount; i++)
[Link](aImages[i]);
[Link] = "block";
}
Скрытие индикатора производится при помощи изменения встроенной
информации о стиле (значение CSS-свойства display изменяется на
"none"). Таким же образом достигается эффект одновременного показа
всех изображений: при добавлении изображений в дерево документа их
родительским узлом служит скрытый на момент добавления элемент DIV
(значение свойства display в его стилевой информации равно "none"), а
Ãëàâà 12. Работа с изображениями 269
после добавления, значение этого свойства изменяется на "block" (эле-
мент отображается вместе со всем его содержимым).
Как видите, реализация предварительной загрузки изображений с обра-
боткой ошибок и ограничением времени загрузки не очень сложна и
требует написания весьма небольшого объема кода.
Пример создания слайд-шоу
с предварительной загрузкой изображений
Предыдущий скрипт, рассмотренный нами, производил загрузку и одно-
временный показ нескольких изображений. Его пример неплохо иллю-
стрирует принципы организации предварительной загрузки изображе-
ний с обработкой ошибок. Однако возможность практического исполь-
зования данного скрипта весьма сомнительна (вряд ли вы захотите
применять скрипт только ради того, чтобы одновременно показать поль-
зователю несколько изображений). Поэтому давайте несколько модифи-
цируем его, приспособив для решения какой-либо практической задачи.
Такой задачей может являться реализация слайд-шоу — эффекта перио-
дической цикличной смены изображений в определенной области Web-
страницы. Конечно, создать подобный эффект можно и другими средст-
вами, например, внедрив в Web-страницу "анимированный GIF" (объект
изображения в формате GIF, содержащий несколько кадров), либо Flash-
ролик. Однако, если учесть, что достаточно часто пользователи настраи-
вают свои браузеры на отображение только первого кадра анимации и
запрещают использование Flash, такой скрипт может оказаться весьма
полезным.
Пример скрипта, осуществляющего предварительную загрузку, а затем
вывод изображений в режиме слайд-шоу представлен в файле
examples\12\ex_12_02.htm на компакт-диске. Как и говорилось, это не-
много доработанный пример из предыдущего раздела. Поэтому я не бу-
ду детально описывать этот скрипт (он содержит те же функции и пере-
менные, имеющие то же назначение, что и в примере ex_12_01.htm),
здесь мы рассмотрим только внесенные изменения.
Итак, главным функциональным отличием данного скрипта от преды-
дущего является не одновременный, а циклический вывод изображений.
Очевидно, для этого необходимо использовать таймер. В предыдущем
примере добавление изображений в документ и их отображение произ-
водилось в функции LoadComplete. В данном примере ее код изменен.
270 Часть III. Создание динамических эффектов
function LoadComplete()
{
[Link]("ID_DIV_PROGRESS").[Link] =
"none";
setInterval(OnTimer, 2000);
}
Здесь функция LoadComplete просто скрывает индикатор процесса за-
грузки и запускает таймер с интервалом две секунды, обработчиком ко-
торого является функция OnTimer. В OnTimer как раз и производится
циклическая замена одного изображения другим. Листинг функции
OnTimer приведен далее.
function OnTimer()
{
if(oPrevShow == null)
[Link](aImages[nShow]);
else
[Link](aImages[nShow], oPrevShow);
oPrevShow = aImages[nShow];
nShow++;
if(nShow >= nImgCount)
nShow = 0;
}
Как вы можете видеть, замена изображения производится при помощи
метода replaceChild DOM-объекта элемента DIV (переменная
oContainer), являющегося контейнером для элементов изображений.
Ссылка на DOM-объект предыдущего добавленного в документ изобра-
жения сохраняется в переменной oPrevShow. В начале работы скрипта
эта переменная инициализируется значением null, а в функции OnTimer
ее значение анализируется. И если ни одно изображение еще не добав-
лялось в документ (oPrevShow равно null), оно добавляется методом
appendChild (для использования метода replaceChild необходим суще-
ствующий узел дерева документа, и appendChild этот узел создает). Но-
мер текущего выведенного изображения хранится в переменной nShow.
Ее значение циклически изменяется от 0 до порядкового номера послед-
него изображения, увеличиваясь на единицу при каждом вызове
OnTimer. На этом список модификаций, внесенных в предыдущий
скрипт, заканчивается.
Ãëàâà 12. Работа с изображениями 271
Обратите внимание, что кроме слайд-шоу данный скрипт вполне может
быть использован для создания анимации путем быстрого чередования
изображений (для создания анимации достаточно просто подобрать со-
ответствующую серию изображений и уменьшить интервал задержки
таймера).
Скрипт галереи изображений
Скрипт слайд-шоу, рассмотренный нами в предыдущем разделе, произ-
водит чередование изображений без участия пользователя и через фик-
сированный промежуток времени. Однако, немного усовершенствовав
его путем введения возможности управления процессом просмотра, мы
получим полноценный скрипт для организации галереи изображений,
готовый к применению на сайте. Очень часто такие галереи реализуются
при помощи серверных скриптов. Но от них наш скрипт (он находится в
файле examples\12\ex_12_03.htm на компакт-диске) выгодно отличается
тем, что позволяет просматривать изображения без перезагрузки стра-
ницы и к тому же, наличие кэширования исключает повторную загрузку
уже просмотренных изображений.
Основное отличие данного скрипта от предыдущих состоит в том, что
изображения не загружаются автоматически, загрузка (или отображе-
ние) очередного изображения инициализируется пользователем. Для
управления процессом просмотра изображений в HTML-разметке стра-
ницы заданы ссылки с атрибутами href, содержащими URL типа
javascript, из которых производятся вызовы функций PrevImage и
NextImage скрипта, для просмотра предыдущего и следующего изобра-
жения соответственно. Код функций PrevImage и NextImage будет рас-
смотрен далее.
<a href="javascript:PrevImage();"><nobr><< --</nobr></a>
<a href="javascript:NextImage();"><nobr>-- >></nobr></a>
Как и прежде, список имен изображений задается массивом aImgNames,
их количество сохраняется в переменной nImgCount, базовый URL фай-
лов изображений содержится в переменной strRootUrl, ссылки на
DOM-объекты уже загруженных изображений хранятся в массиве
aImages. В начале работы скрипта этот массив создается с количеством
элементов, равным количеству изображений, а затем его элементы ини-
циализируются значением null:
var aImages = new Array(nImgCount);
for(var i = 0; i < nImgCount; i++)
aImages[i] = null;
272 Часть III. Создание динамических эффектов
Поскольку изображения будут загружаться по одному в произвольный
момент времени, индикатор загрузки в виде элемента управления
progress control более не нужен. Однако желательно оповещать пользо-
вателя о том, что происходит загрузка очередного изображения. Это
можно сделать, например, при помощи отображения на странице в про-
цессе загрузки небольшой анимации. Для этого при помощи HTML-
разметки в документ внедрен объект анимационного изображения в
формате GIF.
<img src="img/[Link]" ID="ID_IMG_WAIT"
style="visibility:hidden">
Как видите, изначально это изображение скрыто. Скрипт будет отобра-
жать и скрывать его в нужные моменты времени. Для этого, при ини-
циализации скрипта, на DOM-объект этого изображения получается
ссылка:
var oImgWait = [Link]("ID_IMG_WAIT");
В начале скрипта также инициализируются переменные: nCurImage (но-
мер изображения, просматриваемого пользователем в текущий момент),
nImageLoad (номер загружаемого в текущий момент времени изображе-
ния), oImageLoad (ссылка на DOM-объект изображения, загружаемого в
текущий момент времени), nTimerID (идентификатор таймера, запускае-
мого для ограничения времени загрузки изображения), oContainer
(ссылка на DOM-объект элемента DIV — контейнера элементов про-
сматриваемых пользователем изображений).
var nCurImage = -1;
var nImageLoad = 0;
var oImageLoad = null;
var nTimerID = 0;
var oContainer = [Link]("ID_CONTAINER");
В скрипте предусмотрен вывод информации о просматриваемом в дан-
ный момент изображении. Информация выводится в текстовый узел
дерева документа, который также создается при инициализации скрипта.
var oCounter = [Link]("");
[Link]("ID_DIV_COUNTER").
appendChild(oCounter);
Как уже было сказано, отображение того или иного изображения произ-
водится вследствие вызова функций PrevImage и NextImage. Далее при-
веден их листинг.
Ãëàâà 12. Работа с изображениями 273
function NextImage()
{
if(oImageLoad == null)
{
var nImage = nCurImage + 1;
if(nImage == nImgCount)
nImage = 0;
ShowImage(nImage);
}
}
function PrevImage()
{
if(oImageLoad == null)
{
var nImage = nCurImage - 1;
if(nImage < 0)
nImage = nImgCount - 1;
ShowImage(nImage);
}
}
Эти функции просто производят вычисление номера следующего изо-
бражения, которое должно быть показано пользователю, и вызывают
функцию ShowImage, передавая ей этот номер. При этом проверяется
значение переменной oImageLoad и, если оно равно null (что свидетель-
ствует о том, что в данный момент происходит процесс загрузки какого-
либо из изображений), никаких действий не производится.
Должно быть, вы уже поняли, что именно функция ShowImage управляет
выводом конкретного изображения. Далее приведен листинг этой функ-
ции.
function ShowImage(nImage)
{
if(aImages[nImage] != null)
{
if(nCurImage > -1)
aImages[nCurImage].[Link] = "none";
nCurImage = nImage;
274 Часть III. Создание динамических эффектов
aImages[nImage].[Link] = "block";
[Link] = (nCurImage + 1).toString() + " из " +
[Link]();
}
else
LoadImage(nImage);
}
Функция производит проверку элемента массива aImages с индексом,
равным номеру изображения, которое необходимо отобразить. Факт
того, что этот элемент содержит значение null, означает, что данное
изображение еще не загружено, в этом случае вызывается функция
LoadImage для инициализации процесса его загрузки. В противном слу-
чае данный элемент массива содержит ссылку на существующий DOM-
объект изображения, и функция ShowImage "показывает" его, изменяя
значение свойства display встроенной информации о стиле на "block".
Перед этим таким же способом производится скрытие предыдущего
изображения (значение свойства display его DOM-объекта изменяется
на "none"). Функция ShowImage также производит вывод номера про-
сматриваемого изображения и общего их количества в созданный ранее
текстовый узел (переменная oCounter) дерева документа путем установ-
ки значения свойства nodeValue.
Механизм загрузки изображений по сравнению с предыдущими приме-
рами изменился незначительно. Загрузка по-прежнему инициализирует-
ся в функции LoadImage. Однако теперь в ней также создается DOM-
объект изображения (здесь же ему назначаются обработчики событий
окончания и ошибок загрузки) и производится вызов функции ShowWait
для вывода анимационного изображения, служащего индикатором про-
цесса загрузки.
function LoadImage(nImage)
{
nImageLoad = nImage;
oImageLoad = [Link]("IMG");
[Link] = "none";
[Link] = OnImageLoadComplete;
[Link] = OnImageLoadError;
ShowWait(true);
Ãëàâà 12. Работа с изображениями 275
ResetTimer();
[Link] = strRootUrl + aImgNames[nImage];
}
function ShowWait(bShow)
{
[Link] = (bShow ? "visible" : "hidden");
}
Код функций KillTimer и ResetTimer остался прежним. Но немного из-
менилась функциональность обработчиков OnImageLoadComplete и
OnImageLoadError.
В обработчике OnImageLoadComplete теперь производится добавление в
документ очередного загруженного изображения. При этом ссылка на
соответствующий DOM-объект помещается в массив aImages. При по-
мощи вызова ShowWait с параметром false производится скрытие ани-
мации, выведенной на время загрузки, а затем вызывается уже рассмот-
ренная нами функция ShowImage, которая осуществляет отображение
только что загруженного изображения, изменяя значение его свойства
display.
function OnImageLoadComplete()
{
KillTimer();
ShowWait(false);
[Link] = null;
[Link] = null;
aImages[nImageLoad] = oImageLoad;
[Link](oImageLoad);
ShowImage(nImageLoad);
nImageLoad = -1;
oImageLoad = null;
}
В обработчике OnImageLoadError также скрывается анимация, останав-
ливается таймер, запущенный при вызове LoadImage, DOM-объект,
в который загружалось изображение, удаляется, а затем выводится со-
общение об ошибке.
276 Часть III. Создание динамических эффектов
function OnImageLoadError()
{
KillTimer();
ShowWait(false);
delete oImageLoad;
nImageLoad = -1;
oImageLoad = null;
alert("Ошибка загрузки изображения");
}
Таким образом, в случае успешного окончания загрузки изображения
оно будет добавлено в документ и показано пользователю, ссылка на
DOM-объект этого изображения будет сохранена в массиве aImages.
Если же произошла ошибка, DOM-объект будет просто удален и смены
изображений в документе не произойдет. Так как при просмотре поль-
зователем изображений галереи их DOM-объекты не удаляются (и даже
не исключаются из дерева документа), то при следующем переходе к
уже просмотренному изображению в функции ShowImage будет произве-
дено лишь его отображение (и скрытие предыдущего изображения) без
повторной загрузки.
Напоследок стоит отметить тот факт, что показ первого изображения
галереи инициируется автоматически, с секундной задержкой во время
инициализации скрипта.
setTimeout("ShowImage(0);", 1000);
В каталоге examples\12 на компакт-диске, где расположены файлы
примеров к данной главе, находится подкаталог img с файлами изо-
бражений, загружаемых скриптами. Если вы захотите проверить ме-
ханизм обработки ошибок загрузки, реализованный в скриптах, то мо-
жете перенести каталог с примерами на жесткий диск своей машины и
удалить один или несколько файлов изображений из подкаталога img,
либо исправить код любого из скриптов, например, добавив в массив
aImages элемент с именем несуществующего изображения.
ГЛАВА 13
Создание
анимационных эффектов
В этой главе мы рассмотрим создание анимации различного рода сред-
ствами клиентских сценариев на JavaScript. Сначала мы обсудим общие
принципы создания анимационных эффектов и средства, доступные для
этого в исполняющей среде браузеров. Затем сразу же перейдем к прак-
тическим примерам, реализующим подобные эффекты.
Средства и принципы создания
анимации на Web-страницах
Как указывалось во вступлении к предыдущей главе, презентационная
составляющая гипертекстовых документов часто оказывается весьма
важной (особенно в документах, используемых непосредственно в Web).
Во многих случаях оформление Web-страницы должно содержать эле-
менты анимации. Применение "тяжелых" технологий (например, вне-
дрение в страницу Flash-роликов) для реализации простых анимацион-
ных эффектов может не устраивать Web-разработчика по тем или иным
причинам. В этом случае единственным средством для решения постав-
ленных задач является применение клиентских скриптов.
Ранее также указывалось, что отсутствие способов свободного графиче-
ского вывода является одним из специфических аспектов создания кли-
ентских сценариев для браузеров. То есть рисование (в привычном по-
нимании) скриптом осуществлено быть не может. Давайте же рассмот-
рим перечень средств, предоставляемых браузером, которые мы могли
бы использовать для создания анимационных эффектов при помощи
сценариев.
278 Часть III. Создание динамических эффектов
Для начала необходимо отметить несколько фактов, касающихся непо-
средственно анимации. Очевидно, что анимация подразумевает некото-
рые изменения во времени состояний изображаемых объектов (размера,
цвета, положения). Эти состояния дискретны. Однако, вследствие инер-
ции восприятия (особенность человеческой психики), быстрое чередо-
вание дискретных состояний объектов воспринимается как непрерывное
изменение состояния.
Большинство прикладных программ, осуществляющих вывод анимации,
воспроизводят ее кадрами. При этом выводу кадра предшествует этап
визуализации, т. е. отрисовки текущих состояний всех объектов, нахо-
дящихся в кадре. В этом смысле разработчики клиентских скриптов для
браузеров имеют некоторое преимущество, заключающееся в простоте
управления структурой документа и представлением отдельных элемен-
тов в нем с помощью методов и свойств объектной модели. Иными сло-
вами, при изменении представления элементов браузер автоматически
производит визуализацию и отображение нужной части документа.
Таким образом, в качестве основных средств создания анимации мы
будем использовать свойства и методы объектной модели документа,
служащие для изменения параметров представления отдельных элемен-
тов. Необходимо отметить, что в подавляющем большинстве случаев
изменить параметры отображения элемента в документе можно при по-
мощи модификации его встроенной информации о стиле. То есть путем
изменения свойств объекта style, в свою очередь являющегося свойст-
вом соответствующего DOM-объекта. К слову, объект style будет очень
часто использоваться в примерах данной главы.
Примеры создания анимации
Далее мы рассмотрим примеры скриптов, реализующих различные ани-
мационные эффекты.
Плавное "проявление" и "исчезновение"
текста
Скрипт, находящийся в файле examples\13\ex_13_01.htm, является при-
мером реализации простой анимации, заключающейся в изменении цве-
та фрагмента текста. Цвет текста, расположенного на белом фоне, пе-
риодически плавно изменяется от белого до черного, а затем — обратно
до белого. В результате создается эффект "проявления" и исчезновения
Ãëàâà 13. Создание анимационных эффектов 279
изображения (вначале оно постепенно обретает насыщенность, затем
теряет ее, полностью сливаясь с фоном). Такой эффект иногда исполь-
зуют для привлечения внимания пользователя к важной информации.
Я встречал Web-форумы, где подобным образом выделялся блок с со-
общением, призывающим прочитать правила общения, принятые на
данном форуме.
Итак, документ ex_13_01.htm содержит HTML-разметку, определяю-
щую один элемент DIV с атрибутом ID, установленным в значение
ID_DIV_FADER. Этот элемент содержит текст, цвет которого будет изме-
няться:
<DIV class="fader" ID="ID_DIV_FADER">Текст</DIV>
Код скрипта очень мал по объему, поэтому я приведу его целиком.
var nDelta = -1;
var nBrightness = 255;
function OnTimer()
{
nBrightness += nDelta;
if(nBrightness == 0)
nDelta = 1;
else if(nBrightness == 255)
nDelta = -1;
var str = [Link](10);
[Link]("ID_DIV_FADER").[Link] =
"RGB(" + str + "," + str + "," + str + ")";
}
[Link](OnTimer, 20);
Как можно видеть, принцип работы скрипта состоит в изменении значе-
ния свойства color объекта style DOM-объекта, соответствующего
элементу DIV, содержащему текст в обработчике событий таймера. Со-
ответственно изменяется CSS-свойство color этого элемента. Свойству
color присваивается числовое значение цвета в функциональном пред-
ставлении RGB (RGB(r, g, b)). Цветовые составляющие (красный, зе-
леный и синий) одинаковы и являются строковым представлением зна-
чения переменной nBrightness. Фактически, переменная nBrightness
содержит значение яркости текста в диапазоне от 0 до 255.
280 Часть III. Создание динамических эффектов
При каждом вызове обработчика событий таймера OnTimer значение
nBrightness изменяется на величину, содержащуюся в переменной
nDelta. В процессе инициализации скрипта этой переменной присваива-
ется значение –1. Поэтому на протяжении некоторого периода времени,
значение nBrightness будет уменьшаться (это период "проявления" тек-
ста, когда его цвет изменяется от белого к черному). Однако в обработ-
чике OnTimer значение переменной nDelta будет изменено на 1, когда
значение nBrightness станет равно 0. В следующий период времени
значение nBrightness будет уменьшаться (это период "исчезновения"
текста, когда его цвет изменяется от черного к белому). Когда значение
nBrightness станет равно 255, nDelta снова будет присвоено 1. Таким
образом, процесс "проявления" и "исчезновения" текста будет повто-
ряться периодически.
Текст, движущийся на наблюдателя
Анимация, производимая скриптом из файла examples\13\ex_13_02.htm,
выглядит как движение текстовой надписи с удаленного расстояния в
направлении зрителя. Данный эффект создается простым увеличением
размера шрифта текста в обработчике событий таймера. Этот скрипт
очень похож на предыдущий. Текст, с которым производится анимация,
определяется HTML-разметкой в элементе DIV с идентификатором
ID_ANIMATE:
<DIV ID="ID_ANIMATE">Текст</DIV>
Текущий размер шрифта задается переменной nSize, максимальный
размер шрифта — переменной nMaxSize. В переменной oAnimate сохра-
няется ссылка на DOM-объект элемента DIV, содержащего текст.
Обработчиком событий таймера является функция OnTimer. Увеличение
шрифта текста в ней производится путем изменения значения свойства
fontSize объекта style элемента DIV.
function OnTimer()
{
[Link] = nSize++;
if(nSize > nMaxSize)
nSize = 0;
}
Примеры создания "бегущих строк"
Подобно эффекту мерцающего текста, рассмотренному ранее, эффект
бегущей строки также может использоваться для привлечения внимания
Ãëàâà 13. Создание анимационных эффектов 281
пользователя. Однако бегущие строки обладают еще одной интересной
особенностью. Они позволяют осуществить последовательное отобра-
жение достаточно большого объема информации на весьма малом про-
странстве Web-страницы. Стоит отметить, что эффект бегущей строки
может быть реализован и без применения скриптов, при помощи HTML-
элемента MARQUEE. Однако этот элемент не входит в стандарт HTML.
К тому же, использование скрипта позволяет гибко варьировать пара-
метры отображения бегущей строки и реализовать эффекты, недоступ-
ные при использовании элемента MARQUEE.
Вариант 1
Один из подходов к созданию эффекта бегущей строки был рассмотрен
нами в главе 10 (файл примера examples\10\ex_10_07.htm). Он состоит в
периодическом выводе фрагментов текста фиксированной длины, яв-
ляющихся частями всего текста, предназначенного для отображения в
бегущей строке, в одну и ту же позицию на экране. Скрипт, содержа-
щийся в файле examples\10\ex_13_03.htm, реализует алгоритм, идентич-
ный тому, который был представлен в примере ex_10_07.htm с той лишь
разницей, что текст выводится не в строку состояния, а в текстовый узел
дерева документа. Текстовый узел создается в процессе инициализации
скрипта в элементе DIV, заданном в HTML-разметке с идентификатором
ID_ANIMATE. Поскольку код скрипта крайне прост, а детальное описание
алгоритма его работы вы можете найти в главе 10, здесь я приведу толь-
ко листинг создания текстового узла и обработчика событий таймера,
в котором, собственно, и производится вывод текста.
var oContainer = [Link]("");
[Link]("ID_ANIMATE").appendChild(oContainer);
function OnTimer()
{
[Link] = [Link](nPos,
nPos + nLen);
if(++nPos > ([Link] - nLen))
nPos = 0;
}
Вариант 2
Принцип создания эффекта бегущей строки, используемый в предыду-
щем рассмотренном нами скрипте, безусловно, накладывает существен-
ные ограничения на его функциональность. Так, строка может иметь
только текстовое содержимое. К тому же, сдвиг текста производится
282 Часть III. Создание динамических эффектов
"рывками", с интервалом в один символ. Скрипт, работу которого мы
сейчас обсудим, позволяет использовать в бегущей строке произвольное
гипертекстовое содержимое. Перемещение этого содержимого произво-
дится плавно, с интервалом в один пиксел.
Рассматриваемый нами скрипт находится в файле examples\13
\ex_13_04.htm на прилагаемом к книге компакт-диске. Принцип его ра-
боты состоит в периодическом циклическом сдвиге блока с содержи-
мым бегущей строки, образованного элементом PRE относительно его
контейнера, образованного элементом DIV. Эти элементы, как и само
содержимое бегущей строки, задаются HTML-разметкой. Например:
<DIV ID="ID_ANIMATION"><PRE ID="ID_ANIMATE">Содержимое бегущей
строки</PRE></DIV>
Как видите, элемент-контейнер DIV имеет идентификатор ID_ANIMATION,
а элемент PRE — идентификатор ID_ANIMATE. Для обеспечения правиль-
ного позиционирования блоков друг относительно друга и определения
ширины элемента DIV во внедренной в документ таблице стилей CSS
содержатся соответствующие правила:
#ID_ANIMATION
{
position : relative;
padding : 0px;
width : 256px;
overflow : hidden;
}
#ID_ANIMATE
{
display : inline;
position : relative;
...
}
Заметьте, что значением свойства overflow набора правил, соответст-
вующего селектору #ID_ANIMATION, является hidden. Таким образом,
содержимое бегущей строки, выходящее за границы элемента-
контейнера DIV, будет усекаться. Код скрипта мал по объему и поэтому
приводится здесь полностью.
var oText = [Link]("ID_ANIMATE");
var oContainer = [Link]("ID_ANIMATION");
Ãëàâà 13. Создание анимационных эффектов 283
var nWidth = [Link];
var nPos = [Link];
function OnTimer()
{
[Link] = [Link]() + "px";
if(--nPos < -nWidth)
nPos = [Link];
}
OnTimer();
[Link](OnTimer, 20);
Как вы можете видеть, в процессе инициализации скрипта переменным
oContainer и oText присваиваются ссылки на DOM-объекты описанных
ранее элементов DIV и PRE. Затем в переменной nWidth сохраняется ши-
рина (в пикселах) элемента PRE, а переменной nPos присваивается зна-
чение, равное ширине элемента-контейнера DIV. Для определения ши-
рины элемента используется свойство offsetWidth, не специфицируе-
мое в DOM. Однако оно поддерживается большинством популярных
браузеров. Переменная nPos определяет текущее смешение строкового
блока элемента PRE относительно структурного блока элемента DIV. За-
тем производится вызов функции OnTimer и запускается таймер, обра-
ботчиком событий которого также является OnTimer.
В функции OnTimer производится изменение горизонтального смещения
блока элемента PRE относительно его контейнера путем установки зна-
чения свойству left встроенной информации о стиле. Значение, заноси-
мое в left, определяется величиной, содержащейся в nPos. При каждом
вызове OnTimer значение nPos уменьшается на 1 (соответственно, на
один пиксел уменьшается смещение блока элемента PRE относительно
контейнера). При достижении значением смещения величины, равной
отрицательному значению ширины содержимого бегущей строки (в этот
момент вся бегущая строка скрывается за левой границей области со-
держимого элемента-контейнера), это значение (переменная nPos) снова
устанавливается равным ширине контейнера (при таком смещении бе-
гущая строка скрыта за правой границей области содержимого контей-
нера). Таким образом и осуществляется периодический циклический
сдвиг содержимого бегущей строки.
В файле examples\13\ex_13_05.htm на компакт-диске находится
скрипт, использующий только что описанный алгоритм для прокрутки
284 Часть III. Создание динамических эффектов
произвольного гипертекстового содержимого в вертикальном направ-
лении. Он может быть использован, например, для показа серии объ-
явлений на страницах сайта.
Волнообразно движущийся текст
Эффект 1
Скрипт, который мы рассмотрим сейчас, создает эффект анимации тек-
стовой надписи, выглядящий как ее волнообразное движение. Принцип
создания данного эффекта состоит в периодическом изменении размера
шрифта каждого символа надписи. Размер шрифта символов является
функцией синуса от текущего времени и позиции символа в строке.
Чтобы было понятно, о чем идет речь, на рис. 13.1 приведено изображе-
ние работы скрипта в некоторый момент времени.
Рис. 13.1. Работа скрипта, создающего волнообразно движущийся текст (эффект 1)
Скрипт находится в файле examples\13\ex_13_06.htm. Принцип его рабо-
ты основан на создании множества элементов SPAN, содержащих по од-
ному символу надписи, анимация которой будет выполняться. Имея
ссылки на DOM-объекты этих элементов, мы сможем производить из-
менение стилевой информации каждого из них, тем самым задавая па-
раметры представления отдельных символов.
В начале скрипта инициализируются несколько переменных.
var nPeriod = 2; // количество периодов синусоиды
var nMaxSize = 64; // максимальный размер шрифта
var nMinSize = 12; // минимальный размер шрифта
var strText = "Волнообразно движущийся текст";
var fPhase = 0.0;
var aoChars = new Array();
var nSteps = [Link];
var fStep = 2 * [Link] * nPeriod / nSteps;
Ãëàâà 13. Создание анимационных эффектов 285
var nDy = (nMaxSize + nMinSize) / 2;
var nAmp = (nMaxSize - nMinSize) / 2;
Переменная nPeriod задает количество периодов "синусовидных" ис-
кривлений, которым будет подвергнута надпись. Переменные nMinSize
и nMaxSize определяют минимальный и максимальный размеры шрифта
символов. Их разность, соответственно, определяет амплитуду измене-
ний размера шрифта. Переменной nAmp присваивается значение полови-
ны этой амплитуды. Переменная nDy инициализируется значением сред-
него размера шрифта. Текст надписи задается переменной strText.
В переменной nSteps сохраняется количество символов надписи. В пе-
ременную fStep заносится значение приращения угла для каждого сим-
вола, исходя из количества периодов синусоиды и количества символов.
Движение "волн" по тексту будет обеспечиваться путем изменения фазы
синусоиды. Ее значение содержится в переменной fPhase, она инициа-
лизируется величиной 0.0. Ссылки на DOM-объекты элементов SPAN,
содержащих символы надписи, будут храниться в массиве aoChars, соз-
даваемом здесь же.
Элементы SPAN для каждого символа добавляются в элемент SPAN с ат-
рибутом ID, установленным в значение ID_ANIMATE, создаваемый при
помощи HTML-разметки:
<SPAN ID="ID_ANIMATE"></SPAN>
Код добавления элементов SPAN выполняется при инициализации скрип-
та. Он выглядит следующим образом:
for(var i = 0; i < nSteps; i++)
{
var oSpan = [Link]("SPAN");
[Link]([Link](
[Link](i, i + 1)));
[Link]("ID_ANIMATE").appendChild(oSpan);
aoChars[i] = oSpan;
}
Изменение размеров шрифтов символов надписи производится в обра-
ботчике событий таймера OnTimer.
function OnTimer()
{
for(var i = 0; i < nSteps; i++)
{
var nSize = [Link]([Link](fStep * i + fPhase) *
nAmp + nDy);
286 Часть III. Создание динамических эффектов
aoChars[i].[Link] = [Link](10) + "px";
}
fPhase += fStep;
}
Здесь в цикле перебираются все DOM-объекты созданных элементов
SPAN, содержащих символы надписи, для каждого из них производится
вычисление и установка размера шрифта путем изменения свойства
fontSize объекта style (соответственно, изменяется CSS-свойство
font-size встроенной стилевой информации). Затем увеличивается фа-
зовый сдвиг синусоиды (значение переменной fPhase) на величину при-
ращения угла в расчете на один символ (значение переменной fStep).
Эффект 2
Следующий скрипт, который мы рассмотрим, также создает эффект
волнообразного движения текста. Однако в данном случае будет произ-
водиться именно перемещение отдельных символов надписи. Символы
будут двигаться вертикально, располагаясь по синусоиде. Рассматри-
ваемый скрипт находится в файле examples\13\ex_13_07.htm. На
рис. 13.2 приведена иллюстрация его работы.
Рис. 13.2. Работа скрипта, создающего волнообразно движущийся текст (эффект 2)
Алгоритм работы этого скрипта очень похож на алгоритм работы пре-
дыдущего, поэтому здесь будут описаны только существенные отличия.
Итак, в начале скрипта также инициализируются переменные, часть из
которых задает параметры движения текста (траекторию движения, фак-
тически — параметры синусоиды). Переменные nPeriod, strText,
fPhase, nSteps, fStep, aoChars имеют то же назначение, что и в преды-
дущем примере, и им присваиваются те же значения. Роль переменной
nAmp теперь играет переменная nAmplitude2. Ее значение задается кон-
стантой. Переменная nKPhase определяет дополнительный коэффициент
Ãëàâà 13. Создание анимационных эффектов 287
фазового смещения на каждой итерации движения текста. Чем больше
ее значение, тем более плавно движется текст.
var nAmplitude2 = 64; // половина амплитуды синусоиды
var nKPhase = 16; // коэффициент фазового сдвига
// на каждой итерации
Генерируемые для каждого символа элементы SPAN теперь добавляются
в элемент DIV, с идентификатором ID_ANIMATE, создаваемым при помо-
щи HTML-разметки:
<DIV ID="ID_ANIMATE"></DIV>
При инициализации скрипта на DOM-объект данного элемента получа-
ется ссылка:
var oAnimate = [Link]("ID_ANIMATE");
Для того чтобы позиционировать изображение анимации по центру
страницы, этот элемент внедрен в ячейку таблицы (см. код HTML-
разметки в файле ex_13_07.htm) с соответствующими значениями атри-
бутов выравнивания по горизонтали и вертикали. Для точного позицио-
нирования высота данного элемента устанавливается в один пиксел пу-
тем определения соответствующего правила во внедренной в документ
таблице стилей.
#ID_ANIMATE
{
height : 1px;
...
Там же его дочерним элементам SPAN назначается относительная схема
позиционирования.
#ID_ANIMATE SPAN
{
position : relative;
}
Если вы захотите расположить анимацию подобного рода в произволь-
ном месте Web-страницы, воспользовавшись данным скриптом, то мо-
жете просто задать необходимую ширину и высоту таблицы, в которой
содержится описанный ранее элемент DIV, и поместить эту таблицу в
нужное место HTML-разметки вашего документа.
Код создания элементов SPAN, содержащих отдельные символы надписи,
аналогичен коду из предыдущего скрипта с тем лишь отличием, что соз-
288 Часть III. Создание динамических эффектов
даваемым элементам устанавливается значение позиционного уровня,
равное 1, путем изменения свойства zIndex их объекта style. Данное
действие необходимо для правильного отображения анимации в браузе-
ре Opera (без этого верхняя часть изображения усекается).
...
[Link] = 1;
...
Перемещение элементов, содержащих символы, также производится в
обработчике событий таймера OnTimer. В OnTimer в цикле перебираются
DOM-объекты созданных SPAN, и для каждого из них вычисляется
и устанавливается смещение по вертикали относительно элемента-
контейнера DIV путем изменения свойства top объекта style, а затем
производится изменение переменной fPhase, содержащей значение фа-
зового сдвига синусоиды.
Эффект 3
Скрипт, который мы рассмотрим сейчас, так же, как и два предыдущих
скрипта, создает эффект волнообразного движения текста. Однако его
работа делится на два этапа. На первом этапе производится движение
только первого символа надписи влево с замедлением, а затем вправо с
ускорением. Это движение создает эффект удара первым символом по
оставшейся строке. На втором этапе начинается волнообразное движе-
ние текста. Это выглядит как движение одиночной волны по текстовой
надписи слева направо. На рис. 13.3 приведена иллюстрация второго
этапа работы скрипта.
Рис. 13.3. Второй этап работы скрипта
Cкрипт находится в файле examples\13\ex_13_08.htm. При инициализа-
ции в нем также производится создание элементов SPAN для каждого
символа надписи, задаваемой переменной strText, и добавление их в
Ãëàâà 13. Создание анимационных эффектов 289
документ в элемент SPAN с идентификатором ID_ANIMATE, а также добав-
ление ссылок на созданные элементы в массив aoChars.
Первая фаза работы скрипта (движение первого символа) инициализи-
руется функцией InitPhase_1.
function InitPhase_1()
{
fX = 0.1;
fdX = -9;
nTimerID = setInterval(OnTimer_1, 20);
}
Используемые в ней переменные определяются в начале скрипта. Пере-
менная fX содержит текущее значение сдвига первого символа по гори-
зонтали от позиции его нормального размещения. Переменная fdX зада-
ет значение текущего приращения значения переменной fX на каждой
итерации движения первого символа надписи. Функция InitPhase_1
запускает таймер, обработчиком событий которого является функция
OnTimer_1.
function OnTimer_1()
{
fX += fdX;
if(fX > 0)
{
fX = 0;
clearInterval(nTimerID);
InitPhase_2();
}
else
fdX += 0.75;
aoChars[0].[Link] = [Link](fX).toString() + "px";
}
В ней производится установка позиции первого символа путем измене-
ния свойства left объекта style соответствующего элемента SPAN (по-
зиция задается на основе значения переменной fX), а также изменение
переменных fX и fdX. Поскольку при каждом вызове OnTimer_1 значение
fX изменяется на величину fdX, а значение fdX увеличивается, скорость
движения первого символа влево уменьшается, а затем символ начинает
двигаться в обратном направлении с увеличивающейся скоростью. При
290 Часть III. Создание динамических эффектов
достижении первым символом надписи его первоначальной позиции
(fX > 0) с помощью вызова функции InitPhase_2 инициализируется
вторая фаза движения текста.
function InitPhase_2()
{
fPhase = 0;
nTimerID = setInterval(OnTimer_2, 20);
}
Как видите, функция InitPhase_2 просто задает значение переменной
fPhase (ее назначение — то же самое, что и в предыдущем примере) и
запускает таймер, обработчиком событий которого является функция
OnTimer_2. Код OnTimer_2 очень похож на код функции OnTimer_1 пре-
дыдущего скрипта. Он отличается тем, что изменение вертикальной по-
зиции производится для группы символов. Это необходимо для того,
чтобы по тексту двигалась только одна "волна". Движение волны дости-
гается наращиванием значения фазового сдвига синусоиды (значения
переменной fPhase). При достижении "волной" конца текстовой надпи-
си таймер останавливается, а затем запускается таймер с единичным
срабатыванием, обработчиком которого является функция InitPhase_1.
Это обеспечивает цикличность процесса движения текста.
...
if(fPhase > 2 * pi * nPeriod)
{
clearInterval(nTimerID);
setTimeout(InitPhase_1, 2000);
}
Текст, прилетающий по частям
Пример скрипта, который находится в файле examples\13\ex_13_09.htm
на компакт-диске, создает анимацию, выглядящую как одновременное
движение символов, образующих текстовую надпись, к их позициям в
строке. Начальное размещение символов — случайно.
Как и в нескольких предыдущих примерах, в данном скрипте создаются
элементы SPAN, содержащие по одному символу надписи, определяемой
переменной strText. Добавление этих элементов в документ произво-
дится в элемент DIV с идентификатором ID_ANIMATE, при этом ссылки на
них сохраняются в массиве aoChars. Текущие позиции элементов SPAN, а
также величины приращений этих позиций на каждом шаге движения
Ãëàâà 13. Создание анимационных эффектов 291
текста сохраняются в виде свойств m_fX, m_fY (позиция элемента по осям
x и y соответственно), m_fDX и m_fDY (приращения переменных m_fX и
m_fY соответственно) их DOM-объектов. Свойства m_fX и m_fY инициа-
лизируются случайными значениями в диапазоне [-nDevX, nDevX] и
[-nDevY, nDevY], а значения свойств m_fDX и m_fDY рассчитываются на их
основе непосредственно после создания каждого элемента SPAN.
var nDevX = 300;
var nDevY = 300;
...
for(var i = 0; i < nCount; i++)
{
...
aoChars[i].m_fX = [Link]() * 2 * nDevX - nDevX;
aoChars[i].m_fY = [Link]() * 2 * nDevY - nDevY;
aoChars[i].m_fDX = -aoChars[i].m_fX / nDevX;
aoChars[i].m_fDY = -aoChars[i].m_fY / nDevY;
UpdateSpanPos(i);
}
Как видно из листинга, после создания каждого элемента SPAN вызыва-
ется функция UpdateSpanPos. Она служит для изменения позиции эле-
мента в соответствии со значениями его свойств m_fX и m_fY. Код этой
функции приведен далее.
function UpdateSpanPos(nNumber)
{
aoChars[nNumber].[Link] =
[Link](aoChars[nNumber].m_fX).toString(10) + "px";
aoChars[nNumber].[Link] =
[Link](aoChars[nNumber].m_fY).toString(10) + "px";
}
Как и прежде, основной алгоритм создания анимации содержится в об-
работчике событий таймера OnTimer.
function OnTimer()
{
var bStop = true;
for(var i = 0; i < nCount; i++)
{
aoChars[i].m_fX += aoChars[i].m_fDX;
aoChars[i].m_fY += aoChars[i].m_fDY;
292 Часть III. Создание динамических эффектов
if(([Link](aoChars[i].m_fX) < 1) &&
([Link](aoChars[i].m_fY) < 1))
{
aoChars[i].m_fX = 0;
aoChars[i].m_fY = 0;
aoChars[i].m_fDX = 0;
aoChars[i].m_fDY = 0;
}
else
bStop = false;
UpdateSpanPos(i);
}
if(bStop)
[Link](nTimerID);
}
В функции OnTimer в цикле производится последовательный перебор
всех DOM-объектов элементов SPAN, содержащих символы надписи. Для
каждого объекта обновляются значения его свойств m_fX и m_fY и вызы-
вается функция UpdateSpanPos для изменения позиции элемента. При
этом текущая позиция элемента контролируется. Если значения m_fX и
m_fY меньше 1 (это означает, что элемент достиг своей позиции в стро-
ке), значения свойств m_fX, m_fY, m_fDX, m_fDY устанавливаются в 0 (эле-
мент перестает двигаться). Если данное условие выполняется для всех
элементов (все элементы достигли своих позиций), то значение пере-
менной bStop после выхода из цикла остается равным true. В этом слу-
чае останавливается таймер, и движение текста прекращается.
Имитация движения текста по кругу
в 3D-пространстве
В файле examples\13\ex_13_10.htm находится скрипт, реализующий
анимационный эффект, выглядящий как перспективное трехмерное изо-
бражение надписи, символы которой движутся по окружности. Эффект
выглядит весьма интересно. Его можно использовать, например, в каче-
стве анимационного логотипа сайта.
Работа скрипта по-прежнему основана на создании и изменении пара-
метров представления группы элементов SPAN, содержащих по одному
символу надписи (надпись, как и в предыдущих скриптах, задается
переменной strText). Код создания этих элементов остался прежним.
Ãëàâà 13. Создание анимационных эффектов 293
Основной же интерес представляет алгоритм изменения их параметров,
реализованный в обработчике событий таймера OnTimer.
function OnTimer()
{
var dfAlpha = 360 / nCount;
for(var i = 0; i < nCount; i++)
{
var oSpan = aoChars[i];
var nTop = nYPos + nHeight * [Link](
fAlpha - i * dfAlpha * [Link] / 180);
[Link] = nTop;
[Link] = nXPos + nWidth * [Link](
fAlpha - i * dfAlpha * [Link] / 180);
[Link] =
((nTop - nYPos + nHeight) / (2 * nHeight)) *
(nFontSizeMax - nFontSizeMin) + nFontSizeMin;
}
fAlpha += 0.02;
}
Функция OnTimer производит размещение сгенерированных элементов
SPAN вдоль эллиптической траектории, а также изменяет размер шрифта
содержащегося в них текста. Значения свойств top и left (координаты
левого верхнего угла блока) элементов SPAN рассчитываются исходя из
параметрического уравнения эллипса. Переменные nXPos, nYPos, nWidth,
nHeight, определяемые при инициализации скрипта, задают, соответст-
венно, координату по оси x, координату по оси y, длину горизонтальной
и длину вертикальной полуосей эллипса. Переменная fAlpha определяет
начальный угол построения эллипса. Вращение надписи происходит
вследствие постоянного наращивания ее значения. Перспективность
изображения достигается путем изменения размера шрифта символов в
соответствии со значением их мнимого удаления от пользователя (оно
вычисляется на основе вертикальной позиции блока, содержащего сим-
вол). Минимальное и максимальное значения величины шрифта задают-
ся переменными nFontSizeMin и nFontSizeMax, также определяемыми
при инициализации скрипта.
294 Часть III. Создание динамических эффектов
Движение фонового рисунка страницы
Скрипт, находящийся в файле examples\13\ex_13_11.htm, является хо-
рошей иллюстрацией того, как, благодаря простоте управления пред-
ставлением документа при помощи методов объектной модели, можно с
минимальными усилиями создавать достаточно красочные анимацион-
ные эффекты. Данный скрипт осуществляет периодический сдвиг фоно-
вого изображения страницы на один пиксел вправо. При надлежащем
подборе фонового изображения это смотрится весьма эффектно.
Фоновое изображение документа задается правилом таблицы стилей:
BODY
{
background-image : url("img/11_bg.jpg");
}
Сдвиг изображения фона производится с помощью изменения CSS-свой-
ства background-position элемента BODY путем модификации значения
свойства backgroundPosition объекта style объекта [Link]. Код
скрипта состоит всего из двух строк:
var nPos = 0;
[Link]("[Link] =\
(nPos++).toString() + 'px 0px';", 20);
Летящие звезды
Далее мы рассмотрим скрипт, создающий анимационный эффект, вызы-
вающий ассоциации с движением звезд в космическом пространстве.
Этот эффект выглядит весьма интересно и может быть использован для
увеличения привлекательности страниц сайта с малым количеством ин-
формационного содержимого (например, на страницах, содержащих
только ссылки на альтернативные разделы сайта на разных языках,
страницах автоматического перенаправления пользователя на другие
страницы и т. д.).
Скрипт находится в файле examples\13\ex_13_12.htm на компакт-диске.
Принцип его работы основан на создании и периодическом перемеще-
нии в горизонтальном направлении множества небольших элементов
DIV, имеющих различный цвет фона. Величины смещений различных
элементов варьируются в некоторых пределах, вследствие чего создает-
ся иллюзия перспективности изображения ("звезды", движущиеся мед-
леннее, воспринимаются как более удаленные).
Ãëàâà 13. Создание анимационных эффектов 295
Во избежание появления видимых областей документа, не содержащих
движущихся объектов-звезд при увеличении размера окна браузера
пользователем, размер области анимации изначально выбирается равной
разрешающей способности дисплея (в этом случае даже при переводе
браузера в полноэкранный режим просмотра пользователь не увидит
пустых фрагментов страницы). Для того чтобы избежать появления по-
лос прокрутки в окне браузера, во внедренной в документ ex_13_12.htm
таблице стилей, определены следующие правила:
HTML, BODY
{
...
width : 100%;
height : 100%;
overflow : hidden;
}
Теперь рассмотрим непосредственно алгоритм работы скрипта. В про-
цессе его инициализации определяется несколько переменных. Часть из
них задает параметры анимации. Изменение их значений позволит вам
настроить скрипт по собственному желанию.
var nStarCount = 128; // Количество звезд
var nMinSpeed = 1; // Минимальная скорость
var nMaxSpeed = 5; // Максимальная скорость
var nMinSize = 1; // Минимальный размер "звезд"
var nMaxSize = 3; // Максимальный размер "звезд"
Код инициализации остальных переменных приведен в следующем лис-
тинге.
var aStars = new Array(nStarCount);
var nWidth = [Link];
var nHeight = [Link];
Массив aStars предназначен для хранения ссылок на DOM-объекты
создаваемых элементов DIV. А в переменные nWidth и nHeight заносятся
значения разрешения дисплея по осям x и y соответственно.
Генерирование элементов DIV, представляющих "звезды", производится
функцией GenerateStars, также вызываемой в процессе инициализации
скрипта. Ее код достаточно прост (см. файл примера ex_13_12.htm), по-
этому здесь не приводится. Функция создает элементы DIV в количестве,
равном nStarCount, при этом для каждого из них устанавливается схема
абсолютного позиционирования и значение стилистического свойства
296 Часть III. Создание динамических эффектов
overflow, равное hidden. Ссылки на созданные элементы сохраняются в
массиве aStars, потом производится добавление элементов в дерево
документа и вызов функции ResetStar.
Функция ResetStar служит для установки параметров (координат, раз-
мера, цвета фона, скорости движения) элемента DIV, ссылка на который
содержится в элементе массива aStars с индексом, равным значению ее
первого параметра. Листинг функции ResetStar приведен далее.
function ResetStar(i, x)
{
var oDiv = aStars[i];
if(x < 0)
x = [Link]([Link]() * nWidth);
oDiv.x = x;
oDiv.y = [Link]([Link]() * nHeight);
[Link] = [Link]([Link]() *
(nMaxSpeed - nMinSpeed)) + nMinSpeed;
[Link] =
RGB_Str([Link]([Link]() * 0xFF),
[Link]([Link]() * 0xFF),
[Link]([Link]() * 0xFF));
var nSize = [Link]([Link]() *
(nMaxSize - nMinSize)) + nMinSize;
[Link] = [Link](10) + "px";
[Link] = [Link];
UpdateStarPos(i);
}
Из листинга видно, что значения координат и скорости движения эле-
мента сохраняются в его свойствах x, y и nSpeed. Все параметры эле-
мента вычисляются на основе случайных величин, генерируемых мето-
дом [Link]. Значение координаты элемента по оси x, однако, мо-
жет быть передано вторым параметром функции (значение параметра
используется, если оно неотрицательно). Для вычисления значения
свойства backgroundColor (цвета фона) объекта style элемента приме-
няется функция RGB_Str (ее код см. в файле ex_13_12.htm). Она возвра-
Ãëàâà 13. Создание анимационных эффектов 297
щает строку в формате #rrggbb в соответствии со значениями передан-
ных ей параметров — красной, зеленой и синей цветовых составляю-
щих. Для позиционирования элементов в соответствии с заданными ко-
ординатами используется функция UpdateStarPos.
function UpdateStarPos(i)
{
var oDiv = aStars[i];
[Link] = [Link]() + "px";
[Link] = [Link]() + "px";
}
Она просто изменяет значения свойств top и left объекта style DOM-
объекта элемента, индекс которого передается ей в качестве параметра.
Перемещение всех "звезд" осуществляется в функции MoveStars,
являющейся обработчиком событий таймера.
function MoveStars()
{
for(var i = 0; i < nStarCount; i++)
{
aStars[i].x += aStars[i].nSpeed;
if(aStars[i].x > nWidth)
ResetStar(i, 0);
else
UpdateStarPos(i);
}
}
В ней производится перебор всех объектов, ссылки на которые хранятся
в массиве aStars и увеличение значения их свойства x на величину
nSpeed. Затем производится контроль полученного значения и, в случае
превышения им значения nWidth (ширины области анимации), произво-
дится вызов функции ResetStar для установки новых параметров эле-
мента (при этом его горизонтальная позиция устанавливается в 0).
В противном случае вызывается UpdateStarPos, что приводит к пере-
мещению элемента на новую позицию.
В файле examples\13\ex_13_13.htm находится несколько измененный
вариант только что рассмотренного скрипта. Его отличия состоят в
298 Часть III. Создание динамических эффектов
том, что область анимации ограничена элементом DIV. Такой элемент
можно разместить в произвольном месте Web-страницы, а также за-
дать ему необходимые размеры с помощью стилистических правил.
Эффект фейерверка
Скрипт, находящийся в файле examples\13\ex_13_14.htm, создает эффект
анимации, имитирующий взрыв фейерверка. Принцип его работы, как и
в предыдущем случае, основан на перемещении множества небольших
элементов DIV (их ширина и высота равна одному пикселу). Перемеще-
ние происходит в пределах элемента-контейнера (элемент DIV с атрибу-
том ID, установленным в значение ID_ANIMATE).
Алгоритм работы скрипта достаточно прост. В начале фазы анимации
случайным образом выбираются координаты точки "взрыва" в пределах
элемента-контейнера. Все элементы DIV позиционируются в данную
точку. Для каждого из них выбираются случайные значения величины и
направления вектора скорости (скорость элемента определяет величины
его смещений осям x и y от предыдущего местоположения на каждой
итерации движения), а также время жизни (количество перемещений
элемента, после которых он перестанет отображаться). Затем запускает-
ся таймер, в обработчике которого производится изменение позиций,
уменьшение величин векторов скорости и значений времени жизни эле-
ментов, а также изменение случайным образом цвета их фона. Элемен-
ты, время жизни которых стало равным 0, скрываются (цвет их фона
устанавливается равным цвету фона элемента-контейнера) и не переме-
щаются в дальнейшем. Когда время жизни всех элементов становится
равным 0, фаза анимации заканчивается. При этом запускается таймер с
единичным срабатыванием, событие которого инициализирует начало
фазы анимации. И через некоторое время процесс повторяется снова.
Теперь давайте рассмотрим аспекты реализации скрипта. Его код начи-
нается с определения переменных. Некоторые из них задают параметры
воспроизводимого эффекта. Назначение этих переменных понятно из
комментариев к ним.
var nStarCount = 256; // Количество звезд фейерверка
var nMinSpeed = 2; // Минимальная скорость звезд
var nMaxSpeed = 4; // Максимальная скорость звезд
var nMinLive = 32; // Минимальное время жизни элемента
var nMaxLive = 42; // Максимальное время жизни элемента
var fDSpeed = 0.1; // Уменьшение скорости элементов
Ãëàâà 13. Создание анимационных эффектов 299
Переменные nMinX, nMaxX, nMinY и nMaxY задают границы области, в пре-
делах которой могут быть выбраны координаты точки "взрыва". Пара-
метры этой области выбираются с таким расчетом, чтобы основная
часть анимации всегда была видна пользователю.
var nMinX = [Link](nWidth / 4);
var nMaxX = [Link](nWidth / 4 * 3);
var nMinY = [Link](nHeight / 4);
var nMaxY = [Link](nHeight / 4 * 3);
Далее, в переменной oAnimate сохраняется ссылка на DOM-объект эле-
мента-контейнера DIV с идентификатором ID_ANIMATE, в переменные
nWidth и nHeight заносятся значения его ширины и высоты. Для хране-
ния ссылок на DOM-объекты перемещаемых элементов DIV определяет-
ся массив aStars. Переменная nTimerID будет содержать идентификатор
таймера, в обработчике которого будут выполняться основные действия
по анимации.
Генерирование перемещаемых элементов DIV, служащих в качестве
"звезд" фейерверка, добавление их в массив aStars и в дерево докумен-
та осуществляется функцией GenerateStars (см. код примера). Начало
процесса анимации очередного фейерверка выполняет функция
StartFireShow. Функции GenerateStars и StartFireShow последова-
тельно вызываются на последнем этапе инициализации скрипта. Далее
приведен листинг функции StartFireShow.
function StartFireShow()
{
var x = [Link]([Link]() * (nMaxX - nMinX)) + nMinX;
var y = [Link]([Link]() * (nMaxY - nMinY)) + nMinY;
for(var i = 0; i < nStarCount; i++)
ResetStar(i, x, y);
nTimerID = [Link](MoveStars, 40);
}
Как видите, функция выбирает координаты точки "взрыва" (переменные
x и y), производит "настройку параметров" каждого перемещаемого
элемента DIV, вызывая функцию ResetStar, и запускает таймер, обра-
ботчиком которого является функция MoveStars. Функция ResetStar
(подробнее см. код примера) получает ссылку на DOM-объект элемента
DIV из массива aStars, вычисляет и сохраняет параметры движения эле-
300 Часть III. Создание динамических эффектов
мента в виде значений его свойств. Текущие координаты элемента со-
храняются в свойствах x и y, время жизни — в свойстве nLive. Вектор
скорости определяется его модулем (свойство fSpeed) и значениями
синуса и косинуса угла поворота в системе координат окна браузера
(свойства fSin и fCos). Вот код вычисления значений этих свойств:
[Link] = [Link]() * (nMaxSpeed - nMinSpeed) +
nMinSpeed;
[Link] = [Link]() * 2 - 1;
[Link] = [Link]() * 2 - 1;
Функция ResetStar также вызывает функции UpdateStarPos и
UpdateStarBgColor, которые осуществляют позиционирование элемента
DIV в соответствии со значениями его координат, содержащихся в свой-
ствах x и y, и установку элементу случайного цвета фона.
В функции MoveStars, являющейся обработчиком событий таймера, вы-
полняется обновление параметров движения элементов-звезд (измене-
ние значений свойств x и y, уменьшение величин векторов скорости,
уменьшение значений времени жизни), перемещение элементов на но-
вые позиции (при помощи вызова UpdateStarPos), изменение цвета их
фона (при помощи вызова UpdateStarBgColor), а также скрытие элемен-
тов, чье время жизни стало равным 0 (их цвет фона устанавливается в
черный — как и у элемента-контейнера). Здесь же производится кон-
троль завершения анимации (момент, когда время жизни всех элементов
становится равным 0) и, при необходимости, запуск таймера для начала
анимации следующего фейерверка. Далее приведен листинг функции
MoveStars.
function MoveStars()
{
var bEndShow = true;
for(var i = 0; i < nStarCount; i++)
{
var oDiv = aStars[i];
if([Link])
{
oDiv.x += [Link] * [Link];
oDiv.y += [Link] * [Link];
[Link] -= fDSpeed;
Ãëàâà 13. Создание анимационных эффектов 301
if([Link] < 0)
[Link] = 0;
UpdateStarPos(i);
UpdateStarBgColor(i);
[Link]--;
if(![Link])
[Link] = "black";
bEndShow = false;
}
}
if(bEndShow)
{
[Link](nTimerID);
[Link](StartFireShow, 1500);
}
}
Падающий снег
Следующий скрипт создает анимацию в виде падающего снега. Это вы-
глядит весьма привлекательно, и скрипт может использоваться, напри-
мер, для размещения на сайте блоков новогодних объявлений.
Скрипт находится в файле examples\13\ex_13_15.htm. Алгоритм его ра-
боты очень похож на алгоритм работы скрипта, создающего анимацию в
виде летящих звезд. Отличия заключаются в том, что вместо элементов
DIV скрипт производит перемещение изображений в форме снежинок.
Движение изображений производится в вертикальном направлении
сверху вниз с небольшими случайными перемещениями по горизонтали.
Как обычно, код скрипта начинается с определения переменных. Пере-
менная oAnimate инициализируется ссылкой на DOM-объект элемента
DIV с идентификатором ID_ANIMATE (контейнер для движущихся изобра-
жений). Переменные nWidth и nHeight принимают значения ширины и
высоты этого элемента. Массив aoFlakes определяется для хранения
ссылок на DOM-объекты элементов изображений снежинок. Перемен-
ная nFSize должна содержать значение высоты (в пикселах),
strFlakeURL — URL ресурса, а nCount — общее количество этих изо-
бражений.
302 Часть III. Создание динамических эффектов
Создание элементов изображений, добавление их в дерево документа, а
ссылок на них в массив aoFlakes производится при инициализации
скрипта аналогично тому, как это делалось во многих предыдущих при-
мерах для элементов DIV. При этом свойству src объектов изображений
присваивается значение переменной strFlakeURL, и вызываются функ-
ции ResetFlake для установки параметров движения каждой снежинки и
UpdateFlakePos (см. код примера) для позиционирования соответст-
вующего элемента изображения относительно элемента-контейнера.
Прототип функции ResetFlake выглядит следующим образом:
function ResetFlake(nNum, bRandY)
Функция устанавливает значения свойств m_nX, m_nY и m_nSpeed (коор-
дината по оси x, координата по оси y и скорость, соответственно) DOM-
объекта изображения, ссылка на который находится в элементе массива
aoFlakes с индексом nNum. Значения свойств m_nX и m_nSpeed всегда слу-
чайны. Свойству m_nY случайное значение устанавливается, если пара-
метр bRandY функции вычисляется в true. В противном случае, свойству
присваивается величина -nFSize (изображение должно позициониро-
ваться за верхней границей элемента-контейнера). При создании эле-
ментов изображений ResetFlake вызывается со значением второго па-
раметра true.
Перемещение изображений снежинок производится в обработчике со-
бытий таймера OnTimer. Далее приведен листинг этой функции.
function OnTimer()
{
for(var i = 0; i < nCount; i++)
{
var oImg = aoFlakes[i];
oImg.m_nX += [Link]([Link]() * 3 - 2);
oImg.m_nY += oImg.m_nSpeed;
if(oImg.m_nY > nHeight)
ResetFlake(i, false);
UpdateFlakePos(i);
}
}
Как вы можете видеть, в OnTimer просто производится изменение
свойств m_nX и m_nY объектов изображений, их позиционирование при
Ãëàâà 13. Создание анимационных эффектов 303
помощи UpdateFlakePos. Значение m_nY наращивается на величину ско-
рости движения (m_nSpeed), а к m_nX прибавляется случайное значение
из диапазона [–1, 1] (этим достигается хаотичное колебание снежинок).
В случае, если изображение пересекает нижнюю границу элемента-
контейнера (выполняется условие oImg.m_nY > nHeight), вызывается
функция ResetFlake для перемещения этого изображения за пределы
верхней границы и установки ему новой горизонтальной позиции и ско-
рости движения.
Часы со стрелками
Довольно часто на сайтах начинающих Web-мастеров и домашних стра-
ницах можно встретить такой декоративный элемент, как часы — не-
большой блок, в котором отображается текущее время. В большинстве
случаев, время отображается в цифровом виде (пример реализации та-
ких часов уже приводился в главе 6, соответствующий скрипт находится
в файле examples\06\ex_6_04.htm на компакт-диске). Однако гораздо
более интересно может выглядеть отображение времени в аналоговом
представлении, т. е. в виде часов со стрелками. В файле examples\13
\ex_13_16.htm находится скрипт, реализующий подобный эффект. Да-
вайте рассмотрим его работу.
Основная задача, решаемая данным скриптом, состоит в "рисовании"
стрелок аналоговых часов (они отображаются в виде линий). Поскольку,
непосредственное рисование в окне браузера при помощи JavaScript
невозможно, скрипт отображает линии при помощи расположения соот-
ветствующим образом множества элементов DIV с черным цветом фона
и размерами 1 1 пиксел. Позиционирование этих элементов произво-
дится в пределах их элемента-контейнера DIV с идентификатором
ID_ANIMATE. При инициализации скрипта определяются переменные:
oAnimate — ссылка на DOM-объект элемента-контейнера, nWidth,
nHeight — ширина и высота этого элемента, nHourLen, nMinLen,
nSecLen — длины, соответственно, часовой, минутной и секундной
стрелок часов, массивы aDivHour, aDivMin, aDivSec для хранения ссылок
на DOM-объекты элементов DIV, при помощи которых будут отобра-
жаться, соответственно, часовая, минутная и секундная стрелки.
Создание элементов DIV для отображения стрелок производится функ-
цией CreateDivs (см. код примера). В нее, в качестве параметров, пере-
даются ссылка на массив, в который заносятся ссылки на DOM-объекты
создаваемых элементов, и количество элементов, которое необходимо
304 Часть III. Создание динамических эффектов
создать. При инициализации скрипта эта функция вызывается трижды
(ей передаются ссылки на массивы aDivHour, aDivMin, aDivSec и вели-
чины длин соответствующих стрелок — nHourLen, nMinLen и nSecLen).
Затем производится запуск таймера с интервалом в одну секунду, обра-
ботчиком событий которого является функция OnTimer.
function OnTimer()
{
var oDate = new Date();
DrawArrow(aDivHour, GetArrowDim([Link](),
nHourLen, 12));
DrawArrow(aDivMin, GetArrowDim([Link](),
nMinLen, 60));
DrawArrow(aDivSec, GetArrowDim([Link](),
nSecLen, 60));
}
Функция OnTimer получает текущее время путем создания объекта типа
Date, а затем вызывает функцию DrawArrow для "рисования" часовой,
минутной и секундной стрелок, передавая ей ссылки на массивы
aDivHour, aDivMin или aDivSec и результат, возвращаемый функцией
GetArrowDim.
function GetArrowDim(val, r, d)
{
var fAngle = [Link] / 2 - val * 2 * [Link] / d;
Return {
dx : r * [Link](fAngle),
dy : -r * [Link](fAngle)
};
}
Функция GetArrowDim возвращает объект, содержащий в свойствах dx и
dy значения проекций отрезка прямой (стрелки часов) на оси координат,
рассчитанные на основе длины отрезка (параметр r) и угла его поворота,
вычисленного на основе значений параметров val и d. Параметр d пред-
ставляет собой количество перемещений стрелки часов, необходимое
для совершения ей полного поворота вокруг своей оси. Для часовой
стрелки это значение равно 24, а для минутной и секундной — 60. Та-
ким образом, отношение 2*[Link]/d является углом поворота стрелки
при одном ее перемещении. Параметр val указывает текущее положение
Ãëàâà 13. Создание анимационных эффектов 305
стрелки (количество перемещений, совершенное ей от нулевого поло-
жения). В качестве этого параметра функции GetArrowDim передаются
текущие значения часов, минут и секунд.
Функция DrawArrow реализует алгоритм несимметричного ЦДА (цифро-
вого дифференциального анализатора) для развертки отрезка прямой в
растр, производя позиционирование элементов DIV, ссылки на DOM-
объекты которых содержатся в передаваемых ей массивах в соответст-
вии с рассчитываемыми координатами точек отрезка. Этот алгоритм
весьма прост. Вы можете изучить его самостоятельно, проанализировав
код функции DrawArrow.
Использование фильтров
в Microsoft Internet Explorer
для создания анимационных эффектов
Браузер Microsoft Internet Explorer обладает рядом очень интересных
возможностей, позволяющих применять широкий спектр визуальных
эффектов к различным компонентам Web-страниц. Создание этих визу-
альных эффектов основано на использовании фильтров.
Что такое фильтры?
С точки зрения Web-разработчика, фильтры Microsoft Internet Explorer
можно охарактеризовать как набор инструкций для статического либо
динамического преобразования изображения элемента Web-страницы.
Применение фильтров осуществляется за счет расширения набора CSS-
свойств, поддерживаемых браузером. Установка этих свойств позволяет
изменять визуальное представление элементов документа. Работа
фильтров основана на технологии DirectX Transform. DirectX Transform
представляет собой интерфейс прикладного программирования (API)
для мультимедийных приложений. Он позволяет производить обработку
двумерных изображений, в том числе их создание и редактирование, а
также создание анимационных эффектов на основе набора статических
изображений. Таким образом, посредством свойств-расширений CSS,
Microsoft Internet Explorer предоставляет Web-разработчикам способ
использования возможностей DirectX Transform API.
По принципам действия следует выделить следующие типы фильтров.
Процедурные поверхности. Представляют собой цветовую поверх-
ность, отображаемую между фоном объекта и его содержимым. Ре-
306 Часть III. Создание динамических эффектов
зультирующее изображение объекта генерируется динамически, при
каждой его перерисовке (в памяти хранится только процедура обра-
ботки изображения).
Статические фильтры. Фильтры данного типа позволяют изменять
способ статического отображения объектов Web-страницы (к приме-
ру, произвести "размытие" изображения объекта, внести в него вол-
нообразные искривления и т. д.).
Фильтры трансформаций. Данный тип фильтров позволяет созда-
вать анимационные эффекты, основанные на трансформации изо-
бражений различных состояний объектов Web-страниц, выглядящие
как постепенные переходы от одного состояния к другому.
В документации Microsoft Developer Network (MSDN) выделяются по-
нятия "visual filters" (объединяющее понятия "static filters", или просто
"filters" — статические фильтры и "Procedural Surfaces") и "transitions"
(переходы), которые я назвал фильтрами трансформаций, поскольку
данный термин, на мой взгляд, наиболее точно отражает их сущность.
Применение фильтров
Использование фильтров доступно в Microsoft Internet Explorer вер-
сий 4.0 и выше. Браузеры Microsoft Internet Explorer версий 5.5 и выше
содержат более богатый набор оптимизированных фильтров.
Microsoft Internet Explorer 5.5 поддерживает два фильтра типа "про-
цедурные поверхности", шестнадцать статических фильтров и сем-
надцать фильтров трансформаций. Объем данной главы не позволяет
произвести их полное описание, а также описание их специфических
методов и свойств (хотя, общие принципы работы с фильтрами будут
изложены далее). Однако вы можете самостоятельно ознакомиться
с перечнем и возможностями фильтров, изучив соответствующие
разделы документации MSDN. В online-версии MSDN описание
фильтров, их свойств и методов вы можете найти по адресу http://
[Link]/library/[Link]?url=/workshop/author/filter/re
ference/[Link]. Также, используя пример, демонстрирующий
применение большинства фильтров, расположенный по адресу
[Link]
/author/dhtml/DXTidemo/[Link], вы сможете увидеть визу-
альные эффекты, производимые ими.
Ãëàâà 13. Создание анимационных эффектов 307
Назначение фильтров элементам
Назначение фильтров элементу Web-страницы осуществляется посред-
ством использования свойства filter в наборе CSS-правил, применяе-
мых к данному элементу. Значением свойства filter является строка,
содержащая описания фильтров элемента. Описание фильтра осуществ-
ляется в нотации, подобной нотации вызова функции. В общем виде
строку описания фильтра можно представить так:
filter:progid:[Link](Properties)
Здесь FltName является именем фильтра, а Properties — строкой опи-
сания его параметров, определяющих величину и характер преобразова-
ния изображения элемента фильтром. Следующий пример иллюстрирует
применение двух фильтров (MotionBlur и BasicImage) к изображению
(элементу IMG) путем назначения элементу встроенной информации
о стиле.
<IMG src="[Link]"
style="filter:progid:[Link](
strength=50) progid:[Link](
rotation=2, mirror=1);">
Обратите внимание на способ определения параметров фильтра.
Необходимо особо отметить, что значение CSS-свойства filter, также
как и значение любого другого CSS-свойства набора правил встроенной
информации о стиле элемента, отображается в значение соответствую-
щего свойства объекта style, инкапсулируемого DOM-объектом этого
элемента. CSS-свойство filter отображается в свойство filter объекта
style. Изменяя значение данного свойства, можно назначать новый на-
бор фильтров, применяемых к элементу. Например, имея ссылку oImg на
DOM-объект некоторого изображения, можно назначить применяемые
к нему фильтры следующим образом:
[Link] = "progid:[Link]."+
"BasicImage(rotation=2, mirror=1)";
Доступ к объектам фильтров
Программное управление параметрами преобразования изображения
объекта Web-страницы фильтрами из JavaScript-сценария осуществляет-
ся при помощи изменения свойств объектов этих фильтров.
Доступ к отдельным объектам фильтров, примененных к элементу, про-
изводится с помощью коллекции filters DOM-объекта этого элемента.
Коллекция filters имеет свойство length, содержащее числовое значе-
308 Часть III. Создание динамических эффектов
ние количества фильтров, примененных к элементу, а также методы
item и namedItem, позволяющие получать ссылки на объекты-элементы
коллекции по их порядковому номеру в коллекции и символическому
имени соответственно.
Метод item коллекции filters также поддерживает доступ к элемен-
там коллекции по их символическому имени.
Допустим, в документе имеется следующий фрагмент HTML-разметки,
задающий элемент IMG с атрибутом style, содержащим свойство filter,
значение которого назначает элементу два фильтра — BasicImage и
Alpha:
<IMG ID="ID_IMAGE" SRC="[Link]"
STYLE="
filter:progid:[Link](
rotation=2, mirror=1)
progid:[Link](opacity=50)">
Допустим, мы получили ссылку на DOM-объект этого элемента:
var oImage = [Link]("ID_IMAGE");
Тогда, получить ссылки на объекты фильтров BasicImage и Alpha по их
порядковому номеру и символическому имени, соответственно, можно
следующим образом:
var oFilter_BasicImage = [Link](0);
var oFilter_ Alpha =
[Link]("[Link]");
Манипулирование фильтрами
Как следует из описания типов фильтров в начале данной главы, про-
цедурные поверхности и статические фильтры производят статические
преобразования изображений элементов. Это означает, что если исход-
ное изображение элемента не изменяется в течение некоторого проме-
жутка времени, за это время не изменяется и изображение-результат его
преобразования статическим фильтром. Однако, поскольку на преобра-
зование изображения оказывают непосредственное влияние значения
параметров фильтра (изменяемые посредством изменения свойств объ-
екта фильтра), изменение свойств объекта фильтра приведет к измене-
нию результирующего изображения. Таким образом, при помощи мани-
пулирования параметрами статических фильтров (например, периодиче-
Ãëàâà 13. Создание анимационных эффектов 309
ского их изменения в обработчике событий таймера) можно создавать
анимационные эффекты.
Фильтры трансформаций изначально предназначены для создания ани-
мационных эффектов. Исходными данными для анимации являются
изображения двух состояний объекта Web-страницы. Фильтры транс-
формаций самостоятельно рассчитывают и визуализируют все промежу-
точные состояния. При помощи изменений свойств объектов фильтров
данного типа обычно настраиваются параметры воспроизводимой ими
анимации.
Под термином "объект Web-страницы" здесь понимается любая сово-
купность элементов документа, к которой одновременно можно при-
менить фильтр (например, форма, содержащая набор элементов
управления — анимация, создаваемая фильтром трансформации,
примененным к элементу FORM, будет основана на изображении бло-
ка, генерируемого данным элементом, включающего изображения
всех элементов, находящихся в пределах данного блока).
Применение фильтров трансформаций заключается в вызове методов
apply и play их объектов. Последовательность действий для создания
анимации при помощи фильтров трансформаций может выглядеть так:
1. Создание и/или настройка состояния объекта Web-страницы, анима-
цию которого необходимо выполнить.
2. Назначение данному объекту фильтра трансформации, с помощью
которого будет реализована анимация.
3. Если необходимо, настройка параметров фильтра путем изменения
значений свойств его объекта (значения параметров могут быть ука-
заны и при назначении фильтра объекту Web-страницы).
4. Вызов метода apply объекта фильтра. При этом фильтром будет
произведен "захват" текущего изображения объекта.
5. Изменение состояния объекта Web-страницы.
6. Вызов метода play объекта фильтра. При этом начнется процесс
воспроизведения анимации фильтром, в процессе которой изображе-
ние предыдущего состояния объекта будет постепенно трансформи-
роваться до изображения его текущего состояния.
Следует отметить, что фильтры трансформаций воспроизводят анима-
цию асинхронно. Это означает, что вызов метода play не блокирует по-
ток исполнения скрипта, т. е. скрипт будет продолжать выполняться во
время воспроизведения анимации фильтром. Для остановки процесса
310 Часть III. Создание динамических эффектов
анимации до момента его нормального завершения служит метод stop
объектов фильтров трансформаций.
Далее мы перейдем к рассмотрению практических примеров создания
анимационных эффектов при помощи фильтров в Microsoft Internet
Explorer.
Волнообразное движение текста
на основе фильтра Wave
Скрипт, реализованный в данном примере (находится в файле
examples\13\ex_13_17.htm на компакт-диске), создает анимационный
эффект волнообразного движения текста. Для создания этого эффекта
используется статический фильтр Wave, осуществляющий синусоидаль-
ные волновые искажения изображения объекта Web-страницы по верти-
кальной оси координат. Текст, подвергающийся анимации, содержится в
элементе DIV с идентификатором ID_ANIMATE. Фильтр назначается этому
элементу DIV при помощи свойства набора правил во внедренной в до-
кумент таблице стилей:
#ID_ANIMATE
{
width : 300px;
overflow: hidden;
font : normal 64px "Times New Roman", serif;
filter : Wave(freq=3, light=0, phase=0, strength=10);
}
Принцип работы скрипта основан на изменении значения фазы волно-
вых искажений, создаваемых фильтром (это значение просто постоянно
увеличивается). Значение фазы устанавливается при помощи изменения
свойства phase объекта фильтра. Скрипт очень прост, поэтому его код
приведен здесь полностью.
var oAnimate = [Link]("ID_ANIMATE");
function OnTimer()
{
if([Link][0].phase > 100)
[Link][0].phase = 0;
[Link][0].phase += 10;
}
[Link](OnTimer, 100);
Ãëàâà 13. Создание анимационных эффектов 311
Динамическое изменение прозрачности
изображений с помощью фильтра Alpha
Скрипт, находящийся в файле examples\13\ex_13_18.htm, еще более
прост, чем предыдущий. Он осуществляет изменение прозрачности вне-
дренных в документ изображений в зависимости от того, находится ли
курсор мыши над изображением (если курсор мыши находится над изо-
бражением, оно не прозрачно, в противном случае, значение коэффици-
ента его прозрачности составляет 50%). Далее приведен полный листинг
скрипта.
var fnAlpha50 =
new Function("[Link] = 50;");
var fnAlpha100 =
new Function("[Link] = 100;");
for(var i = 0; i < 9; i++)
{
var oImg = [Link]("ID_IMG_" +
[Link](10));
[Link] = fnAlpha50;
[Link] = fnAlpha100;
}
Как видите, скрипт просто создает две функции, одна из которых уста-
навливает значение свойства opacity (уровня непрозрачности) фильтра
Alpha объекта, методом которого она является, в значение 50 (объект
Web-страницы наполовину не прозрачен), другая, соответственно, в 100
(объект не прозрачен на 100%, т. е. полностью). Затем эти функции ус-
танавливаются в качестве обработчиков onmouseout и onmouseover эле-
ментов изображений (атрибуты ID HTML-элементов изображений уста-
новлены в значения ID_IMG_0, ID_IMG_1...ID_IMG_9, и ссылки на соответ-
ствующие DOM-объекты получаются на основании значений этих
идентификаторов).
Анимационное изменение содержимого
с помощью фильтра Fade
Скрипт, который мы сейчас рассмотрим, осуществляет периодическое
циклическое анимационное изменение содержимого фрагмента доку-
мента (элемента DIV с идентификатором ID_ANIMATE). Скрипт находится
в файле examples\13\ex_13_19.htm.
312 Часть III. Создание динамических эффектов
Элемент DIV с идентификатором ID_ANIMATE содержит четыре дочерних
блока DIV, задающих варианты содержимого, которое будет отобра-
жаться в этом элементе. Принцип работы скрипта состоит в периодиче-
ском изменении состояния видимости вложенных блоков DIV таким об-
разом, что в каждый момент времени лишь один из них видим, а осталь-
ные не отображаются. Для создания эффекта анимации при смене
блоков информации используется фильтр трансформации Fade. Он осу-
ществляет плавное "затухание" изображения предыдущего содержимого
на фоне нового содержимого.
В начале скрипта инициализируются несколько переменных:
var nNum = 0;
var oVisible = ObjByNum(0);
var oAnimate = [Link]("ID_ANIMATE");
Переменная nNum содержит порядковый номер текущего отображаемого
элемента DIV, oVisible — ссылку на DOM-объект этого элемента, а
oAnimate — ссылку на DOM-объект элемента DIV с идентификатором
ID_ANIMATE (элемент-контейнер для элементов DIV с информационным
содержимым). Используемая здесь функция ObjByNum возвращает ссыл-
ку на DOM-объект элемента DIV с информационным содержимым иден-
тификатора по его порядковому номеру.
Фильтр Fade назначается этому элементу DIV с идентификатором
ID_ANIMATE при помощи свойства набора правил во внедренной в доку-
мент таблице стилей:
#ID_ANIMATE
{
...
filter:progid:[Link](duration=1,
overlap=1.0);
}
Смена состояний видимости блоков с информационным содержимым
производится в обработчике событий таймера OnTimer.
function OnTimer()
{
[Link][0].apply();
[Link]="hidden";
nNum++;
Ãëàâà 13. Создание анимационных эффектов 313
if(nNum > 3)
nNum = 0;
oVisible = ObjByNum(nNum);
[Link]="visible";
[Link][0].play();
}
Обратите внимание, для того чтобы реализовать анимационную смену
содержимого, достаточно просто вызвать метод apply объекта фильтра
до внесения изменений в содержимое блока и play — после изменений.
Эффектная анимация на основе
использования фильтра Light
Последний пример скрипта, который мы рассмотрим в данной главе,
создает анимацию, вызывающую иллюзию движения светящихся шаров
по эллиптическим траекториям в трехмерном пространстве. Выглядит
это весьма эффектно.
Анимация создается с помощью применения статического фильтра
Light, создающего эффект вспышек света (или световых пятен) на изо-
бражении содержимого объекта Web-страницы. В качестве такого объ-
екта выступает элемент DIV с идентификатором ID_ANIMATE, заданный
HTML-разметкой. При помощи правила во внедренной в документ таб-
лице стилей фильтр Light назначается этому элементу.
#ID_ANIMATE
{
...
filter : Light();
}
Алгоритм работы скрипта крайне прост. В процессе его инициализации
в переменную oFilter получается ссылка на объект фильтра Light
(здесь же определяются переменные fPhase — параметр, олицетворяю-
щий общее время движения объектов, sin и cos — ссылки на соответст-
вующие методы объекта Math, для удобства).
var fPhase = 0;
var oFilter =
[Link]("ID_ANIMATE").filters[0];
314 Часть III. Создание динамических эффектов
var sin = [Link];
var cos = [Link];
А в обработчике событий таймера производится очистка (с помощью
метода clear объекта фильтра), затем добавление (с помощью метода
addPoint объекта фильтра) световых пятен на изображение упомянутого
ранее элемента DIV с координатами и размерами, вычисленными на
основе уравнения эллипса и различным цветом, а также наращивание
значения переменной fPhase.
function OnTimer()
{
[Link]();
[Link](200 + 80 * sin(fPhase * 1.2),
150 + 120 * cos(fPhase * 1.2),
8 + 3 * cos(fPhase * 1.2),
192, 192, 255, 100);
[Link](200 + 60 * sin(fPhase * 0.6),
150 + 70 * cos(fPhase * 0.6),
10 + 6 * sin(fPhase * 0.6),
192, 255, 192, 100);
fPhase += 0.1;
}
На этом мы заканчиваем рассмотрение принципов и практических
примеров создания анимационных эффектов с помощью клиентских
JavaScript-сценариев.
ЧАСТЬ IV
Примеры разработки
сложных скриптов
Глава 14. Разработка визуального редактора HTML
на основе режима редактирования
документа Microsoft Internet Explorer
Глава 15. Приемы эффективного
объектно-ориентированного
программирования на JavaScript
Эта, последняя, часть книги посвящена несколько более сложным
вопросам создания скриптов, чем предыдущие. В ней мы ознакомим-
ся с некоторыми специфическими возможностями браузера Microsoft
Internet Explorer и разработаем визуальный редактор HTML, исполь-
зующий режим редактирования документа этого браузера. Также
мы обсудим некоторые неочевидные моменты, трудности и прие-
мы эффективного объектно-ориентированного программирования на
JavaScript. Мы разработаем библиотеку классов, решающую вы-
деленные проблемы и создающую основу для более продуктивной
объектно-ориентированной разработки.
ГЛАВА 14
Разработка визуального
редактора HTML на основе
режима редактирования
документа Microsoft Internet
Explorer
В этой главе мы разработаем клиентский сценарий, реализующий не-
большое приложение, позволяющее производить создание гипертексто-
вых документов в режиме визуального редактирования. В настоящее
время скрипты подобного рода достаточно широко применяются для
организации интерфейсов тех частей Web-сайтов, где требуется воз-
можность формирования содержимого, включающего элементы размет-
ки (например, в формах редактирования сообщений на форумах). Наш
редактор, однако, полностью предназначен для работы на клиентской
машине — он имеет функцию сохранения созданного документа на уст-
ройство хранения данных, подключенное к системе пользователя. Вид
окна браузера с открытым документом, в котором реализован редактор,
представлен на рис. 14.1.
Надо сказать, что создание скрипта для полноценного редактирования
гипертекстового содержимого не осуществимо без использования спе-
цифических возможностей конкретных браузеров. Так, на данный мо-
мент можно реализовать такие скрипты для работы в браузерах семей-
ства Mozilla (Mozilla и Mozilla FireFox), а также Microsoft Internet Ex-
plorer.
Скрипт, процесс создания которого мы рассмотрим далее, предназначен
для использования в Microsoft Internet Explorer версий 5.0 и выше. Это
обуславливает его простоту. Во-первых, скрипт реализуется только для
318 Часть IV. Примеры разработки сложных скриптов
одной платформы, а во-вторых, Internet Explorer обладает очень богатым
набором специфических возможностей, в том числе и возможностью
редактирования гипертекстового содержимого в окне браузера. Несмот-
ря на простоту скрипта, он хорошо демонстрирует принципы создания
клиентского JavaScript-сценария, реализующего редактор HTML.
Рис. 14.1. Работа визуального редактора HTML
Редактирование гипертекстового
содержимого в Microsoft Internet
Explorer
Перед тем как перейти непосредственно к обсуждению разработки
скрипта HTML-редактора, давайте рассмотрим, какие же средства для
его реализации предоставляет Microsoft Internet Explorer. К слову,
Internet Explorer имеет массу интересных специфических возможностей.
Многие из них весьма впечатляют. Здесь можно упомянуть:
создание HTML-приложений (HTML applications);
назначение элементам поведения (Behaviors);
создание пользовательских элементов управления (WebControls) и
использование их через механизм Behaviors;
Ãëàâà 14. Разработка визуального редактора HTML… 319
связывание данных (Data Binding);
применение фильтров к элементам Web-страницы;
возможность использования Timer API;
расширенные возможности работы с окнами — использование мо-
дальных, немодальных диалогов, всплывающих (popup) окон и окон
справки;
возможность программной инициации исполнения некоторых функ-
ций браузера (например, печати документа);
включение режима редактирования содержимого отдельного элемен-
та либо всего документа.
Некоторые из данных возможностей мы будем использовать при разра-
ботке нашего скрипта.
Наличие в Microsoft Internet Explorer большого количества возможно-
стей, не присущих другим браузерам, объясняется тем, что он созда-
вался как неотъемлемый стандартный компонент операционной сис-
темы, предназначенный для решения гораздо более широкого спектра
задач, нежели просто загрузка и просмотр multimedia-данных. Так,
Internet Explorer может быть встроен как элемент ActiveX в любое при-
ложение с графическим пользовательским интерфейсом. Благодаря
этому, в приложении под Windows можно достаточно легко реализо-
вать поддержку просмотра гипертекстового содержимого, а также ор-
ганизовать построение пользовательского интерфейса на основе ги-
пертекстовых данных. Кроме того, Internet Explorer поддерживает ра-
боту с обработчиками подключаемых протоколов (Asynchronous
Pluggable Protocols), что позволяет производить загрузку данных в
браузер из произвольных источников, используя URL с нестандартной
схемой протокола, а также MIME-фильтрами, позволяющими произво-
дить фильтрацию данных, загружаемых в браузер.
Итак, редактирование гипертекстового содержимого средствами Micro-
soft Internet Explorer возможно двумя способами:
редактирование содержимого отдельного элемента текущего доку-
мента. Данный режим включается путем установки значения true
свойству contentEditable DOM-объекта элемента, содержимое ко-
торого должно редактироваться;
редактирование содержимого всего документа. Этот режим включа-
ется путем установки значения "On" свойству designMode объекта до-
кумента.
320 Часть IV. Примеры разработки сложных скриптов
В первом случае контейнером редактируемого содержимого должен
являться некоторый элемент документа (можно использовать элемент
DIV), свойству contentEditable DOM-объекта которого присвоено зна-
чение true. Во втором случае редактирование может производиться во
встроенном фрейме (элемент IFRAME), для объекта document которого
свойство designMode установлено в "On". Во втором случае большинство
аспектов функциональности скрипта (операции редактирования, форма-
тирования текста, а также сохранение документа) реализуются проще,
чем в первом, поэтому при создании скрипта визуального редактора
HTML мы будем использовать именно этот подход.
В задачи данной главы, безусловно, не входит подробное рассмотре-
ние всех специфических возможностей браузера Microsoft Internet
Explorer. Однако вы можете изучить их самостоятельно, воспользо-
вавшись информацией раздела Web Development библиотеки MSDN.
В Интернете MSDN доступна по адресу [Link]
/library.
Реализация
визуального редактора HTML
Итак, давайте приступим непосредственно к рассмотрению аспектов
разработки скрипта визуального редактора HTML. Все файлы (гипер-
текстовые документы, файлы таблиц стилей и изображений), относя-
щиеся к реализации редактора, вы можете найти в каталоге examples\14
на прилагаемом к книге компакт-диске. Основной код сценария содер-
жится в документе [Link]. Открыв этот документ в Internet Explorer,
вы сможете опробовать скрипт его в работе.
В каталоге examples\14 также находится файл [Link]. Это тот же
самый редактор HTML, оформленный в виде HTML-приложения. Со-
держимое файла [Link] практически идентично содержимому фай-
ла [Link] (он отличается лишь наличием элемента HTA в разделе
HEAD).
Структура документа [Link]
В файле [Link] содержится HTML-разметка, при помощи которой
создаются панели инструментов, содержащие кнопки операций, произ-
водимых над документом, элемент IFRAME, в котором будет выполняться
Ãëàâà 14. Разработка визуального редактора HTML… 321
редактирование документа, а также элемент OBJECT, реализующий объ-
ект Dialog Helper, при помощи которого будет получен список шрифтов,
содержащихся в системе пользователя, а также будет отображаться
стандартный диалог выбора цвета.
Панели инструментов построены на основе таблиц, а кнопки являются
обычными ссылками (они содержат изображения пиктограмм кнопок), в
атрибутах onclick которых производятся вызовы соответствующих об-
работчиков. Параметры визуального представления элементов, состав-
ляющих панели инструментов, определяются связанной с документом
таблицей стилей, содержащейся в файле [Link]. Панель инструмен-
тов форматирования текста включает также два выпадающих списка
(элементы SELECT), которые содержат названия гарнитур и размеры
шрифтов. Элемент списка гарнитур шрифтов имеет идентификатор
ID_COMBO_FONT, а список размеров шрифтов — ID_COMBO_FONT_SIZE.
Элементы списка размеров шрифтов задаются HTML-разметкой, а спи-
сок гарнитур заполняется в процессе инициализации редактора.
Элемент IFRAME, в котором будет производиться редактирование гипер-
текстового содержимого, имеет атрибут name, установленный в значение
"EDITOR", а элемент OBJECT, реализующий объект Dialog Helper, — атри-
бут ID со значением ID_FONT_DIALOG.
Процесс инициализации редактора
Процесс инициализации редактора включает в себя получение списка
гарнитур шрифтов, установленных в системе пользователя, заполнение
ими выпадающего списка шрифтов на панели форматирования текста, а
также создание нового документа в элементе IFRAME.
Получение списка гарнитур шрифтов производится при помощи объекта
Dialog Helper, упоминавшегося ранее. Ссылка на данный объект, значе-
ния имен гарнитур шрифтов, а также другие значения, использующиеся
при работе редактора, сохраняются в полях статического объекта App,
находящегося в глобальной области видимости идентификаторов. Лис-
тинг создания данного объекта приведен далее:
var App =
{
m_bUseSysColorDialog : true,
m_oEditor : [Link]("EDITOR"),
m_aFonts : null,
m_oFontDialog : null
};
322 Часть IV. Примеры разработки сложных скриптов
Как можно видеть, в свойстве m_oEditor этого объекта сохраняется
ссылка на DOM-объект элемента IFRAME, в котором будет производиться
редактирование гипертекстового содержимого. Свойство m_aFonts
предназначено для хранения массива доступных гарнитур шрифтов.
В поле m_oFontDialog будет сохранена ссылка на объект Dialog Helper.
А поле m_bUseSysColorDialog хранит логическое значение, указываю-
щее, надо ли пытаться использовать системный диалог выбора цвета
при установке цвета шрифта и фона текста (более подробно это свойст-
во описано в разд. "Установка цвета шрифта и фона текста" далее в
этой главе).
Как уже упоминалось, объект Dialog Helper создается при помощи эле-
мента OBJECT, задаваемого HTML-разметкой. Фрагмент соответствую-
щей разметки приведен далее.
<OBJECT
ID="ID_FONT_DIALOG"
CLASSID="clsid:3050f819-98b5-11cf-bb82-00aa00bdce0b"
width="0px"
height="0px"
onerror="OnDialogHelperError();"
onreadystatechange="OnDialogHelperReadyStateChange();"
></OBJECT>
Dialog Helper является COM-объектом. Его создание и инициализация
занимают некоторое время. И, если попытаться получить доступ к како-
му-либо из его свойств или вызвать его метод из сценария до оконча-
ния процесса инициализации, произойдет фатальная ошибка работы
браузера. С целью предотвращения такой ситуации для данного объ-
екта установлены обработчики событий error и readystatechange
(OnDialogHelperError и OnDialogHelperReadyStateChange соответст-
венно). Таким образом, функция OnDialogHelperReadyStateChange будет
вызываться при изменении состояния объекта в процессе инициализа-
ции, а OnDialogHelperError — в случае, если инициализация объекта
завершилась неудачей. Листинг данных функций приведен далее.
function OnDialogHelperReadyStateChange()
{
var oFontDialog = [Link]("ID_FONT_DIALOG");
if( ([Link] == 4) ||
([Link] == "complete"))
{
App.m_oFontDialog = oFontDialog;
Ãëàâà 14. Разработка визуального редактора HTML… 323
[Link]("OnInit()", 0);
}
}
function OnDialogHelperError()
{
[Link]("OnInit()", 0);
}
В функции OnDialogHelperReadyStateChange производится анализ кода
состояния готовности объекта Dialog Helper (анализируется свойство
readyState объекта). В случае полной инициализации объекта ссылка на
него присваивается свойству m_oFontDialog объекта App и запускается
таймер с однократным срабатыванием с интервалом 0, вследствие воз-
никновения события которого вызовется функция OnInit. Отложенный
вызов функции OnInit необходим все по той же причине — для предот-
вращения возникновения фатальной ошибки браузера вследствие досту-
па к свойствам и методам не до конца инициализированного объекта.
Практика показывает, что в обработчике события readystatechange да-
же в случае, если свойство readyState объекта Dialog Helper установле-
но в значение, свидетельствующее о его полной инициализации, работу
с объектом производить все еще нельзя.
Функция OnDialogHelperError просто инициирует отложенный вызов
OnInit, аналогично тому, как это делается в OnDialogHelperReadyState-
Change (заметьте, в этом случае значение свойства m_oFontDialog объек-
та App остается равным null).
Функция OnInit (см. полный код в файле [Link]) производит основ-
ные действия по инициализации редактора. Функция инициализирует
свойство m_aFonts объекта App значением пустого массива. Затем, в слу-
чае если доступен объект Dialog Helper, осуществляется попытка (обра-
тите внимание на обработку исключений) заполнения данного массива
списком гарнитур шрифтов, полученных при помощи использования
коллекции fonts этого объекта:
App.m_aFonts = new Array();
if(App.m_oFontDialog)
{
try
{
var oFonts = App.m_oFontDialog.fonts;
var nCount = [Link];
324 Часть IV. Примеры разработки сложных скриптов
for(var i = 1; i < nCount; i++)
App.m_aFonts[i - 1] = oFonts(i);
}
catch(oException)
{
}
}
В случае, если массив App.m_aFonts не был заполнен, данному свойству
присваивается значение скалярно определенного массива, содержащего
набор предопределенных гарнитур:
if(App.m_aFonts.length < 1)
{
App.m_aFonts =
[
"Arial",
"Courier",
...
];
}
Далее массив App.m_aFonts сортируется, и его значениями заполняется
список шрифтов на панели инструментов:
App.m_aFonts.sort();
var oCombo = [Link]("ID_COMBO_FONT");
for(var i = 0; i < App.m_aFonts.length; i++)
{
var oOption = [Link]("OPTION");
[Link] = App.m_aFonts[i];
[Link] = [Link]();
[Link](oOption);
}
В завершение, вызывается функция NewDocument, выполняющая созда-
ние нового документа в элементе IFRAME, на основе которого построен
редактор.
Создание нового документа
Как было сказано в предыдущем разделе, новый документ создается
функцией NewDocument. Ее листинг (несколько сокращенный) приведен
далее.
Ãëàâà 14. Разработка визуального редактора HTML… 325
function NewDocument()
{
GetDocument().designMode = "Off";
var oDocument = GetDocument();
[Link]("text/html", "replace");
[Link]
(
"<html>\r\n" +
...
"</html>\r\n" +
"\r\n"
);
[Link]();
[Link] = "On";
oDocument = GetDocument();
[Link]("2D-Position", true, true);
[Link]("LiveResize", true, true);
[Link]("MultipleSelection", true, true);
[Link] = OnDocContextMenu;
[Link] = OnDocSelChahge;
setTimeout("OnAfterNew()", 0);
}
Данная функция используется как функцией OnInit, так и обработчиком
команды создания нового документа OnNew. Поэтому первым действием,
которое совершает функция NewDocument, является "отключение" режи-
ма редактирования документа элемента IFRAME (свойству designMode
объекта документа присваивается значение "Off"). Ссылка на объект
документа возвращается функцией GetDocument. Ее листинг приведен
далее.
function GetDocument()
{
return App.m_oEditor.document;
}
Затем функция NewDocument открывает документ для записи методом
open, записывает его начальное содержимое (элементы HTML, BODY, HEAD,
326 Часть IV. Примеры разработки сложных скриптов
TITLE) методом write, закрывает документ методом close и снова
"включает" режим редактирования (свойству designMode присваивается
значение "On").
Далее при помощи метода execCommand в редактируемом документе ис-
полняются несколько команд с кодами 2D-Position, LiveResize и
MultipleSelection. Данные команды, соответственно, разрешают: пе-
ремещение с помощью мыши абсолютно позиционированных блоков,
немедленное обновление представления элементов при их перемещении
или изменении размеров, множественное выделение элементов.
Здесь надо сказать несколько слов о методе execCommand. Он служит для
исполнения команд для текущего документа, выделенной части доку-
мента либо объекта выделения (TextRange или ControlRange). Прототип
метода выглядит следующим образом:
execCommand(sCommand [, bUserInterface] [, vValue])
Исполняемая данным методом команда идентифицируется при помощи
кода, передаваемого в качестве первого параметра строкового типа.
В документации MSDN определен достаточно широкий перечень ко-
манд различного назначения (например, команды редактирования, фор-
матирования документа и т. д.). Второй параметр логического типа ука-
зывает на то, должен ли браузер при исполнении команды осуществлять
взаимодействие (если команда предполагает возможность такого взаи-
модействия) с пользователем посредством собственных механизмов и
элементов интерфейса. Смысл третьего параметра переменного типа
зависит от конкретной команды. Далее мы будем широко применять
возможности метода execCommand.
Итак, вернемся к функции NewDocument. После исполнения в документе
необходимых команд производится установка обработчиков двух собы-
тий объекта документа:
[Link] = OnDocContextMenu;
[Link] = OnDocSelChahge;
Обработчиком события при необходимости открытия контекстного ме-
ню назначается функция OnDocContextMenu, а обработчиком события
изменения состояния текущего выделения в документе — функция
OnDocSelChahge. Функция OnDocContextMenu просто возвращает false —
так предотвращается появление стандартного контекстного меню брау-
зера при совершении пользователем щелчка правой кнопкой мыши на
элементе IFRAME, содержащем редактируемый документ. Об обработчи-
ке OnDocSelChahge будет сказано в разд. "Обработка событий измене-
ния гарнитуры и размера шрифта текста" далее в этой главе.
Ãëàâà 14. Разработка визуального редактора HTML… 327
В конце функция NewDocument запускает таймер с единичным срабаты-
ванием с интервалом 0, обработчиком события которого является функ-
ция OnAfterNew:
function OnAfterNew()
{
App.m_oEditor.focus();
UpdateToolbar();
}
Она устанавливает фокус ввода на элемент IFRAME, содержащий редак-
тируемый документ, а также вызывает функцию UpdateToolbar для об-
новления состояния списков гарнитур и размеров шрифтов в соответст-
вии с текущим выделением в документе. Функция UpdateToolbar также
будет описана в разд. "Обработка событий изменения гарнитуры и
размера шрифта текста" далее в этой главе.
Последним обстоятельством, на которое следует обратить внимание при
рассмотрении функции NewDocument, является многократное получение
ссылки на объект редактируемого документа — при отключении режима
редактирования, после отключения режима редактирования, после
включения режима редактирования. Необходимость совершения данных
действий обоснована тем, что при изменении состояния активности ре-
жима редактирования документа изменяется значение ссылки на его
объект и значение, полученное ранее и сохраненное в какой-либо пере-
менной, становится недействительным — при попытке доступа к свой-
ствам или вызова методов объекта по ссылке, сохраненной в перемен-
ной, происходит ошибка исполнения скрипта. В частности, данным об-
стоятельством мотивируется создание функции GetDocument — она
служит для удобства получения ссылки на объект редактируемого доку-
мента.
Обработка команд редактирования
и форматирования текста, печати
и сохранения документа
Все команды редактирования и форматирования текста (а также добав-
ления и удаления гиперссылок), печати и сохранения документа основа-
ны на одном механизме — исполнении команд в редактируемом до-
кументе методом execCommand. Для удобства использования метода
execCommand в сценарии была реализована функция DoCommand. Порядок
следования ее параметров и их назначение аналогичны порядку следо-
328 Часть IV. Примеры разработки сложных скриптов
вания и назначению параметров метода execCommand. Код функции
DoCommand приведен далее.
function DoCommand(strCommand, bUserInterfase, oValue)
{
bUserInterfase = bUserInterfase | false;
var bResult = GetDocument().execCommand(
strCommand, bUserInterfase, oValue);
App.m_oEditor.focus();
return bResult;
}
Как видите, функция DoCommand просто вызывает метод execCommand
редактируемого документа, передавая ему свои параметры, а потом ус-
танавливает фокус ввода на элемент IFRAME. Таким образом, код подав-
ляющего большинства обработчиков команд кнопок панелей инстру-
ментов просто сводится к вызову функции DoCommand с передачей ей
необходимого кода команды. Так, обработчики команд сохранения до-
кумента OnSave и установки/отключения жирного начертания шрифта
OnBold выглядят следующим образом:
function OnSave()
{
DoCommand("SaveAs", true);
}
function OnBold()
{
DoCommand("Bold");
}
Далее, в табл. 14.1 приведен список команд редактирования документа,
соответствующих функций-обработчиков и идентификаторов команд,
передаваемых этими обработчиками функции DoCommand.
Таблица 14.1. Команды редактирования документа
Команда редактирования Обработчик Идентификатор
Вырезать OnCut Cut
Копировать OnCopy Copy
Ãëàâà 14. Разработка визуального редактора HTML… 329
Таблица 14.1 (окончание)
Команда редактирования Обработчик Идентификатор
Вставить OnPaste Paste
Жирный шрифт OnBold Bold
Курсивный шрифт OnItalic Italic
Подчеркнутый шрифт OnUnderline Underline
Выравнивание влево OnTextAlignLeft JustifyLeft
Выравнивание по центру OnTextAlignCenter JustifyCenter
Выравнивание вправо OnTextAlignRight JustifyRight
Создать/изменить гиперссылку OnHyperlink CreateLink
Удалить гиперссылку OnHyperlinkDelete Unlink
Сохранить документ OnSave SaveAs
Напечатать документ OnPrint Print
Реализация обработчиков команд изменения гарнитуры и размера
шрифта, а также изменения цвета шрифта и фона текста будут рассмот-
рены нами в следующих разделах этой главы.
Обработка событий изменения гарнитуры
и размера шрифта текста
Изменение гарнитуры и размера шрифта текста пользователем произво-
дится посредством выбора пунктов в соответствующих выпадающих
списках на панели инструментов форматирования. Обработчиками со-
бытий change элементов списков являются функции OnFontComboShange
и OnFontComboSizeShange. Их листинг приведен далее.
function OnFontComboShange()
{
var oCombo = [Link]("ID_COMBO_FONT");
var nSelIndex = [Link];
if(nSelIndex > -1)
DoCommand("FontName", false,
App.m_aFonts[
parseInt([Link](nSelIndex).value)]);
}
330 Часть IV. Примеры разработки сложных скриптов
function OnFontComboSizeShange()
{
var oCombo = [Link]("ID_COMBO_FONT_SIZE");
var nSelIndex = [Link];
if(nSelIndex > -1)
DoCommand("FontSize", false,
[Link](nSelIndex).value);
}
Функция OnFontComboShange вызывает функцию DoCommand, передавая ей
в качестве параметра команды с кодом FontName наименование устанав-
ливаемой гарнитуры шрифта, полученной из массива m_aFonts, являю-
щегося свойством объекта App. А функция OnFontComboSizeShange пере-
дает в DoCommand код команды FontSize и значение размера шрифта,
сохраненное в свойстве value элемента списка функцией OnInit.
Для отображения в выпадающих списках гарнитур и размеров шрифтов
значений, соответствующих текущему положению курсора или блока
выделения в редактируемом документе, в сценарии производится обра-
ботка события selectionchange документа. Обработчиком данного со-
бытия является уже упоминавшаяся функция OnDocSelChahge. Она про-
изводит только одно действие — вызывает функцию UpdateToolbar:
function OnDocSelChahge()
{
UpdateToolbar();
}
Функция UpdateToolbar, собственно, и служит для установки в выпа-
дающих списках гарнитур и размеров шрифтов текущих элементов в
соответствии с положением курсора или блока выделения в редакторе.
Далее приведен листинг фрагмента кода функции UpdateToolbar (пол-
ный код функции см. в файле [Link]), производящий установку те-
кущего выбранного элемента в списке гарнитур шрифтов.
var oFontCombo = [Link]("ID_COMBO_FONT");
var oFontSizeCombo = [Link]("ID_COMBO_FONT_SIZE");
var oDocument = GetDocument();
var nNewIndex = -1;
if([Link]("FontName"))
{
var strFont = [Link]("FontName");
Ãëàâà 14. Разработка визуального редактора HTML… 331
if(strFont)
{
for(var i = 0; i < App.m_aFonts.length; i++)
{
if(App.m_aFonts[i] == strFont)
{
nNewIndex = i;
break;
}
}
}
}
if([Link] != nNewIndex)
[Link] = nNewIndex;
Как видно из листинга, в функции UpdateToolbar производится провер-
ка возможности исполнения команды с кодом FontName при помощи
метода queryCommandEnabled объекта редактируемого документа. Затем,
в случае успеха, получается текущая гарнитура шрифта при помощи
вызова метода queryCommandValue (метод queryCommandValue возвраща-
ет текущее значение команды, код которой ей передан). В случае, если
имя гарнитуры было получено, производится поиск элемента массива
App.m_aFonts, содержащего идентичное значение. Индекс этого элемен-
та соответствует индексу элемента выпадающего списка гарнитур
шрифтов, который должен быть установлен в качестве текущего. В ито-
ге, если индекс элемента, который должен быть выделен в списке, отли-
чается от индекса текущего элемента, при помощи изменения свойства
selectedIndex, производится установка нового выделенного элемента
списка. Аналогичным образом осуществляется и обновление списка
размеров шрифтов.
Установка цвета шрифта и фона текста
Обработчиками команд установки цвета шрифта и фона текста являются
функции OnTextColor и OnBackgroundColor. Они вызывают функцию
DoCommand, передавая ей идентификаторы команд ForeColor и
BackColor, соответственно, а также значение параметра команды, воз-
вращаемой функцией DoColorDialog. Листинг функций OnTextColor и
OnBackgroundColor приведен далее.
function OnTextColor()
{
var oDocument = GetDocument();
332 Часть IV. Примеры разработки сложных скриптов
if([Link]("ForeColor"))
DoCommand("ForeColor", false,
DoColorDialog([Link]("ForeColor")));
}
function OnBackgroundColor()
{
var oDocument = GetDocument();
if([Link]("BackColor"))
DoCommand("BackColor", false,
DoColorDialog(
[Link]("BackColor")));
}
Функция DoColorDialog производит отображение диалога выбора цвета.
В качестве параметра ей передается значение текущего цвета (текста
или фона), полученное в функциях OnTextColor и OnBackgroundColor
при помощи вызова метода queryCommandValue объекта редактируемого
документа. В случае, если свойство m_bUseSysColorDialog объекта App
установлено в true и объект Dialog Helper был успешно инициализиро-
ван (ссылка на него содержится в свойстве m_oFontDialog), функция
DoColorDialog отображает стандартный диалог выбора цвета операци-
онной системы. В противном случае отображается диалог выбора цвета,
реализованный в файле [Link] (его реализация очень проста, основ-
ную часть файла [Link] составляет HTML-разметка, и вы можете
самостоятельно изучить принципы его работы). Листинг функции
DoColorDialog приведен далее.
function DoColorDialog(strColor)
{
var oColor = null;
var bSuccess = false;
strColor = strColor || null;
if(App.m_bUseSysColorDialog)
{
try
{
if(strColor)
oColor = App.m_oFontDialog.ChooseColorDlg();
else
oColor = App.m_oFontDialog.ChooseColorDlg(oColor);
Ãëàâà 14. Разработка визуального редактора HTML… 333
bSuccess = true;
}
catch(oException)
{
}
}
if(!bSuccess)
oColor = [Link]("[Link]", "",
"center: Yes; edge: Raised; help: No; " +
"resizable: No; scroll: No; status: No;");
oColor = [Link](16);
if([Link] < 6)
oColor = "000000".substring(0, 6 - [Link])+oColor;
return oColor;
}
Признаком успешности вызова системного диалога выбора цвета слу-
жит значение локальной переменной bSuccess. Ее значение будет уста-
новлено в true только после завершения работы метода ChooseColorDlg
объекта App.m_oFontDialog. В случае, если в процессе вызова метода
ChooseColorDlg возникло исключение, либо системный диалог выбора
цвета не вызывался (значение свойства App.m_bUseSysColorDialog равно
false), значение переменной bSuccess останется равным false, и функ-
ция DoColorDialog при помощи метода showModalDialog объекта window
произведет отображение модального диалога, реализованного в файле
[Link].
Таким образом, если вы хотите, чтобы при работе редактора никогда
не использовался системный диалог выбора цвета, измените значе-
ние свойства m_bUseSysColorDialog на false в коде инициализации
объекта App.
Системный диалог выбора цвета возвращает числовое значение, которое
должно быть конвертировано в его строковое представление в шест-
надцатеричной системе счисления, дополненное лидирующими нулями
до длины шесть символов для того, чтобы это значение можно было
использовать в качестве параметра метода execCommand. Функция
DoColorDialog производит необходимые действия и возвращает полу-
334 Часть IV. Примеры разработки сложных скриптов
ченное значение. Диалог, реализованный в файле [Link], повторяет
поведение системного диалога выбора цвета — он также возвращает
числовое значение в нужном формате.
На этом мы закончим рассмотрение реализации визуального редактора
HTML. Как вы можете видеть, код сценария, осуществляющего функ-
ционирование редактора, весьма прост — большинство функционально-
сти основано на работе метода execCommand. Думаю, при желании, вы
сможете легко приспособить редактор для своих нужд, дополнив его
необходимыми вам возможностями.
ГЛАВА 15
Приемы эффективного
объектно-ориентированного
программирования
на JavaScript
В разд. "Объекты" главы 1 нами была рассмотрена реализация объект-
но-ориентированной парадигмы программирования в языке JavaScript.
Мы изучили принципы создания и использования объектов, а также
осуществления наследования. До настоящего момента мы занимались
разработкой весьма простых скриптов и почти не использовали объект-
ный подход. Однако при решении серьезных практических задач, при-
менение объектного подхода представляется более чем целесообразным
(думаю, это весьма очевидно для любого читателя данной книги). Дей-
ствительно, преимущества объектно-ориентированного программирова-
ния позволяют успешно справляться со сложностью разработки про-
граммного продукта, быстро растущей по мере увеличения объема со-
ставляющего его кода.
В главе 1 была указана основная особенность реализации объектно-
ориентированной парадигмы в JavaScript — он является языком на базе
прототипов. Эта особенность, безусловно, оказывает сильное влияние на
принципы объектно-ориентированного программирования на данном
языке, определяя существенные отличия от принципов программирова-
ния на языках, использующих классы.
Можно долго производить сравнение языков на базе классов и на базе
прототипов — каждая модель по-своему хороша, имеет свои достоинст-
ва, недостатки, и должна применяться соответственно решаемой задаче.
Так, модель прототипов, отсутствие жесткой типизации, возможность
изменения структуры объектов во время исполнения (Run-time) обеспе-
336 Часть IV. Примеры разработки сложных скриптов
чивают JavaScript гибкость и лаконичность, необходимую скриптовым
языкам. Однако ряд вещей, естественно и элегантно реализуемых в язы-
ках на базе классов, требуют дополнительных ухищрений и с трудом
воспринимаются при программировании на JavaScript.
Итак, в данной главе речь пойдет о тонких моментах, связанных с объ-
ектно-ориентированным программированием на JavaScript. Мы попыта-
емся провести некоторые параллели между C++ и JavaScript для того,
чтобы лучше понять суть объектно-ориентированного подхода в данном
языке и выделить в нем некоторые аспекты, которые хотелось бы улуч-
шить. Далее, мы реализуем несколько алгоритмов, позволяющих эмули-
ровать некоторые синтаксические конструкции и применять подходы,
свойственные объектно-ориентированным языкам на базе классов, по-
зволяющих повысить эффективность создания скриптов с использова-
нием объектно-ориентированного подхода. Также я дам несколько об-
щих рекомендаций по поводу эффективного объектно-ориентирован-
ного программирования на JavaScript.
Анализ модели
объектно-ориентированного
программирования на JavaScript
В данном разделе мы проведем небольшой анализ модели объектно-
ориентированного подхода, реализованного в JavaScript, выделим неко-
торые ее особенности, характерные проблемы, а также обсудим воз-
можности и приемы эффективного использования данной модели.
Класс или объект?
Как вы, возможно, знаете, понятие класса является центральным поня-
тием концепции реализации объектно-ориентированного подхода в язы-
ках программирования на базе классов. Понятие класса удобно — класс
описывает некоторую сущность предметной области, обладающую не-
кими свойствами и поведением. Класс — это абстракция. Синтаксиче-
ски выделяются: объявление (декларация) класса — конструкция языка
программирования, при помощи которой определяется его структура
(набор переменных-членов и методов, которые будут иметь объекты
данного класса), и реализация — реализации методов и некоторых (на-
пример, статических) переменных-членов класса. Объект класса пред-
ставляется как уникальный экземпляр сущности — под каждый объект
выделяется некоторый объем памяти ЭВМ для хранения его данных.
Ãëàâà 15. Приемы программирования на JavaScript 337
Как уже говорилось в главе 1, в JavaScript существует только понятие
объекта, данные не имеют жестко определенного типа, объектами явля-
ется все — функции, строки, числовые значения. Логически отсюда сле-
дует, что объект в JavaScript не имеет семантической привязки к какой-
либо сущности, однако это утверждение верно лишь частично.
При программировании на JavaScript все данные являются объектами.
Методы их создания и использования определяются программистом.
Однако при разумном подходе к построению приложения каждый объ-
ект служит определенной цели, имеет свое назначение и, как правило,
содержит набор свойств, характеризующих некоторый объект предмет-
ной области. Объекты, имеющие определенный набор свойств и мето-
дов, обычно создаются при помощи функций-конструкторов. Причем
в подавляющем большинстве случаев функции-конструкторы создают
объекты лишь одного типа (вспомним, к примеру, что в ядре исполне-
ния сценариев JavaScript есть такие конструкторы, как Number, String,
Function, возвращающие объекты числа, строки и функции соответст-
венно).
Реализация на JavaScript функции-конструктора, возвращающей объ-
екты разных типов при определенных условиях (например, в зависи-
мости от переданных функции параметров), не представляет пробле-
мы. Однако в своей практике я ни разу не встречал подобного подхода
к созданию объектов. Использование такого приема будет являться,
как минимум, признаком плохого стиля программирования.
Необходимо также заметить, что в JavaScript существует оператор
typeof. Его прямое назначение состоит в определении типа объекта
(возможности идентификации типа typeof, правда, весьма ограниче-
ны — об этом будет сказано далее). Поэтому понятие типа (класса) объ-
екта применимо (и более того, весьма актуально) при рассмотрении раз-
личных аспектов объектно-ориентированного программирования на
JavaScript.
Так что же тогда подразумевает "бестиповость" в JavaScript (к слову, в
отношении JavaScript также часто применяется термин "слаботипизиро-
ванный язык программирования")? Во-первых, это относится к пере-
менным. Переменной в языках со строгой типизацией может соответст-
вовать объект только определенного типа (класса). Переменные в
JavaScript не имеют типа. Фактически они являются ссылками на объек-
ты. Любая переменная может ссылаться на объект любой структуры
(любого типа).
338 Часть IV. Примеры разработки сложных скриптов
На мой взгляд, наилучшей аналогией переменной в JavaScript являет-
ся объект "умного" указателя (smart pointer) в C++. Проводя дальней-
шие аналогии, можно заметить, что переменная в JavaScript обладает
одновременно свойствами ссылки и свойствами указателя C++. На-
пример, доступ к значению переменной осуществляется в нотации
доступа по ссылке при сравнении двух переменных, соответствующих
объектам простых типов данных (числа, строки), происходит сравне-
ние значений. Однако при сравнении переменных, являющихся ссыл-
ками на пользовательские объекты, происходит сравнение значений
ссылок, подобно сравнению указателей C++ (результатом сравнения
будет являться истина, если сравниваемые переменные ссылаются
на один и тот же объект).
Вторым обстоятельством, характеризующим "бестиповость" в JavaScript,
является возможность изменения структуры объекта во время исполне-
ния (например, удаление одних свойств и добавление других). Как мы
уже говорили, структура объекта естественным образом определяет се-
мантику его использования и, скорее всего, указывает на его принад-
лежность к группе объектов некоторого типа. Тип объектов в данном
случае соответствует некоторой сущности предметной области. Так,
изменяя структуру объекта некоторого типа, можно вполне "превратить"
его в объект другого типа.
Обобщая сказанное, можно сделать вывод: понятие типа в JavaScript
применимо в контексте существования групп объектов, обладающих
одинаковым поведением — одинаковым набором свойств и методов.
Как уже отмечалось, создание объектов, обладающих одинаковым пове-
дением, рационально производить при помощи функции-конструктора.
Причем хороший стиль программирования подразумевает реализацию
отдельного конструктора для объектов каждого типа. Это особо важно
при решении масштабных задач на JavaScript с интенсивным примене-
нием объектно-ориентированного программирования. Фактически при
использовании данного подхода каждая функция-конструктор определя-
ет класс объектов. Здесь действительно прослеживается достаточно
сильная аналогия с понятием класса в таких языках, как C++. Так, дек-
ларация класса в C++ определяет набор переменных-членов и методов
данного класса. В функции-конструкторе же производится создание
свойств и, возможно, методов инициализируемого объекта. Методы
объектов в JavaScript могут задаваться и через прототип — свойство
prototype, которое также является свойством объекта функции-
конструктора. Конечно, данная аналогия не совсем точна — определе-
ние класса в C++ декларирует структуру объекта, а конструктор класса
Ãëàâà 15. Приемы программирования на JavaScript 339
производит его инициализацию, присваивая значения переменным-
членам в уже созданном объекте (память под объект уже выделена пол-
ностью). А функция-конструктор в JavaScript именно создает свойства,
часть из которых может являться методами.
В любом случае, конструктор в JavaScript полностью определяет на-
чальную структуру создаваемого им объекта. И при правильно спроек-
тированной архитектуре приложения, данная структура не будет ме-
няться на протяжении всего времени жизни объекта. Таким образом,
типы объектов явно ассоциируются с создающими объекты конструкто-
рами. Иными словами, можно утверждать, что функция-конструктор
определяет класс объектов. Исходя из этого, для обозначения принад-
лежности объекта к определенному классу, характеризующему его свой-
ства, логично ссылаться на создавший данный объект конструктор.
Подводя итог сказанному, давайте определим терминологию, которая
для большей лаконичности и ясности будет использоваться в данной
главе.
Класс. Под термином "класс" мы будем понимать, в основном, про-
граммную реализацию (включающую функцию-конструктор) меха-
низма, обеспечивающего создание объектов, обладающих одинако-
вой структурой и поведением. Фактически, в нашем случае класс оп-
ределяется функцией-конструктором (ее реализацией), а также
свойствами и методами, задаваемыми через прототип объекта функ-
ции-конструктора.
Класс X. Классом с именем (идентификатором) X мы будем называть
класс, реализации которого принадлежит функция-конструктор с
именем X.
Конструктор класса X. Этим понятием будет обозначаться функция-
конструктор с именем X, входящая в реализацию класса X.
Объект класса X. Так мы будем обозначать факт принадлежности
объекта к классу X, т. е. множеству объектов, созданных функцией-
конструктором с именем X.
Базовый класс. Базовым классом класса X мы будем называть класс,
определяемый при помощи объекта функции-конструктора Y, кото-
рой (фукцией-конструктором Y) был создан объект, установленный
в качестве объекта-прототипа функции-конструктора с именем X.
Фактически, в соответствии со схемой осуществления наследования
(см. далее в этой главе), эти функции-конструкторы должны будут
обладать еще рядом свойств.
340 Часть IV. Примеры разработки сложных скриптов
Метод M класса X. Данным понятием мы будем обозначать метод,
назначаемый объектам, создаваемым при помощи функции-кон-
структора X либо при помощи присваивания в теле данной функции,
либо через prototype объекта функции-конструктора X.
Проблемы идентификации типов объектов
в JavaScript
Как было упомянуто в предыдущем разделе, JavaScript является слабо
типизированным языком. Формально, в нем отсутствует понятие класса.
Однако данным понятием удобно оперировать, обозначая множества
объектов, создаваемых одной и той же функцией-конструктором,
имеющих одинаковую структуру, поведение, назначение и семантику
использования. Поэтому мы ввели понятие класса, фактически характе-
ризующего тип объекта. В предыдущем разделе также упоминались
объекты стандартных типов данных, их конструкторы и оператор
typeof, производящий идентификацию типа объекта. Так давайте же
рассмотрим более подробно аспекты программирования на JavaScript,
связанные с идентификацией типов данных — зачем нужна и в каких
случаях требуется идентификация типа, какие возможности идентифи-
кации типа имеются в JavaScript.
Начнем с вопроса о том, существует ли принципиальная необходимость
наличия возможности идентификации типа при программировании на
JavaScript, ведь "бестиповость" положена в саму основу данного языка.
Проанализировав всевозможные задачи, возникающие при разработке
клиентских сценариев, можно прийти к отрицательному ответу на дан-
ный вопрос. Действительно, JavaScript осуществляет автоматическое
конвертирование данных различных типов при вычислении выражений,
и код любого, даже весьма сложного скрипта можно построить таким
образом, чтобы исключить необходимость проверки типов данных ис-
пользуемых переменных, свойств объектов, параметров функций и т. д.
Однако возможность идентификации типа данных оказывается полезной
в очень многих случаях. Рассмотрим небольшой пример.
Допустим, в разрабатываемом вами клиентском сценарии достаточно
часто производится установка геометрических параметров (ширины и
высоты) различных элементов в документе. Допустим, все элементы,
для которых требуется устанавливать ширину и высоту, имеют атрибут
ID, значение которого уникально. Установить требуемый параметр эле-
мента можно, изменив значение соответствующего CSS-свойства встро-
Ãëàâà 15. Приемы программирования на JavaScript 341
енной информации о стиле, получив доступ к DOM-объекту необходи-
мого элемента при помощи метода getElementById объекта document.
Это может выглядеть, например, так (далее, для простоты, будем рас-
сматривать только установку ширины элемента):
[Link]("DIV_0").[Link] = "100px";
Очевидно, что вместо многократного употребления в скрипте подобных
фрагментов кода логичнее было бы реализовать функцию, принимаю-
щую в качестве параметров идентификатор элемента и значение шири-
ны, которая бы производила необходимые действия. Код данной функ-
ции может выглядеть следующим образом:
function SetWidth(strID, strWidth)
{
[Link](strID).[Link] = strWidth;
}
Применение такой функции сократит код скрипта и сделает его более
наглядным. Так, вместо кода установки ширины элемента, приведенного
ранее, можно будет использовать вызов функции:
SetWidth("DIV_0", "100px");
Заметьте, второй параметр данной функции является строкой — через
него мы должны передавать значение, пригодное для установки в каче-
стве значения CSS-свойства width (в строке необходимо указывать еди-
ницы измерения длины). Однако допустим, в скрипте, использующем
данную функцию, в большинстве случаев ширина элементов устанавли-
вается в пикселах (правда, иногда и в других единицах, например в про-
центах). Возникает желание реализовать еще одну функцию, прини-
мающую в качестве второго параметра число и производящую установ-
ку ширины элемента в пикселах. Например:
function SetPxWidth(strID, nWidth)
{
[Link](strID).[Link] =
[Link]() + "px";
}
Получается, что в скрипте будут содержаться две функции, решающие
одинаковую задачу и производящие практически одинаковые действия,
отличие между которыми состоит лишь в типе принимаемого парамет-
ра. К слову, в некоторых языках программирования (например, в C++)
существует элегантное решение данной проблемы, заключающееся в
342 Часть IV. Примеры разработки сложных скриптов
использовании перегруженных функций (функций с одинаковыми иден-
тификаторами, но различным числом и/или типом аргументов). Реали-
зовать подобный подход в JavaScript представляется возможным при
помощи идентификации типов переданных в функцию параметров. Так,
можно модифицировать рассмотренную ранее функцию SetWidth сле-
дующим образом:
function SetWidth(strID, oWidth)
{
if(typeof(oWidth) == "number")
oWidth = [Link]() + "px";
[Link](strID).[Link] = oWidth;
}
Теперь функция проверяет тип параметра oWidth и, если параметр явля-
ется числом, конвертирует его в строку и добавляет к полученной строке
значение "px". Иначе, значение oWidth используется непосредственно
для установки ширины элемента. Как видите, идентификация типа объ-
екта может оказаться весьма полезной.
К сожалению, возможности идентификации типа в JavaScript ограничи-
ваются только оператором typeof. Для объектов нескольких встроенных
типов данных оператор typeof возвращает строковые значения, позво-
ляющие идентифицировать их тип. Так, для объекта числа typeof вернет
значение "number", для строки — "string", для функции — "function",
но, к примеру, для объекта, созданного конструктором Date, а также для
пользовательских объектов, он возвращает "object". Иными словами,
для объектов с разной семантикой использования оператор typeof мо-
жет возвращать одинаковое значение.
Мы уже говорили о том, что функция-конструктор в основном опреде-
ляет устойчивую структуру и поведение объекта, а также о том, что для
обозначения типа объекта удобно ссылаться на создавший его конструк-
тор. Но, к сожалению, в JavaScript не существует стандартного средства
для получения информации о конструкторе объекта. Это могло бы ока-
заться весьма полезным, поскольку при программировании сложных
сценариев с интенсивным применением объектно-ориентированного
подхода может возникнуть потребность в определении типов объектов,
созданных пользовательскими конструкторами, а также выявлении фак-
та принадлежности объекта к классу, являющемуся производным от
других классов, определенных в сценарии.
Ãëàâà 15. Приемы программирования на JavaScript 343
К слову, механизмы идентификации типа существуют и в языках с
жесткой типизацией. Так, компиляторы С++ поддерживают механизм
RTTI (Run-time type information), при использовании которого стано-
вится возможным определение принадлежности объекта к конкретно-
му типу по указателю на объект во время исполнения.
Далее в этой главе мы разработаем механизм осуществления наследова-
ния, в рамках которого станет возможным получение имени конструк-
тора по ссылке на объект. Мы также разработаем методы идентифика-
ции типа объектов.
Использование классических конструкций
объектно-ориентированного программирования
в JavaScript
В этом разделе мы обсудим возможности и проблемы применения раз-
личных конструкций и концепций, повсеместно используемых при про-
граммировании на объектно-ориентированных языках "классического"
типа (таких как C++) в JavaScript. Мы рассмотрим различные решения и
найдем аналоги многих таких конструкций.
Несколько слов о наследовании
Мы уже рассматривали схему осуществления наследования в JavaScript.
Теперь давайте посмотрим, каковы возможности наследования в данном
языке.
Как вы помните, наследование в JavaScript обычно осуществляется при
помощи установки функции-конструктору прототипичного объекта.
Ссылка на прототипичный объект должна быть помещена в свойство
prototype объекта функции-конструктора. Кроме того, конструктор
класса должен вызывать конструктор базового класса (функцию-
конструктор, создавшую объект, установленный в качестве прототипа
конструктору класса-наследника) для того, чтобы конструктор базового
класса мог создать свойства (и, возможно, методы), специфичные для
объектов этого класса.
Первое следствие подобной схемы наследования состоит в невозможно-
сти (в общем случае) осуществления множественного наследования. Это
прямо вытекает из того факта, что у любого объекта существует только
одно свойство prototype (для некоторой функции-конструктора мы мо-
жем установить только лишь один прототипичный объект).
344 Часть IV. Примеры разработки сложных скриптов
Вообще, на мой взгляд, схема наследования в JavaScript выглядит не
очень элегантно, как, впрочем, и многие другие вещи, касающиеся
реализации в нем объектного подхода. Это, видимо, является резуль-
татом следования разработчиками JavaScript некоторой общей кон-
цепции, обуславливающей простоту данного языка.
Принципиально, множественное наследование в JavaScript можно эму-
лировать. Для того чтобы объекты некоторого класса имели набор
свойств, являющийся объединением наборов свойств объектов несколь-
ких других (базовых) классов, достаточно произвести последовательный
вызов этих конструкторов из конструктора класса-наследника. При
этом, естественно, объекты, создаваемые таким конструктором, будут
иметь все методы, создаваемые в конструкторах базовых классов.
Сложнее дело обстоит с методами базовых классов, определяемых через
прототип. Поскольку объект-прототип может быть только один, изна-
чально будут наследоваться только методы (определенные через прото-
тип) класса данного объекта. Эту проблему можно решить, создав у
объекта-свойства prototype функции-конструктора класса-наследника
свойства, являющиеся ссылками на определенные через прототип мето-
ды родительских классов, не являющихся классами объекта, установ-
ленного в качестве прототипа.
Еще одна проблема, связанная с наследованием в JavaScript, состоит в
вызове реализаций методов базовых классов, переопределенных в клас-
се-наследнике. Для выявления проблемы рассмотрим небольшой пример
на C++ (приятной особенностью данного языка является прозрачное
наследование реализаций методов базовых классов и простая семантика
вызовов унаследованных методов).
Итак, далее приведена декларация двух классов: Class1 и Class2. Класс
Class2 наследуется от класса Class1 (класс Class1 является базовым
классом класса Class2):
class Class1
{
public:
void Method1();
};
class Class2 : public Class1
{
public:
void Method1();
};
Ãëàâà 15. Приемы программирования на JavaScript 345
Как видно, классы имеют один метод Method1. Реализация методов этих
классов может выглядеть, например, так:
void Class1::Method1() { }
void Class2::Method1() { }
Оба этих метода ничего не делают (но в нашем примере это не важно).
Существенно то, что, поскольку Class2 наследуется от класса Class1,
реализация метода Method1 класса Class2 переопределяет реализацию
метода Method1 класса Class1. Иными словами, если создать объект
класса Class2 и вызвать его метод Method1, то исполнится код, содер-
жащийся в теле функции Class2::Method1.
Class2 oClass2; // определение объекта oClass2 класса
oClass2.Method1(); // вызов метода Method1 объекта oClass2
Однако очень часто при решении практических задач требуется, чтобы
реализация метода класса-наследника вызывала реализацию того же
метода базового класса. На C++ это делается очень просто. Далее при-
веден листинг метода Method1 класса Class2, вызывающего метод
Method1 класса Class1:
void Class2::Method1()
{
Class1::Method1();
}
Теперь давайте рассмотрим аналогичную конструкцию на языке
JavaScript. Ниже приведен листинг двух функций-конструкторов Class1
и Class2, создающих объекты, имеющие метод Method1:
function Class1()
{
this.Method1 = function() { };
};
function Class2()
{
[Link] = Class1;
[Link]();
this.Method1 = function() { };
};
[Link] = new Class1();
Как видно, в качестве объекта-прототипа Class2 задается экземпляр
объекта, создаваемого функцией-конструктором Class1. Также функ-
346 Часть IV. Примеры разработки сложных скриптов
ция-конструктор Class2 вызывает функцию Class1 (конструктор объек-
тов базового типа). Так выглядит классическая схема наследования в
JavaScript. Теперь мы можем создать объект типа Class2 и вызвать его
метод Method1:
var oClass2 = new Class2();
oClass2.Method1();
Но, допустим, нам необходимо вызывать из кода метода Method1 объек-
тов, создаваемых функцией-конструктором Class2, метод Method1, на-
значаемый объектам в функции-конструкторе Class1. Как это сделать?
В JavaScript нет нотации для подобного действия. Мало того, в данном
случае это сделать просто невозможно. Дело в том, что, как вы помните,
функции в JavaScript также являются объектами. То есть при исполне-
нии следующего кода в функции-конструкторе фактически происходит
присваивание свойству Method1 инициализируемого объекта (в теле
функции его идентификатор this) объекта безымянной функции:
this.Method1 = function() { };
Поскольку функция-конструктор Class2 вызывает функцию-конструк-
тор Class1, свойству Method1 инициализируемого объекта сначала при-
сваивается функция, реализуемая в конструкторе Class1. Однако, далее,
в коде Class2 свойству Method1 присваивается другой объект-функция, и
у нас просто не остается ссылок на объект-функцию, созданную в кон-
структоре Class1 для того, чтобы ее вызвать.
Далее в этой главе мы реализуем алгоритм осуществления наследова-
ния, кроме всего прочего, решающий данную проблему.
Пространства имен
В языке C++ существует такое понятие, как "пространство имен"
(namespace). Пространство имен определяется при помощи ключевого
слова namespace и формирует локальную область видимости идентифи-
каторов. Бывают анонимные и именованные пространства имен. В про-
странстве имен могут определяться переменные, функции, классы,
и т. д. Разумное применение пространств имен улучшает структуриза-
цию и читабельность исходного кода. Рассмотрим небольшой фрагмент
кода на C++.
namespace SomeNamespace
{
int nVariable;
Ãëàâà 15. Приемы программирования на JavaScript 347
int SomeFunction()
{
return 10;
}
};
В данном примере создается пространство имен с именем
SomeNamespace. В нем объявляется переменная nVariable и реализуется
функция SomeFunction. Вызов функции SomeFunction может быть про-
изведен при помощи специальной нотации с указанием идентификатора,
содержащего ее пространства имен:
int n = SomeNamespace::SomeFunction();
При разработке достаточно больших проектов на JavaScript (например,
библиотек классов) может оказаться уместным применение подхода,
аналогичного использованию пространств имен в C++. Эмулировать
описание пространства имен в JavaScript можно при помощи именован-
ного объекта, создаваемого литерально. Так, рассмотренный только что
пример будет выглядеть на JavaScript следующим образом:
SomeNamespace =
{
nVariable : null,
SomeFunction : function()
{
return 10;
}
};
Вызов функции SomeFunction в данном случае будет производиться
в нотации доступа к свойству объекта:
var n = [Link]();
Следует заметить, что подобным образом вполне можно эмулировать
"вложенные" пространства имен.
Классы, инкапсулирующие классы
При разработке классов с достаточно сложной структурой рациональ-
ным может оказаться реализация некоторых частей их функционально-
сти в виде отдельных классов. При этом часто область применения объ-
ектов таких вспомогательных классов ограничивается исключительно
методами одного класса. В этом случае уместным представляется реали-
348 Часть IV. Примеры разработки сложных скриптов
зация вспомогательного класса в пространстве имен класса, часть функ-
циональности которого инкапсулируется этим вспомогательным клас-
сом.
Для определенности положим, что некоторый класс с идентификатором
Class1 должен инкапсулировать класс с идентификатором Class2. На
C++ подобное включение будет выглядеть следующим образом (декла-
рация класса Class1 включается в декларацию класса Class2):
class Class1
{
...
class Class2
{
...
};
...
};
В JavaScript же понятие декларации класса отсутствует. Но, поскольку,
как мы говорили, класс здесь определяется объектом функции-
конструктора (реализацией конструктора и свойствами его объекта, за-
данными через прототип), для инкапсуляции одного класса другим не-
обходимо создать объект функции-конструктора инкапсулируемого
класса как свойство объектов инкапсулирующего класса (в этом случае,
естественно, данный конструктор не будет находиться в глобальной об-
ласти видимости идентификаторов). Это можно сделать как в конструк-
торе инкапсулирующего класса, так и через его прототип. Рассмотрим
оба случая.
Далее приведен листинг функции-конструктора Class1, создающей у
инициализируемых им объектов свойство Class2, в свою очередь яв-
ляющееся ссылкой на функцию-конструктор (этот конструктор создает у
объектов свойство-метод Method1).
function Class1()
{
this.Class2 = function()
{
this.Method1 = function()
{
// код тела метода Method1
}
}
Ãëàâà 15. Приемы программирования на JavaScript 349
[Link] = function()
{
with(this)
{
var oClass2 = new Class2();
// использование объекта oClass2
}
}
};
Как видно из листинга, объекты класса Class1 будут иметь также метод
SomeMethod, в теле которого производится создание объекта класса
Class2, инкапсулируемого классом Class1. Обратите внимание, что,
поскольку конструктор Class2 находится в области видимости иденти-
фикаторов класса Class1, при создании объектов класса Class2 в мето-
дах класса Class1 не требуется указания идентификаторов пространств
имен для доступа к конструктору Class2.
Теперь рассмотрим пример, по сути, идентичный предыдущему. Однако
в нем определение класса Class2, а также методов классов Class1 и
Class2 производится через прототипы. Листинг кода примера приведен
далее.
function Class1()
{
};
[Link].Class2 = function()
{
}
[Link].Method1 = function()
{
// код тела метода Method1
}
[Link] = function()
{
with(this)
{
var oClass2 = new Class2();
// использование объекта oClass2
}
}
350 Часть IV. Примеры разработки сложных скриптов
Стоит сказать, что с точки зрения быстродействия (впрочем, на мой
взгляд, также и с точки зрения элегантности, структурированности и
удобочитаемости кода) определение инкапсулируемых методов и клас-
сов через прототип гораздо более эффективно, чем создание их в конст-
рукторе (это будет обсуждаться в следующем разделе).
Нюансы использования свойств объектов и вызовов
методов классов
В этом разделе мы обсудим некоторые особенности использования чле-
нов объектов (свойств и методов) в JavaScript. Эти особенности в основ-
ном определяются все теми же принципами реализации объектно-
ориентированного подхода в данном языке.
Начнем со свойств объектов. Про них можно сказать относительно
немного. Как уже отмечалось, свойства объектов, принадлежащих како-
му-либо классу, обычно создаются функцией-конструктором. Поэтому
при наследовании конструктор класса-наследника должен вызывать
конструктор базового класса (для того чтобы конструктор базового
класса создал специфические для этого класса свойства инициализируе-
мого объекта). Однако свойства можно определять через прототип объ-
екта функции-конструктора. Отличие данных свойств от свойств, созда-
ваемых в конструкторе, состоит в том, что они разделяются между все-
ми объектами, созданными этим конструктором. Аналогом таких
свойств могут являться статические переменные-члены классов в C++.
В статических свойствах класса удобно хранить неизменяемые данные
(различные константы, специфические для конкретного класса), уни-
кальные значения и т. д.
Методы объектов (как уже неоднократно упоминалось) могут либо соз-
даваться в конструкторе, либо задаваться через прототип функции-
конструктора. Принципиальных отличий в использовании методов обо-
их типов нет. Однако определение методов через прототип дает некото-
рые преимущества.
Дело в том, что при создании методов в функции-конструкторе произ-
водится, фактически, создание свойств (с идентификаторами методов)
инициализируемого объекта, значениями которых являются ссылки на
объекты анонимных функций, определенных в конструкторе литераль-
но. Таким образом, каждый раз при создании объекта конструктором
затрачивается время на создание его свойств, являющихся методами.
Также в данном случае, как и при любом присваивании переменной ли-
терально определенного объекта, производится копирование объектов
Ãëàâà 15. Приемы программирования на JavaScript 351
функций (у каждого объекта будут свои копии методов, создаваемых в
конструкторе), что приводит к неоправданно большому расходу памяти.
Методы класса же, задаваемые через прототип, создаются один раз, при
первом прогоне скрипта. И поскольку ссылки на объекты функций этих
методов хранятся в статических свойствах класса (свойствах объекта
prototype объекта функции-конструктора), для всех объектов, создан-
ных некоторой функцией-конструктором, будет существовать по одной
копии объектов-функций, являющихся методами, заданными через про-
тотип.
Другое замечание относительно методов объектов касается принципа их
вызова. В терминах C++ все методы объектов в JavaScript можно харак-
теризовать как виртуальные. Данное обстоятельство лучше всего пояс-
нить на примере. Допустим, существуют два класса — Class1 и Class2,
класс Class2 является наследником класса Class1. В классе Class1 оп-
ределены два метода — Method1 и Method2, причем метод Method1 про-
изводит вызов метода Method2. В классе Class2 реализация метода
Method2 переопределяется. При создании объекта класса Class2 и вызо-
ве его метода Method1 в C++ из данного метода будет вызван метод
Method2, реализация которого принадлежит классу Class1. В JavaScript
же вызовется реализация метода Method2, принадлежащая классу
Class2. Для того чтобы в C++ в данном случае вызывалась переопреде-
ленная реализация метода Method2, этот метод должен быть виртуаль-
ным (объявлен с помощью спецификатора virtual). В JavaScript же вы-
зов методов объектов всегда происходит подобным образом. Это явно
вытекает из принципа доступа к свойствам объектов (которые, в частно-
сти, могут являться и ссылками на методы) — производится поиск свой-
ства с указанным именем среди свойств объекта, затем, если поиск был
не успешным — среди свойств, определенных через прототип, далее —
среди свойств объекта-прототипа, среди свойств объекта-прототипа,
определенных через его прототип, и т. д. Таким образом, поиск свойств
и методов производится по всей иерархии наследования объектов по
направлению от объекта, доступ к свойству или методу которого произ-
веден к корню иерархии. Вследствие этого, при вызове одним методом
объекта другого, для которого в иерархии наследования существуют
переопределенные реализации, произойдет вызов той реализации, кото-
рая ближе к "вершине" иерархии.
Как видите, схема вызова переопределенных при наследовании методов
в JavaScript проста, понятна и предсказуема. Однако существует один
неявный момент, игнорирование которого вследствие невнимательности
может привести к ошибкам в реализации логики приложения, не за-
352 Часть IV. Примеры разработки сложных скриптов
метным с первого взгляда. Этот момент заключается в попытке пере-
определения метода, создаваемого в конструкторе базового класса, ме-
тодом, определяемым через прототип в классе-наследнике. Рассмотрим
пример.
Определим класс Class1, имеющий метод Method1, определяемый через
прототип, который вызывает метод Method2. Метод Method2 будет соз-
даваться в конструкторе Class1.
function Class1()
{
this.Method2 = function()
{
alert("Class1::Method2");
};
};
[Link].Method1 = function()
{
Method2();
};
Далее создадим класс Class2, являющийся наследником класса Class1,
и определим через прототип Class2 метод Method2, который, по идее,
должен переопределять реализацию метода Method2 класса Class1.
function Class2()
{
[Link] = Class1;
[Link]();
};
[Link] = new Class1();
[Link].Method2 = function()
{
alert("Class2::Method2");
};
Заметьте, что конструктор Class2, как и полагается, вызывает конструк-
тор базового класса Class1. Теперь создадим объект класса Class2 и
вызовем его метод Method1:
var oClass2 = new Class2();
oClass2.Method1();
Ãëàâà 15. Приемы программирования на JavaScript 353
Результатом исполнения данного кода будет являться вывод сообщения
с текстом "Class1::Method2", а не "Class2::Method2", как можно было
ожидать. Это, в общем-то, естественно, ведь метод Method2 класса
Class2 определен через прототип, а во время создания объекта класса
Class2 при вызове конструктора Class1 из конструктора Class2 у само-
го объекта конструктором Class1 создается метод Method2. И, как гово-
рилось ранее, методы, принадлежащие непосредственно объекту, имеют
больший приоритет при вызове, чем методы, определенные через про-
тотип конструктора данного объекта.
Из сказанного можно сделать простой вывод — при разработке иерар-
хии классов, во избежание ошибок, не стоит смешивать создание мето-
дов в конструкторах и определение их через прототипы.
Общие рекомендации относительно
объектно-ориентированного программирования
на JavaScript
В заключение теоретической части данной главы позволю себе дать не-
сколько простых советов по поводу эффективного применения объект-
но-ориентированного подхода при программировании на JavaScript.
1. Используя объектный подход при программировании на JavaScript,
старайтесь построить логику сценария так, чтобы исключить измене-
ние структуры (а значит, и семантики использования) объектов после
их создания. Это сделает работу сценария более прозрачной и пред-
сказуемой и позволит избежать многих труднообнаруживаемых
ошибок.
2. Создание объектов старайтесь производить в основном с помощью
конструкторов. Это улучшит структуризацию и ясность кода. Конст-
рукторы обычно не имеет смысла использовать для создания объек-
тов, которые при работе сценария должны присутствовать в единст-
венном числе (такие объекты лучше делать статическими, определяя
их литерально). Если объекты некоторого типа используются только
в пределах одного класса, можно реализовать конструктор объектов
данного типа как член этого класса.
3. При построении сложной системы классов (например, при разработ-
ке различных библиотек) имеет смысл делать функции-конструкторы
членами статических объектов, т. е. эмулировать использование про-
странств имен.
4. Не стоит смешивать создание методов в конструкторах и определе-
ние их через прототипы.
354 Часть IV. Примеры разработки сложных скриптов
5. В идеале лучше вообще отказаться от создания методов в конструк-
торах — желательно определять все методы через прототипы (при-
чины уже обсуждались ранее). В общем случае это повысит эффек-
тивность работы скрипта с точки зрения быстродействия и затрат
памяти.
Применение этих принципов позволяет улучшить как эффективность
разработки, так и эффективность самой работы клиентских сценариев.
Примеры эффективного
объектно-ориентированного
программирования на JavaScript
Ранее мы говорили о том, что применение некоторых подходов и эму-
лирование синтаксических конструкций, свойственных объектно-
ориентированным языкам на базе классов, может повысить эффектив-
ность создания сценариев на JavaScript. Также в разд. "Анализ модели
объектно-ориентированного программирования на JavaScript" ранее в
данной главе мы выделили несколько проблем, связанных с использова-
нием объектного подхода в данном языке, — проблема идентификации
типов объектов и проблема вызова базовых реализаций переопределен-
ных методов.
В этом разделе мы разработаем механизмы, решающие описанные про-
блемы. Мы создадим алгоритм осуществления наследования, дающий
возможность идентификации типа объектов и вызова базовых реализа-
ций переопределенных методов. Далее, применяя данный алгоритм, мы
разработаем небольшую библиотеку классов, включающую классы гра-
фических примитивов и класс таймера. Ее корневой класс будет инкап-
сулировать методы идентификации типа. На примере данной библиоте-
ки мы и изучим приемы эффективного объектно-ориентированного про-
граммирования на JavaScript.
Данная библиотека является основой библиотеки визуальных эле-
ментов управления пользовательского интерфейса JSVCL (JavaScript
Visual Controls Library), разработанной автором. Изначально планиро-
валось включить в состав книги полное описание данной библиотеки.
Однако описание слишком велико (оно увеличило бы объем рукописи
в полтора раза) и не достаточно познавательно с точки зрения общей
концепции книги. К тому же, библиотека JSVCL еще весьма "сырая" и
в ближайшем будущем должна претерпеть множество изменений. По-
Ãëàâà 15. Приемы программирования на JavaScript 355
этому в данной главе на представленных примерах мы разберем саму
суть построения клиентских сценариев такого рода. Однако, если вас
интересует тема разработки подобных проектов (к слову, библиотеки
элементов пользовательского интерфейса для Web-страниц в на-
стоящее время все чаще применяются при разработке сайтов, осо-
бенно совместно с технологией AJAX), вы можете получить более
подробную информацию о библиотеке JSVCL и загрузить ее исходный
код, посетив ресурсы [Link] или [Link]
Возможно, новой для вас окажется концепция слотов и сигналов, при-
меняемая в библиотеке для создания механизма синхронной обработки
событий (асинхронная обработка событий с созданием очереди событий
и последующей их диспетчеризацией пока отсутствует). Данная концеп-
ция и ее практическая реализация подробно описывается в разд. "Мо-
дель событий, сигналы и слоты" далее в этой главе. Исходные тексты
библиотеки расположены в каталоге examples\Lib\jsvcl на компакт-
диске.
Реализация алгоритма наследования
Ранее в разд. "Несколько слов о наследовании" мы выделили проблему,
связанную с вызовом реализаций методов базовых классов, переопреде-
ленных в классе-наследнике. Алгоритм осуществления наследования,
используемый в библиотеке JSVCL, призван решить данную (и не толь-
ко эту) проблему. Он реализуется функцией IMPLEMENT_INHERITANCE,
находящейся в файле examples\Lib\jsvcl\[Link]. Далее приведен ее лис-
тинг:
function IMPLEMENT_INHERITANCE(strClassName, strBaseClassName)
{
function GetNamedObject(strObjectName)
{
return (new Function("return " + strObjectName +
";")).call();
};
var fnClass = GetNamedObject(strClassName);
var fnBaseClass = GetNamedObject(strBaseClassName);
var oProto = new fnBaseClass();
for(var strProp in oProto)
{
var oProp = oProto[strProp];
356 Часть IV. Примеры разработки сложных скриптов
if(typeof(oProp) == "function")
fnBaseClass[strProp] = oProp;
}
[Link] = oProto;
[Link].__class = fnClass;
[Link].__base_class = fnBaseClass;
fnClass.__base_class = fnBaseClass;
fnClass.__class_name = strClassName;
fnBaseClass.__class_name = strBaseClassName;
};
В качестве параметров функции передаются две строки — имя класса-
наследника strClassName и имя его базового класса strBaseClassName.
При помощи функции GetNamedObject, определяемой в ее теле, функция
IMPLEMENT_INHERITANCE получает ссылки на функции-конструкторы с
именами strClassName и strBaseClassName (локальные переменные
fnClass и fnBaseClass соответственно). Далее при помощи вызова кон-
структора базового класса создается объект oProto, который потом бу-
дет назначен в качестве прототипа класса-наследника. Затем функция
IMPLEMENT_INHERITANCE производит свою основную работу — в цикле
for..in итерирует по свойствам созданного объекта и, если свойство
является функцией (т. е. методом объекта), создает свойство с данным
именем у функции-конструктора базового класса, присваивая ему ссыл-
ку на объект функции данного объекта. Таким образом, ссылки на все
методы объекта, создаваемого функцией-конструктором, определяющей
базовый класс, сохраняются как ее свойства. Затем, для функции-
конструктора класса-наследника устанавливается объект-прототип и
выполняются следующие действия:
через прототип создается свойство __class, являющееся ссылкой на
саму функцию-конструктор. Это сделано для того, чтобы из любого
метода объекта данного класса можно было получить ссылку на его
(объекта) конструктор;
через прототип создается свойство __base_class, являющееся ссыл-
кой на конструктор базового класса. Это сделано для того, чтобы из
любого метода объекта данного класса можно было получить ссылку
на конструктор базового класса;
создается свойство __base_class, являющееся ссылкой на конструк-
тор базового класса. Это будет применяться для динамической иден-
тификации типа (объяснения даны далее в этой главе);
Ãëàâà 15. Приемы программирования на JavaScript 357
создается свойство __class_name — строковое значение, содержащее
имя конструктора класса. Это будет применяться для динамической
идентификации типа.
Также у функции-конструктора базового класса создается свойство
__class_name, содержащее символическое имя этой функции (это необ-
ходимо для создания данного свойства у первого в цепочке наследова-
ния конструктора).
Теперь рассмотрим пример реализации наследования с использованием
функции IMPLEMENT_INHERITANCE, в котором из метода Method1 класса-
наследника Class2 вызывается реализация метода Method1 базового
класса Class1:
function Class1()
{
this.Method1 = function() { }
};
function Class2()
{
[Link](this);
this.Method1 = function()
{
[Link](this);
}
};
IMPLEMENT_INHERITANCE("Class2", "Class1");
Неправда ли, это выглядит достаточно просто. Как видите, в данном
примере для вызова конструктора базового класса, а также метода базо-
вого класса мы используем метод call объектов-функций. Первым па-
раметром в данный метод должна передаваться ссылка на объект, для
которого будет вызван данный метод. Далее должны следовать осталь-
ные параметры метода в обычном порядке.
Класс VSimpleObject — корневой класс
в иерархии наследования
Как следует из названия раздела, класс VSimpleObject является корнем
иерархии наследования в библиотеке JSVCL. Он не определяет свойств
объектов, однако инкапсулирует методы идентификации типа. Опреде-
358 Часть IV. Примеры разработки сложных скриптов
ление класса VSimpleObject содержится в файле examples\Lib\jsvcl
\[Link]. Далее приведен полный листинг кода данного класса.
function VSimpleObject()
{
};
[Link] = JSVCL;
[Link] = function()
{
return this.__class.__class_name;
}
[Link] = function(strClassName)
{
var fnClass = this.__class;
while(fnClass)
{
if(fnClass.__class_name == strClassName)
return true;
fnClass = fnClass.__base_class;
}
return false;
}
Как можно видеть из листинга, в качестве объекта-прототипа конструк-
тора VSimpleObject установлен объект JSVCL. Данный объект является
статическим и определяется литерально в скрипте [Link]. В данной реа-
лизации (представленной в книге) он не содержит ни свойств, ни мето-
дов.
Класс VSimpleObject имеет два метода для осуществления идентифика-
ции типа: GetClassName и IsKindOf (таким образом, все объекты, созда-
ваемые конструкторами классов библиотеки JSVCL, наследованными от
класса VSimpleObject, поддерживают идентификацию их типа во время
исполнения скрипта). Метод GetClassName возвращает символическое
имя класса объекта, используя свойство __class_name объекта функции-
конструктора класса, доступ к которому осуществляется через свойство
__class объекта. Метод IsKindOf возвращает true, если объект, для ко-
торого он вызван, принадлежит классу, либо класс объекта наследован
Ãëàâà 15. Приемы программирования на JavaScript 359
от класса, символическое имя которого передается методу в качестве
параметра strClassName. В противном случае метод возвращает false.
Метод IsKindOf получает ссылку на объект функции конструктора клас-
са объекта, а затем, используя свойство __base_class объектов функ-
ций-конструкторов, итерирует по их цепочке к корню иерархии насле-
дования, сравнивая при этом имена конструкторов, содержащиеся в их
свойствах __class_name со значением переданного методу параметра
strClassName. Если имя конструктора совпадает со значением парамет-
ра, метод IsKindOf возвращает true. Если же корень иерархии конст-
рукторов был достигнут, и не нашлось конструктора с заданным име-
нем, метод возвращает false.
Классы для работы
с геометрическими типами данных
В состав библиотеки JSVCL входят несколько классов, предназначен-
ных для оперирования геометрическими данными. Это классы VPoint,
VSize и VRect. Определения данных классов содержатся в файле
examples\Lib\jsvcl\[Link] на компакт-диске. Нам эти классы инте-
ресны, прежде всего, тем, что они наследуются непосредственно от
класса VSimpleObject, в реализации их методов часто применяется
идентификация типа, а на основе идентификации типа и анализа количе-
ства параметров эмулируется создание перегруженных методов класса.
В принципе, весь код конструкторов и методов классов VPoint, VSize и
VRect крайне прост. Методы лишь изменяют свойства объектов соответ-
ствующих классов, производя с ними простейшие арифметические опе-
рации. Поэтому здесь мы рассмотрим только конструктор класса VPoint
(конструкторы VSize и VRect реализованы аналогично), а также методы
SetPoint класса VPoint и Move класса VRect для иллюстрирования прин-
ципа эмулирования перегруженных методов класса при помощи анализа
количества и идентификации типа параметров.
Классы VPoint, VSize и VRect также инкапсулируют методы
toString, автоматически вызывающиеся при конвертировании объек-
тов данных классов в строку. Методы toString были введены в со-
став этих классов в основном по соображениям удобства вывода ди-
агностической информации, поэтому далее они упоминаться не будут.
360 Часть IV. Примеры разработки сложных скриптов
Класс VPoint
Класс VPoint предназначен для работы с данными точки в двухмерном
пространстве. Объекты данного класса хранят координаты точки в свой-
ствах x и y. В табл. 15.1 приведено описание методов класса VPoint
(конструктор также включен в это описание). Заметьте, что для некото-
рых методов в таблице приводятся несколько прототипов (аналог пере-
груженных методов), однако эти методы имеют одну реализацию.
Таблица 15.1. Методы класса VPoint
Прототип Описание
VPoint() Конструктор. Создает объект класса VPoint.
Свойства x и y инициализируются значением 0
VPoint(VPoint pt) Конструктор. Создает объект класса VPoint.
Свойства x и y инициализируются значениями,
соответствующими свойствам объекта pt, пере-
данного в качестве параметра
VPoint(number x, Конструктор. Создает объект класса VPoint.
number y) Свойства x и y инициализируются значениями x
и y, переданными в качестве параметров x и y
Copy() Возвращает объект-копию
SetPoint(VPoint pt) Устанавливает значения свойств x и y равными
значениям x и y объекта pt, переданного в каче-
стве параметра
SetPoint(number x, Устанавливает значения свойств x и y равными
number y) значениям x и y, переданным в качестве пара-
метров
Add(VSize szAdd) Производит "сложение" объекта класса VPoint с
объектом класса VSize, переданным в качестве
параметра szAdd. Увеличивает значения
свойств x и y на величины свойств cx и cy объ-
екта szAdd соответственно
Add(VPoint ptAdd) Производит "сложение" объекта класса VPoint с
объектом класса VPoint, переданным в качестве
параметра ptAdd. Увеличивает значения
свойств x и y на величины свойств x и y объекта
ptAdd соответственно
Add(number nAddX, Увеличивает значения свойств x и y на величи-
number nAddY) ны параметров nAddX и nAddY соответственно
Ãëàâà 15. Приемы программирования на JavaScript 361
Таблица 15.1 (окончание)
Прототип Описание
Sub(VSize szSub) Производит "вычитание" объекта класса VSize,
переданного в качестве параметра szSub из
объекта класса VPoint. Уменьшает значения
свойств x и y на величины свойств cx и cy объ-
екта szSub соответственно
Sub(VPoint ptSub) Производит "вычитание" объекта класса VPoint,
переданного в качестве параметра ptSub из
объекта класса VPoint. Уменьшает значения
свойств x и y на величины свойств x и y объекта
ptSub соответственно
Sub(number nSubX, Уменьшает значения свойств x и y на величины
number nSubY) параметров nSubX и nSubY соответственно
Теперь давайте разберемся, как работают конструктор и метод SetPoint
класса VPoint. Как следует из табл. 15.1, метод SetPoint может прини-
мать один или два параметра. В обоих случаях тип параметров заранее
известен. Вследствие этого метод SetPoint производит необходимые
действия, анализируя только количество переданных ему параметров.
Далее приведен листинг значимой части кода метода SetPoint.
if([Link] == 2)
{
// SetPoint(number x, number y)
x = arguments[0];
y = arguments[1];
}
else
{
// SetPoint(VPoint pt)
x = arguments[0].x;
y = arguments[0].y;
}
Конструктор VPoint также принимает переменное число параметров
(ноль, один или два). Он использует метод SetPoint для установки зна-
чений свойств инициализируемого объекта в случае передачи ему одно-
го или двух параметров. Далее приведен листинг конструктора VPoint.
function VPoint()
{
[Link](this);
362 Часть IV. Примеры разработки сложных скриптов
// default - VPoint()
this.x = 0;
this.y = 0;
with(this)
{
switch([Link])
{
case 2:
{
// VPoint(number x, number y)
SetPoint(arguments[0], arguments[1]);
break;
}
case 1:
{
// VPoint(VPoint pt)
SetPoint(arguments[0]);
break;
}
}
}
}
IMPLEMENT_INHERITANCE("VPoint", "VSimpleObject");
Обратите внимание — путем присваивания значений по умолчанию
конструктор создает свойства x и y инициализируемого объекта. Далее,
при необходимости, вызывается метод SetPoint (ему передаются пара-
метры, переданные самому конструктору). Метод SetPoint изменяет, но
не создает свойства x и y, поэтому, если бы они не были инициализиро-
ваны в конструкторе, при вызове SetPoint возникла бы ошибка.
Класс VSize
Объекты данного класса в свойствах cx и cy хранят геометрические раз-
меры. В данной реализации библиотеки JSVCL набор и прототипы ме-
тодов класса VSize очень схожи с набором и прототипами методов клас-
са VPoint. Однако семантика использования объектов данных классов в
реальных проектах сильно отличается. В табл. 15.2 приведено описание
методов класса VSize.
Ãëàâà 15. Приемы программирования на JavaScript 363
Таблица 15.2. Методы класса VSize
Прототип Описание
VSize() Конструктор. Создает объект класса VSize.
Свойства cx и cy инициализируются значением 0
VSize(VSize sz) Конструктор. Создает объект класса VSize.
Свойства cx и cy инициализируются значениями,
соответствующими свойствам объекта sz, пере-
данного в качестве параметра
VSize(number cx, Конструктор. Создает объект класса VSize.
number cy) Свойства cx и cy инициализируются значениями
cx и cy , переданными в качестве параметров
Copy() Возвращает объект-копию
SetSize(VSize sz) Устанавливает значения свойств cx и cy равны-
ми значениям cx и cy объекта sz, переданного в
качестве параметра
SetSize(number cx, Устанавливает значения свойств cx и cy равны-
number cy) ми значениям cx и cy, переданным в качестве
параметров
Add(VSize szAdd) Производит "сложение" объекта класса VSize с
объектом класса VSize, переданным в качестве
параметра szAdd. Увеличивает значения свойств
cx и cy на величины свойств cx и cy объекта
szAdd соответственно
Add(VPoint ptAdd) Производит "сложение" объекта класса VSize с
объектом класса VPoint, переданным в качестве
параметра ptAdd. Увеличивает значения свойств
cx и cy на величины свойств x и y объекта ptAdd
соответственно
Add(number nAddX, Увеличивает значения свойств cx и cy на вели-
number nAddY) чины параметров nAddX и nAddY соответственно
Sub(VSize szSub) Производит "вычитание" объекта класса VSize,
переданного в качестве параметра szSub из объ-
екта класса VSize. Уменьшает значения свойств
cx и cy на величины свойств cx и cy объекта
szSub соответственно
Sub(VPoint ptSub) Производит "вычитание" объекта класса VPoint,
переданного в качестве параметра ptSub из объ-
екта класса VSize. Уменьшает значения свойств
cx и cy на величины свойств x и y объекта ptSub
соответственно
364 Часть IV. Примеры разработки сложных скриптов
Таблица 15.2 (окончание)
Прототип Описание
Sub(number nSubX, Уменьшает значения свойств cx и cy на величи-
number nSubY) ны параметров nSubX и nSubY соответственно
Конструктор VSize устроен аналогично конструктору VPoint — он ис-
пользует метод SetSize для установки начальных значений свойств
инициализируемого объекта (см. код в файле [Link]). Реализация
методов Copy, Add и Sub также очень похожа на реализацию аналогич-
ных методов класса VPoint.
Класс VRect
Объекты класса VRect в свойства left, top, right, bottom хранят коор-
динаты, соответственно, левой, верхней, правой и нижней сторон пря-
моугольника. Методы, инкапсулируемые данным классом, предназначе-
ны для совершения трансформаций и получения геометрических пара-
метров прямоугольника. Описание методов класса VRect приведено в
табл. 15.3.
Таблица 15.3. Методы класса VRect
Прототип Описание
VRect() Конструктор. Создает объект класса VRect.
Все свойства инициализируются значени-
ем 0
VRect(VRect rc) Конструктор. Создает объект класса VRect.
Все свойства инициализируются соответст-
вующими значениями объекта rc, передан-
ного в качестве параметра
VRect( Конструктор. Создает объект класса VRect.
VPoint ptTopLeft, Левый верхний угол прямоугольника, опи-
VSize szWidthHeight) сываемого создаваемым объектом, будет
находиться в точке, определяемой объек-
том ptTopLeft. Размеры прямоугольника
задаются объектом szWidthHeight
VRect( Конструктор. Создает объект класса VRect.
VPoint ptTopLeft, Левый верхний угол прямоугольника, опи-
VPoint ptBottomRight) сываемого создаваемым объектом, будет
находиться в точке, определяемой объек-
том ptTopLeft, а правый нижний — в точке,
определяемой объектом ptBottomRight
Ãëàâà 15. Приемы программирования на JavaScript 365
Таблица 15.3 (продолжение)
Прототип Описание
VRect(number nLeft, Конструктор. Создает объект класса VRect.
number nTop, Координаты левой, верхней, правой и ниж-
number nRight, ней стороны прямоугольника, описываемого
number nBottom) создаваемым объектом, определяются,
соответственно, параметрами nLeft, nTop,
nRight, и nBottom
Copy() Возвращает объект-копию
SetRect(VRect rc) Устанавливает параметры прямоугольника.
Копирует значения свойств объекта rc в
соответствующие свойства объекта, для
которого вызывается метод
SetRect( Устанавливает параметры прямоугольника.
VPoint ptTopLeft, Объект ptTopLeft определяет координаты
VSize szWidthHeight) его левого верхнего угла, а объект
szWidthHeight — размеры
SetRect( Устанавливает параметры прямоугольника.
VPoint ptTopLeft, Объект ptTopLeft определяет координаты
VPoint ptBottomRight) его левого верхнего, а ptBottomRight —
правого нижнего угла
SetRect( Устанавливает параметры прямоугольника.
number nLeft, Параметры определяют координаты его
number nTop, левой, верхней, правой и нижней стороны
number nRight,
number nBottom)
Move(VPoint ptTo) Перемещает прямоугольник без изменения
его размеров. Новая координата его левого
верхнего угла задается объектом ptTo
Move(VSize szOffset) Перемещает прямоугольник без изменения
его размеров. Левый верхний угол прямо-
угольника смещается по осям x и y на рас-
стояние, заданное объектом szOffset
Move(number nOffsetX, Перемещает прямоугольник без изменения
number nOffsetY) его размеров. Левый верхний угол прямо-
угольника смещается по осям x и y на рас-
стояние, определяемое, соответственно,
параметрами nOffsetX и nOffsetY
Expand(VSize sz) Расширяет прямоугольник вправо, влево,
вверх и вниз. Значения расширения по го-
ризонтали и вертикали задаются значения-
ми свойств cx и cy объекта sz
366 Часть IV. Примеры разработки сложных скриптов
Таблица 15.3 (продолжение)
Прототип Описание
Expand(VRect rc) Расширяет прямоугольник. Значения рас-
ширения влево, вверх, вправо, вниз опре-
деляются свойствами left, top, right,
bottom, соответственно, объекта rc
Expand(number nGrow) Расширяет прямоугольник. Значение рас-
ширения во все стороны определяется па-
раметром nGrow
Expand(number nGrowX, Расширяет прямоугольник. Значения рас-
number nGrowY) ширения по горизонтали и вертикали опре-
деляются параметрами nGrowX и nGrowY
соответственно
Expand( Расширяет прямоугольник. Значения рас-
number nGrowLeft, ширения влево, вверх, вправо, вниз опре-
number nGrowTop, деляются, соответственно, параметрами
number nGrowRight, nGrowLeft, nGrowTop, nGrowRight,
number nGrowBottom) nGrowBottom
Contract(VSize sz) Сжимает прямоугольник вправо, влево,
вверх и вниз. Значения сжатия по горизон-
тали и вертикали задаются значениями
свойств cx и cy объекта sz
Contract(VRect rc) Сжимает прямоугольник. Значения сжатия
влево, вверх, вправо, вниз определяются
свойствами left, top, right, bottom, соот-
ветственно, объекта rc
Contract(number nClip) Сжимает прямоугольник. Значение сжатия
во все стороны определяется параметром
nClip
Contract( Сжимает прямоугольник. Значения сжатия
number nClipX, по горизонтали и вертикали определяются
number nClipY) параметрами nClipX и nClipY соответст-
венно
Contract( Сжимает прямоугольник. Значения сжатия
number nClipLeft, влево, вверх, вправо, вниз определяются,
number nClipTop, соответственно, параметрами nClipLeft,
number nClipRight, nClipTop, nClipRight, nClipBottom
number nClipBottom)
Size() Возвращает объект класса VSize, содер-
жащий геометрические размеры (длины
сторон) прямоугольника
Ãëàâà 15. Приемы программирования на JavaScript 367
Таблица 15.3 (окончание)
Прототип Описание
Width() Возвращает ширину прямоугольника
Height() Возвращает высоту прямоугольника
TopLeft() Возвращает объект класса VPoint, описы-
вающий точку левого верхнего угла прямо-
угольника
BottomRight() Возвращает объект класса VPoint, описы-
вающий точку правого нижнего угла прямо-
угольника
PtInRect(VPoint pt) Определяет, находится ли точка, опреде-
ляемая объектом pt, в области прямоуголь-
ника. Возвращает true, если точка нахо-
дится внутри прямоугольника, и false в
противном случае
Теперь давайте рассмотрим метод Move класса VRect. Он хорошо иллю-
стрирует принцип эмулирования перегрузки методов класса при помощи
анализа количества и идентификации типа переданных методу парамет-
ров. Как видно из табл. 15.3, метод Move может вызываться с одним или
двумя параметрами. В случае вызова метода с двумя параметрами нам
заведомо известен их тип — числа. Однако единственный параметр ме-
тода может являться как объектом класса VSize, так и объектом класса
VPoint. Заметьте, что в зависимости от типа единственного параметра
меняется поведение метода Move: если передан объект класса VSize,
производится смещение прямоугольника на заданную величину, а если
объект класса VPoint — перемещение прямоугольника в заданную точку
(координата левого верхнего угла станет равна координате, определяе-
мой объектом, передаваемым в качестве параметра). Далее приведен
листинг метода Move класса VRect.
[Link] = function()
{
with(this)
{
if([Link] == 1)
{
var oMove = arguments[0];
368 Часть IV. Примеры разработки сложных скриптов
if([Link]("VSize"))
{
// Move(VSize szOffset)
left += [Link];
top += [Link];
right += [Link];
bottom += [Link];
}
else
{
// Move(VPoint ptTo)
SetRect(oMove, Size());
}
}
else
{
// Move(number nOffsetX, number nOffsetY)
left += arguments[0];
top += arguments[1];
right += arguments[0];
bottom += arguments[1];
}
}
return this;
}
Как можно видеть, в методе производится анализ количества передан-
ных ему параметров. В случае, если методу передан один параметр,
производится также анализ типа объекта, переданного в качестве этого
параметра при помощи метода IsKindOf. Заметьте, как производится
перемещение прямоугольника в случае, если методу Move передан объ-
ект класса VPoint — используется метод SetRect, которому передается
объект, определяющий точку верхнего левого угла и объект, опреде-
ляющий размеры прямоугольника, полученный при помощи метода
Size.
На этом мы закончим рассмотрение классов VPoint, VSize и VRect. И в
дополнение ко всему сказанному, хотелось бы отметить, что, несмотря
на возможно кажущуюся избыточность данных классов, их применение
в проектах, требующих большого количества операций с геометриче-
скими данными (таких, например, как библиотеки элементов управления
для Web-страниц), весьма целесообразно. Оно поможет сильно сокра-
тить общий объем кода и избежать многих ошибок.
Ãëàâà 15. Приемы программирования на JavaScript 369
Класс VObject
Данный класс является наследником класса VSimpleObject. В нем реа-
лизованы механизмы синхронной обработки событий и идентификации
экземпляра объекта. Класс VObject может использоваться в качестве
базового для достаточно сложных классов (например, классов элемен-
тов управления), объекты которых должны иметь возможность взаимо-
действия с другими объектами, например, с целью уведомления о про-
изошедших в них изменениях. Код конструктора класса VObject выгля-
дит следующим образом:
function VObject()
{
this.m_nID = VObject.__GlobalID++;
this.m_aConnections = new Array();
}
IMPLEMENT_INHERITANCE("VObject", "VSimpleObject");
В конструкторе VObject происходит назначение объекту уникального
числового идентификатора. Числовой идентификатор объекта присваи-
вается его свойству m_nID в конструкторе VObject. Очередное значение
данного идентификатора хранится в виде свойства __GlobalID объекта
функции VObject. Оно наращивается при каждом ее вызове. Начальное
значение __GlobalID равно 0.
VObject.__GlobalID = 0;
Значение уникального идентификатора объекта возвращается методом
GetID класса VObject:
[Link] = function()
{
return this.m_nID;
}
Также класс VObject имеет метод GetName, возвращающий уникальное
символическое имя объекта, полученное путем конкатенации префикса
"V" со строковым представлением идентификатора объекта в шест-
надцатеричной системе счисления. Имя объекта может использоваться
для сохранения списка ссылок на объекты классов библиотеки JSVCL
в виде свойств некоторого объекта-контейнера.
[Link] = function()
{
return "V" + this.m_nID.toString(16);
}
370 Часть IV. Примеры разработки сложных скриптов
В конструкторе VObject также инициализируется свойство m_aConnections
значением пустого массива (см. следующий раздел).
Модель событий, сигналы и слоты
На данный момент механизм слотов и сигналов в библиотеке JSVCL
является основным средством обеспечения взаимодействий объектов,
имеющих характер событий. Он позволяет производить сопоставление
специальным образом определенных методов (сигналов) объектов клас-
сов, являющихся наследниками VObject любому количеству любых ме-
тодов (слотов) любых объектов. Ключевой момент данной концепции
состоит в том, что при вызове метода объекта, являющегося сигналом,
происходит последовательный вызов всех сопоставленных ему методов-
слотов объектов, которым принадлежат слоты (при этом всем слотам
передаются параметры, переданные методу-сигналу). Данный процесс
мы будем называть распространением сигнала.
Идея сигналов и слотов была позаимствована мной из объектно-
ориентированного мультиплатформенного пакета разработки графи-
ческого пользовательского интерфейса на C++ — Qt. Реализация
данной концепции в JSVCL во многом отличается от реализации ее в
Qt, однако основные принципы схожи.
Для наглядного иллюстрирования принципов работы механизма слотов
и сигналов далее приводится небольшой фрагмент кода.
function VObject_1()
{
[Link](this);
this.Method1 = function() { alert("VObject_1.Method1"); };
this.Method2 = function() { alert("VObject_1.Method2"); };
};
IMPLEMENT_INHERITANCE("VObject_1", "VObject");
function VObject_2()
{
[Link](this);
[Link]("test_signal"); // создание сигнала
this.Method1 = function() { alert("VObject_2.Method1"); };
};
IMPLEMENT_INHERITANCE("VObject_2", "VObject");
Ãëàâà 15. Приемы программирования на JavaScript 371
var oObject1 = new VObject_1();
var oObject2 = new VObject_2();
[Link]("test_signal", oObject1, "Method1");
[Link]("test_signal", oObject1, "Method1");
[Link]("test_signal", oObject1, "Method2");
[Link]("test_signal", oObject2, "Method1");
oObject2.test_signal(); // инициация распространения сигнала
В данном фрагменте кода (его вы можете также найти в файле примера
examples\15\slot_signal.html) объявляются два класса-наследника VObject:
VObject_1 и VObject_2. Класс VObject_1 имеет два метода: Method1 и
Method2, класс VObject_2 — один метод Method1. Кроме того, у объектов
класса VObject_2 путем вызова метода SIGNAL класса VObject создается
сигнал с именем test_signal. Затем в данном примере создаются два
объекта — oObject1 и oObject2 — классов VObject_1 и VObject_2 соот-
ветственно. Далее, при помощи вызова метода connect (метод класса
VObject) осуществляется связывание сигнала test_signal объекта
oObject2 с различными слотами. Обратите внимание, что связывание
сигнала test_signal со слотом Method1 объекта oObject1 производится
два раза. Также выполняется связывание сигнала test_signal объекта
oObject2 со слотом Method1 этого же объекта. Затем, вследствие вызова
метода test_signal объекта oObject2 происходит инициация распро-
странения данного сигнала. Вследствие выполнения данного кода на
экран будет последовательно выведено четыре окна сообщений с содер-
жимым: "VObject_1.Method1", "VObject_1.Method1", "VObject_1.Method2",
"VObject_2.Method1". То есть вследствие распространения сигнала вы-
зов слотов производится в той последовательности, в которой они были
связаны с сигналом, и столько раз, сколько раз они были связаны. Те-
перь давайте разберемся в деталях реализации этого механизма.
Итак, вы уже знаете, что создание сигнала производится при помощи
метода SIGNAL класса VObject. Его код очень прост:
[Link] = function(strSignal)
{
this[strSignal] = new Function("[Link](\"" +
strSignal + "\", arguments);");
this.m_aConnections[strSignal] = new [Link]();
}
У объекта, метод SIGNAL которого вызывается, создается метод с име-
нем, передаваемым в качестве параметра strSignal. Созданный метод-
372 Часть IV. Примеры разработки сложных скриптов
сигнал должен производить вызов метода Dispatch объекта, передавая
ему имя этого сигнала и массив аргументов arguments, переданных в
метод-сигнал при вызове. Также в свойство-массив m_aConnections объ-
екта добавляется элемент, строковый индекс которого равен имени сиг-
нала, а значением является объект класса Slots (он будет рассмотрен
далее).
Как мы уже видели, соединение сигнала со слотом производится при
помощи метода connect объекта, которому принадлежит сигнал. Суще-
ствует и обратная операция — метод disconnect, производящий удале-
ние связи сигнала со слотом. Далее приведен листинг этих двух ме-
тодов.
[Link] =
function(strSignal, oObject, strSlot)
{
with(this)
m_aConnections[strSignal].Insert(new Slot(oObject,
oObject[strSlot]));
}
[Link] =
function(strSignal, oObject, strSlot)
{
with(this)
m_aConnections[strSignal].Remove(new Slot(oObject,
oObject[strSlot]));
}
Оба метода принимают строковое имя сигнала, ссылку на объект, со-
держащий слот, и строковое имя слота в качестве первого, второго и
третьего параметров соответственно. Эти методы просто осуществляют
вызов методов Insert либо Remove объекта класса Slots, являющегося
элементом массива m_aConnections, доступ к которому производится по
строковому индексу — имени сигнала. Класс Slots и его методы рас-
сматриваются в следующем разделе.
Классы Slots и Slot, массивы слотов
Как вы уже наверно поняли, массив m_aConnections объектов класса
VObject и его наследников хранит информацию о связях сигналов объ-
екта с группами слотов. Для хранения информации о группе слотов, со-
ответствующих конкретному сигналу, используются объекты класса
Slots, являющиеся элементами массива m_aConnections. Фактически
Ãëàâà 15. Приемы программирования на JavaScript 373
класс Slots не является классом в том смысле, который мы вложили в
это понятие в начале данной главы. Конструктор Slots возвращает объ-
ект массива, расширенный методами Insert и Remove. Элементами тако-
го массива являются объекты класса Slot, содержащие информацию об
одном слоте некоторого объекта. Класс VObject агрегирует классы
Slots и Slot. Далее приводится листинг функции-конструктора объек-
тов класса Slots.
[Link] = function()
{
var oSlots = new Array();
[Link] = function(oSlot)
{
var nIndex = [Link];
for(var i = 0; i < [Link]; i++)
{
if(typeof(this[i]) == "undefined")
{
nIndex = i;
break;
}
}
this[nIndex] = oSlot;
}
[Link] = function(oSlot)
{
for(var i = 0; i < [Link]; i++)
{
if((typeof(this[i]) != "undefined") &&
(this[i].IsEqual(oSlot)))
{
delete this[i];
break;
}
}
}
return oSlots;
}
374 Часть IV. Примеры разработки сложных скриптов
Как видите, методы Insert и Remove объекта класса Slots просто произ-
водят, соответственно, добавление, либо удаление элементов массива
(которым является такой объект). Методу Insert передается ссылка на
объект класса Slot, который необходимо добавить в массив. Алгоритм
добавления элемента реализован таким образом, чтобы минимизировать
количество неиспользуемых элементов массива (перед добавлением
производится поиск элементов, не содержащих ссылки на объекты, и
если такой элемент найден, ссылка помещается в него, в противном слу-
чае в массив добавляется новый элемент). В метод Remove также переда-
ется ссылка на объект класса Slot. Метод Remove производит поиск ана-
логичного объекта среди элементов объекта-массива (используется ме-
тод IsEqual объектов-элементов), и, если аналогичный объект найден,
производится его удаление из массива. Далее приведен код определения
класса Slot (листинг его конструктора и метода IsEqual).
[Link] = function(oObject, fnSlot)
{
[Link] = oObject;
[Link] = fnSlot;
}
[Link] = function(oSlot)
{
return ([Link] == [Link]) &&
([Link] == [Link]);
}
Конструктор класса Slot принимает два параметра — ссылку на объект
(oObject) и ссылку на функцию — метод-слот этого объекта. Конструк-
тор просто помещает эти значения в свойства oObject и fnSlot созда-
ваемого объекта. Метод IsEqual объектов класса Slot принимает ссыл-
ку на некоторый объект класса Slot и возвращает true, если значения
свойств oObject и fnSlot переданного в качестве параметра объекта
равны значениям этих же свойств объекта, метод IsEqual которого вы-
зывается. То есть метод IsEqual предназначен для определения факта
идентичности объектов класса Slot.
Подведем некоторый итог сказанному ранее в виде небольшого списка
тезисов.
Каждый объект класса VObject имеет свойство — массив
m_aConnections.
Метод SIGNAL класса VObject принимает в качестве параметра стро-
ковое значение (имя сигнала), создает у объекта метод с данным
Ãëàâà 15. Приемы программирования на JavaScript 375
именем, а также добавляет в массив m_aConnections пару "ключ-
значение". Ключом является все то же имя сигнала, а значением —
объект класса Slots.
Объект класса Slots представляет собой массив. Для добавления
в него элементов используется его метод Insert, для удаления —
Remove. Элементами такого массива должны являться объекты класса
Slot.
Метод connect класса VObject принимает в качестве параметров имя
сигнала, ссылку на объект (со слотом которого надо соединить сиг-
нал) и имя слота. Метод connect получает ссылку на функцию-слот,
создает новый объект класса Slot, передавая его конструктору ссыл-
ку на объект и метод-слот, и, используя метод Insert, добавляет соз-
данный объект в один из массивов (являющихся объектами класса
Slots), хранящихся в массиве m_aConnections (индекс этого элемен-
та-массива соответствует имени сигнала, переданного в качестве па-
раметра в метод connect). Метод disconnect производит операцию,
обратную той, которую производит connect — удаляет один из объ-
ектов из одного из массивов-элементов массива m_aConnections.
Фактически имеем, что массив m_aConnections хранит соответствие
имен сигналов конкретного объекта спискам соединенных с данным
сигналом слотов.
Метод Dispatch класса VObject, распространение сигнала
Как мы видели ранее, создаваемый вследствие вызова метода SIGNAL
метод-сигнал производит лишь одно действие — вызывает метод
Dispatch объекта, которому принадлежит, передавая этому методу стро-
ковое имя сигнала (соответствует имени идентификатора метода-
сигнала) и массив переданных ему аргументов (arguments). Метод
Dispatch как раз и является "рабочей лошадкой", ответственной за рас-
пространение сигнала. Далее приведен листинг этого метода.
[Link] = function(strSignal, arrArgs)
{
with(this)
{
var arrSlots = m_aConnections[strSignal];
var nCount = [Link];
if(nCount)
{
var strBody = "[Link](obj";
376 Часть IV. Примеры разработки сложных скриптов
for(var i = 0; i < [Link]; i++)
strBody += ",a[" + [Link]() + "]";
strBody += ");";
var fnDisp = new Function("obj", "fnSlot",
"a", strBody);
for(var i = 0; i < nCount; i++)
{
if(typeof(arrSlots[i]) != "undefined")
fnDisp(arrSlots[i].oObject,
arrSlots[i].fnSlot, arrArgs);
}
}
}
}
Принцип действия метода Dispatch достаточно прост. Он получает
ссылку на массив объектов, хранящих информацию о слотах сигнала с
именем strSignal (переменная arrSlots) и, если в массиве есть элемен-
ты, конструирует прокси-функцию для вызова слотов с параметрами
(локальная переменная fnDisp), генерируя код ее тела, исходя из коли-
чества элементов массива аргументов слотов (параметр arrArgs). Затем
метод Dispatch перебирает все элементы массива arrSlots (объекты
класса Slot) и производит вызов прокси-функции fnDisp, передавая ей
ссылку на объект, содержащий слот, ссылку на функцию-слот (эти дан-
ные берутся из свойств объектов-элементов массива arrSlots) и массив
аргументов слотов arrArgs. А функция fnDisp, в свою очередь, вызыва-
ет слот объекта, на который были переданы ссылки, передавая слоту
параметры из массива arrArgs. Таким образом и производится распро-
странение сигнала в реализации модели слотов и сигналов библиотеки
JSVCL.
Класс таймера VTimer
Как упоминалось в предыдущем разделе, на основе класса VObject мож-
но разрабатывать классы, объекты которых должны иметь возможность
взаимодействия с другими объектами. Хорошим примером, иллюстри-
рующим использование функциональности класса VObject, может яв-
ляться класс таймера. Как вы знаете, ядро исполнения клиентских сце-
нариев браузеров предоставляет API для работы с таймерами. Так, "за-
пустить" таймер можно при помощи метода setTimeout объекта window.
Ãëàâà 15. Приемы программирования на JavaScript 377
Для обработки событий таймера должна быть реализована глобальная
функция-обработчик. Также необходимо сохранить идентификатор тай-
мера, возвращаемый методом setTimeout для того, чтобы впоследствии
иметь возможность "остановить" его при помощи вызова метода
clearTimeout. Однако при разработке скриптов с интенсивным приме-
нением объектно-ориентированного подхода использование различного
рода глобальных функций и данных весьма нежелательно. Поэтому для
осуществления прозрачной и гибкой работы с таймерами логично раз-
работать соответствующий класс.
Класс таймера в библиотеке JSVCL имеет имя VTimer. Он является пря-
мым наследником класса VObject. Использование функциональности
VTimer возможно посредством вызова нескольких его методов.
В табл. 15.4 приведено их описание.
Таблица 15.4. Описание методов класса VTimer
Прототип метода Описание
SetInterval(nInterval) Устанавливает интервал срабатывания тай-
мера равным значению, переданному в каче-
стве параметра nInterval (в миллисекун-
дах). По умолчанию интервал срабатывания
таймера равен 0
Start() Запускает таймер
Stop() Останавливает таймер
Reset(nInterval) Перезапускает таймер. Если передан пара-
метр nInterval, то таймер перезапускается
с интервалом срабатывания, равным значе-
нию nInterval
IsActive() Возвращает true, если таймер находится
в активном состоянии, т. е. запущен, и false
в противном случае
Обработка событий таймеров, реализуемых объектами класса VTimer,
осуществляется в рамках модели слотов и сигналов. Класс VTimer опре-
деляет сигнал tick, распространяющийся при каждом срабатывании
таймера. В этом плане класс VTimer предоставляет разработчику опре-
деленную гибкость, ведь теперь у одного таймера может быть не один
обработчик событий (им могут являться любое количество слотов лю-
бых объектов).
378 Часть IV. Примеры разработки сложных скриптов
Теперь давайте разберемся в деталях реализации класса VTimer. Ниже
приведен сокращенный листинг конструктора VTimer (исключены опре-
деления всех методов).
function VTimer()
{
[Link](this);
...
[Link]("tick");
this.m_nTimerID = 0;
this.m_nInterval = 0;
this.m_nTickCount = 0;
}
IMPLEMENT_INHERITANCE("VTimer", "VObject");
Как видите, кроме сигнала tick и описанных ранее методов, объекты
класса VTimer содержат несколько свойств. В свойстве m_nTimerID будет
сохраняться идентификатор таймера, возвращаемый методом
[Link]. Свойство m_nInterval устанавливается методом
SetInterval и используется как значение интервала задержки таймера,
передаваемое методу [Link] при запуске таймера. Свойст-
во m_nTickCount сохраняет количество срабатываний таймера, произо-
шедшее с момента его последнего запуска. Оно передается слотам, об-
рабатывающим сигнал tick.
Принцип, лежащий в основе функциональности, реализуемой классом
VTimer, состоит в создании временной функции-обработчика событий
таймера, инициализируемого методом [Link], из которого
производится вызов метода Tick объекта класса VTimer, инициализиро-
вавшего таймер. Создание такого обработчика и, собственно, запуск
таймера производится в методе Start. Далее приведен листинг методов
Start и Tick класса VTimer.
[Link] = function()
{
with(this)
{
if(!m_nTimerID)
{
var strName = GetName();
Ãëàâà 15. Приемы программирования на JavaScript 379
VTimer[strName] = this;
VTimer["on" + strName] = new Function(
"VTimer[\"" + strName + "\"].Tick();");
m_nTickCount = 0;
m_nTimerID = [Link](
VTimer["on" + strName], m_nInterval);
}
}
}
[Link] = function()
{
with(this)
{
if(m_nTimerID)
tick(++m_nTickCount);
}
}
Метод Start производит действия по запуску таймера только в том слу-
чае, если он еще не запущен (проверяется значение свойства
m_nTimerID). Для того чтобы при возникновении события таймера, ини-
циализированного в методе Start некоторого объекта класса VTimer,
иметь возможность делегировать управление методу Tick именно этого
объекта, в методе Start выполняются следующие действия:
1. У объекта-функции VTimer создается свойство с именем, соответст-
вующим уникальному имени объекта, запускающего таймер, воз-
вращаемым методом GetName.
2. Функция-обработчик событий таймера генерируется динамически.
В коде ее тела путем доступа к свойству объекта-функции VTimer
с именем, определенным в пункте 1, получается ссылка на нужный
объект класса VTimer и вызывается его метод Tick.
Метод Start запускает таймер, передавая методу [Link]
ссылку на сгенерированный обработчик. Таким образом, каждому тай-
меру, инициализируемому в методе Start, назначается свой обработчик,
"знающий" имя свойства объекта-функции VTimer, в котором хранится
ссылка на объект, методом Start которого был запущен данный таймер.
Перед запуском таймера свойству m_nTickCount присваивается значе-
ние 0. Также в свойстве m_nTimerID сохраняется значение идентифика-
тора запущенного таймера.
380 Часть IV. Примеры разработки сложных скриптов
Метод Stop класса VTimer производит остановку таймера, вызывая ме-
тод clearInterval объекта window. Он также удаляет свойства объекта-
функции VTimer, созданные методом Start, и обнуляет свойство
m_nTimerID. Метод Reset осуществляет перезапуск таймера путем по-
следовательного вызова методов Stop, SetInterval (в случае, если ме-
тоду передан параметр nInterval — новое значение интервала срабаты-
вания таймера) и Start.
Использовать функциональность, реализуемую VTimer, очень просто.
Достаточно создать объект класса VTimer, с помощью метода connect
соединить его сигнал tick со слотом некоторого объекта и вызвать ме-
тод Reset, передав ему значение интервала задержки, либо вызвать по-
следовательность методов SetInterval и Start. Пример использования
класса VTimer вы можете найти в файле examples\15\[Link] на прила-
гаемом к книге компакт-диске.
Заключение
В этой книге мы рассмотрели множество вопросов, связанных с про-
граммированием на JavaScript, начиная с общих принципов построения
Web-страниц, внедрения в них скриптов и работы с объектной моделью
документа, заканчивая реализацией редактора HTML и разработкой
библиотеки классов для эффективного объектно-ориентированного про-
граммирования. Я надеюсь, что книга оказалась полезной для вас. Так
что же дальше? Дальше я желаю вам успехов в дальнейшем совершен-
ствовании навыков программирования на JavaScript и освоении новых
технологий Web-разработки.
Напоследок хотелось бы заметить, что высказать свое мнение об этой
книге и задать вопросы относительно содержащихся в ней материалов
вы можете, посетив сайт издательства, расположенный по адресу
[Link] Текущие и новые версии большинства скриптов, описан-
ных в книге, можно будет найти на сайте [Link]
ПРИЛОЖЕНИЕ
Описание компакт-диска
На компакт-диске, прилагаемом к книге, содержатся файлы приложений
и примеров к ее главам, а также документация по языку гипертекстовой
разметки HTML, каскадным таблицам стилей первого и второго уров-
ней, а также объектным моделям документа первого, второго и третьего
уровней.
В папке addons содержатся файлы приложений к некоторым главам.
В папке examples содержатся примеры к главам 4—15 книги:
в папке examples\04 находятся примеры главы 4;
в папке examples\05 находятся примеры главы 5;
в папке examples\06 находятся примеры главы 6;
в папке examples\07 находятся примеры главы 7;
в папке examples\08 находятся примеры главы 8;
в папке examples\09 находятся примеры главы 9;
в папке examples\10 находятся примеры главы 10;
в папке examples\11 находятся примеры главы 11;
в папке examples\12 находятся примеры главы 12;
в папке examples\13 находятся примеры главы 13;
в папке examples\14 находятся примеры главы 14;
в папке examples\15 находятся примеры главы 15;
в папке examples\Lib находятся файлы библиотек, для работы с
cookie, регулярными выражениями, датой/временем, для динами-
ческой генерации изображений;
Описание компакт-диска 383
в каталоге examples\Lib\jsvcl содержится объектно-ориентиро-
ванная библиотека JSVCL, разработанная в рамках пятнадцатой
главы.
В папке w3c находится документация по HTML, CSS и DOM:
в папке w3c\css размещены спецификации CSS1 и CSS2:
в папке w3c\css\css2 в подкаталогах eng и rus находятся, соот-
ветственно, английская и русская версии спецификаций CSS2;
в папке w3c\html находятся спецификации HTML 4.0 и 4.01:
в папке w3c\html\4.0 — спецификация HTML 4.0;
в папке w3c\html\4.01 в подкаталогах eng и rus находятся, со-
ответственно, английская и русская версии спецификаций
HTML 4.01;
в папке w3c\dom находятся спецификации объектных моделей до-
кумента первого, второго и третьего уровней:
в папке w3c\dom\dom1 — спецификация объектной модели
документа первого уровня;
в папке w3c\dom\dom2 — спецификация объектной модели
документа второго уровня;
в папке w3c\dom\dom3 — спецификация объектной модели
документа третьего уровня.
Предметный указатель
C работа с изображениями 261
регулярное выражение 171
Cookie 147
N
D
Namespace 346
Document Object Model (DOM)
80
Document Type Definition (DTD) S
49 Standardized Generalized Markup
Dynamic HTML (DHTML) 81 Language (SGML) 45
E U
Escape-код 26 Uniform Resource Locator (URL)
16, 188
H Universal Character Set (UCS)
50
HyperText Markup Language Universal Resource Identifier
(HTML) 45 (URI) 54
J
JavaScript:
версия 124
Предметный указатель 385
А Д
Анимация 277 Дата/время, конвертирование
бегущая строка 280 в строку 139
волнообразный движущийся
текст 284, 310
движение светящихся шаров
И
313 Идентификатор 10, 11
движение текста Изображение 254
в 3D-пространстве 292 изменение прозрачности 311
движение фонового рисунка Инструкция 18
294 пустая 18
летящие звезды 294 Интерфейс 83
появление и исчезновение Исключение 41
текста 278
прилетающий по частям
текст 290
К
снег 301 Каскадная таблица стилей 60,
текст, движущий на 97
наблюдателя 280 альтернативная 64
фейерверк 298 визуальные эффекты 74
часы 303 внедренная в документ 63
Атрибут элемента HTML 48 внешняя 64
встроенная 103
Б курсор мыши 78
описание 62
Бегущая строка 280 постоянная 65
Браузер:
предпочитаемая 64
заголовок окна 213
цвет 75
окно:
шрифт 76
задание параметров 205
Кэш в оперативной памяти
изменение размеров 210
263
перемещение 210
Класс 336, 339
полосы прокрутки 215
базовый 339
строка состояния 213
Кнопка 226
Коллекция images 257
В Комментарий:
Визуальный редактор HTML CSS1 и CSS2 67
320 HTML 48
Выражение 11 JavaScript 18
386 Предметный указатель
Конвертирование: О
в boolean 24
в число 26 Обработка события 107
Константа 20 Обработчик события 15, 107
определение 21 Объект 29, 337
Конструктор класса 339 body 95
Контроль данных на стороне Date 134
клиента 218 document 93, 206, 213
Кэш файловый 263 Image 256
Кэширование изображений 262 navigator 153
RegExp 171
Л String 175
style 103
Литерал 10 window 91, 206
объекта 30 [Link] 126
строковый 26 [Link] 132
функции 43 класса 339
числовой 25 создание 30
Объектная модель:
М браузера 80
документа 80
Массив 27 Оператор 11
Метка 40
break 40
Метод:
catch 42
класса 340
continue 40
объекта 30
Модель: delete 28, 29
визуального форматирования finally 42
74 if...else 37
представления документа new 27, 31, 257
в виде блоков 73 switch 37
Модель обработки событий 107 throw 41
DOM уровня 2 117 try 42
базовая 108 typeof 22, 337
специфическая 114 составной 19
цикла 38
do...while 39
Н for 38
Набор правил 66 for...in 39
Наследование 33, 344 while 39
Предметный указатель 387
Операторный блок 19 Селектор 68
Определение типа документа атрибутов 70
49 дочерних элементов 70
потомков 69
П простой 69
сестринских элементов 70
Переключатель 229 Символ:
Переменная 20, 337
; 18
глобальная 22
пробельный 17
локальная 21
языка 10
область видимости 21 Слово:
объявление 21 зарезервированное 10, 19
Правила "at" 66
ключевое 10
Правило 66
Событие 107
каскада 72
Список 231
Пространство имен 346
выпадающий 231
Протокол 188
Ссылка 54
javascript 195
на внешнюю таблицу стилей
mailto 189
65
Псевдокласс 71
на символ 51
Псевдоэлемент 70
Строка 26
Структура документа HTML 49
Р
Радиокнопка 229 Т
Регистрозависимость 17
Регулярное выражение 163 Таблица HTML 56
захватывающие скобки 166 Тег:
незахватывающие скобки HTML 47
166 <SCRIPT> 14
примеры шаблонов 179 Текстовое поле 225
проверки электронного Тип данных 22
адреса 186 null 23
символы 164 undefined 23
флаг 163, 170 логический 24
шаблон 163, 164 числовой 25
простой 164
У
С Универсальный набор
Свойство объекта 29 символов 50
388 Предметный указатель
Ф Функция 43
вызов 44
Фильтр 305 определение 43
Alpha 311
Fade 311
Light 313 Ч
Wave 310 Часы 303
Флажок 229
Форма 193, 217
HTML 57 Я
обратной связи 249, 250
Фрейм 58 Якорь 54