Вы находитесь на странице: 1из 436

Елена Бенкен

Геннадий Самков

Санкт-Петербург
«БХВ-Петербург»
2009
УДК 681.3.06
ББК 32.973.26-018.2
Б46
Бенкен, Е. С.
Б46 AJAX: программирование для Интернета / Е. С. Бенкен, Г. А. Самков. —
СПб.: БХВ-Петербург, 2009. — 464 с.: ил. + (СD-ROM)
ISBN 978-5-9775-0428-7
Описана технология AJAX и показаны возможности, которые открываются перед разра-
ботчиком с ее применением. Рассмотрена объектная модель документа: DOM в JavaSript
и DOM-функции в PHP. Изложены основы языка XML и формат JSON. Показан принцип ге-
нерации асинхронных запросов к серверу средствами JavaScript. Сделан обзор основных
JavaScript-библиотек: Prototype, Scriptaculous, ExtJS и jQuery. Подробно рассмотрены попу-
лярные и перспективные библиотеки ExtJS и jQuery: описана объектная модель языка
JavaScript, на которой базируются эти библиотеки; применение AJAX-запросов; обработка
событий и др. Приведено большое количество практических примеров. Компакт-диск со-
держит дистрибутивы Web-сервера, модуля PHP и сервера MySQL, исходные коды описы-
ваемых библиотек, распространяемых на основании лицензии GPL, а также примеры из книги.
Для Web-программистов
УДК 681.3.06
ББК 32.973.26-018.2

Группа подготовки издания:


Главный редактор Екатерина Кондукова
Зам. главного редактора Игорь Шишигин
Зав. редакцией Григорий Добин
Редактор Анна Кузьмина
Компьютерная верстка Натальи Караваевой
Корректор Виктория Пиотровская
Дизайн обложки Инны Тачиной
Оформление обложки Елены Беляевой
Зав. производством Николай Тверских

Лицензия ИД № 02429 от 24.07.00. Подписано в печать 04.03.09.


Формат 70×1001/16. Печать офсетная. Усл. печ. л. 37,41.
Тираж 2000 экз. Заказ №
"БХВ-Петербург", 190005, Санкт-Петербург, Измайловский пр., 29.
Санитарно-эпидемиологическое заключение на продукцию
№ 77.99.60.953.Д.003650.04.08 от 14.04.2008 г. выдано Федеральной службой
по надзору в сфере защиты прав потребителей и благополучия человека.
Отпечатано с готовых диапозитивов
в ГУП "Типография "Наука"
199034, Санкт-Петербург, 9 линия, 12

ISBN 978-5-9775-0428-7 © Бенкен Е. С., Самков Г. А., 2009


© Оформление, издательство "БХВ-Петербург", 2009
Оглавление

Введение .................................................................................................................. 3
Терминология .......................................................................................................... 3
Структура книги ...................................................................................................... 5
Как работать с книгой ............................................................................................. 6
Источники информации ......................................................................................... 7
Благодарности ......................................................................................................... 7
ЧАСТЬ I. ТЕХНОЛОГИИ, СОСТАВЛЯЮЩИЕ AJAX ................................ 9
Глава 1. Принцип работы AJAX ...................................................................... 11
Глава 2. Объектно-ориентированное программирование
в серверных приложениях ................................................................. 14
Принципы объектно-ориентированного программирования............................ 14
Объектная модель в PHP 5. Классы и объекты .................................................. 15
Конструктор класса ............................................................................................... 16
Создание объекта .................................................................................................. 17
Деструктор объекта ............................................................................................... 17
Копирование и клонирование объектов.............................................................. 19
Наследование ......................................................................................................... 20
Финальные классы ................................................................................................ 22
Доступ к свойствам и методам класса ................................................................ 24
Статические свойства и методы класса .............................................................. 27
Абстрактные классы и интерфейсы .................................................................... 28
Константа класса ................................................................................................... 29
Ключевое слово instanceof.................................................................................... 30
Обработка ошибок ................................................................................................ 30
Автозагрузка класса .............................................................................................. 32
Итераторы: просмотр всех общедоступных свойств объекта .......................... 33
Синглетон............................................................................................................... 34
IV Оглавление
Глава 3. Объектно-ориентированное программирование
в JavaScript ........................................................................................... 35
Создание объекта с помощью оператора new .................................................... 36
Создание объектов с помощью объектных литералов ...................................... 36
Конструктор объекта ............................................................................................ 37
Функции как объекты ........................................................................................... 38
Добавление методов при помощи прототипа ..................................................... 38
Наследование при помощи прототипа ................................................................ 40
Создание класса-наследника ................................................................................ 41
Полиморфизм ........................................................................................................ 42
Частные элементы классов ................................................................................... 43
Пространства имен ................................................................................................ 44
Обработка ошибок ................................................................................................ 45
Синглетоны ............................................................................................................ 46
Замыкания .............................................................................................................. 47
Применение замыканий .................................................................................... 48
Глава 4. XML и JSON ......................................................................................... 51
Язык XML .............................................................................................................. 51
Синтаксис XML. Правильно оформленный XML.......................................... 51
XML-декларация................................................................................................ 53
Атрибуты ............................................................................................................ 53
Комментарии ...................................................................................................... 53
Процессуальная инструкция ............................................................................. 55
Пространства имен XML .................................................................................. 55
Особые символы ................................................................................................ 56
CDATA ............................................................................................................... 57
JSON ....................................................................................................................... 58
Глава 5. Объектная модель документа ........................................................... 61
Объект Node ........................................................................................................... 64
Свойства и методы объекта Document ................................................................ 65
Доступ к узлу DOM ........................................................................................... 66
Объект Element ...................................................................................................... 66
Объект NodeList ..................................................................................................... 67
Объект NamedNodeMap ........................................................................................ 67
Объект Attr ............................................................................................................. 68
Объект Text ............................................................................................................ 68
Объект DOMImplementation ................................................................................. 68
Оглавление V

Глава 6. DOM в JavaScript ................................................................................ 69


Объект Element ...................................................................................................... 69
Создание HTML-элемента с помощью методов DOM и включение его
в дерево документа................................................................................................ 71
Чтение данных из XML-документа ..................................................................... 73
Глава 7. DOM-функции в PHP ......................................................................... 75
Создание XML-документа с помощью DOM-функций..................................... 76
Глава 8. Проблема русификации Web-приложений .................................... 81
Кодировки .............................................................................................................. 81
Передача локализованных данных в протоколе HTTP ..................................... 83
Кодирование символов в сценарии JavaScript.................................................... 85
Русский язык в PHP............................................................................................... 86
Локализация MySQL ............................................................................................. 89
ЧАСТЬ II. СОЗДАНИЕ AJAX-ПРИЛОЖЕНИЙ .......................................... 91
Глава 9. Объект XMLHttpRequest ..................................................................... 93
Глава 10. Использование XML и создание периодических запросов ..... 101
Создание периодических запросов.................................................................... 108
Глава 11. Запрос данных с сервера MySQL ................................................. 112
Передача данных в формате JSON .................................................................... 118
ЧАСТЬ III. БИБЛИОТЕКИ ДЛЯ РАБОТЫ С AJAX ................................ 125
Глава 12. Обзор библиотек для создания AJAX-приложений.................. 127
Глава 13. Библиотека Prototype ..................................................................... 131
Полезные методы в Prototype ............................................................................. 132
Класс Element....................................................................................................... 135
Класс Array .......................................................................................................... 137
AJAX в Prototype ................................................................................................. 141
Класс Ajax.Request ........................................................................................... 142
Класс Ajax.Response ......................................................................................... 143
Класс Ajax.Updater .......................................................................................... 144
Класс Ajax.PeriodicalUpdater.......................................................................... 145
Использование AJAX-запросов в Prototype .................................................. 145
VI Оглавление
Глава 14. Библиотека script.aculo.us.............................................................. 149
Эффекты ............................................................................................................... 150
Перетаскивание и сортировка (Draggable & Sortable) ..................................... 153
AJAX в script.aculo.us ......................................................................................... 157
Автодополнение............................................................................................... 157
Класс Ajax.InPlaceEditor ................................................................................. 161

ЧАСТЬ IV. БИБЛИОТЕКА EXTJS ............................................................... 165


Глава 15. Структура и идеология библиотеки ............................................ 167
Соглашения об именах ....................................................................................... 169
Конфигурирование ExtJS и первый пример применения................................ 169
Объект Ext.Element .......................................................................................... 171
Firebug — запаситесь выжигателем жучков ..................................................... 172
Контекст ........................................................................................................... 172
Задание контекста в ExtJS .................................................................................. 173
Адаптеры и пространство имен ......................................................................... 175
Механизм наследования в ExtJS ........................................................................ 175
Вызов метода базового класса ........................................................................... 176
Обработка событий в ExtJS ................................................................................ 177
События DOM .................................................................................................. 177
События JavaScript .......................................................................................... 177
Пользовательские события ............................................................................. 179
Xtypes ................................................................................................................... 180
Классы ExtJS ........................................................................................................ 181
Класс Component .............................................................................................. 181
Класс BoxComponent........................................................................................ 183
Класс Container ................................................................................................ 184
Класс Panel ....................................................................................................... 184
Компоновка (layout) ............................................................................................ 184
Глава 16. Поиск элементов: класс DomQuery .............................................. 187
Выбор узлов DOM ............................................................................................... 187
Селекторы элементов ...................................................................................... 188
Селекторы атрибутов ...................................................................................... 188
Отбор элементов CSS Value selectors ............................................................ 188
Глава 17. Панели и компоновка элементов ................................................. 196
Простая панель .................................................................................................... 196
Вложенные панели .............................................................................................. 198
Оглавление VII

Компоновка панелей: создание аккордеона ..................................................... 199


Панель с несколькими вкладками ..................................................................... 203
Глава 18. Формы ............................................................................................... 209
Создание элемента формы ................................................................................. 209
Компоновка формы ............................................................................................. 210
Передача данных формы на сервер методом submit ........................................ 213
Проверка форм с помощью класса VTypes. Календарь-подсказка ................. 217
Глава 19. Визуальные эффекты. Drag & drop ............................................. 222
Свертывание и развертывание блока ................................................................ 222
Изменение размеров блока ................................................................................. 226
Drag & drop .......................................................................................................... 229
Глава 20. Простые виджеты............................................................................ 236
Всплывающие подсказки.................................................................................... 236
Глава 21. Создание редактируемых таблиц................................................. 240
Создание базы данных ........................................................................................ 240
Серверный сценарий для запроса к базе и генерации ответа клиенту........... 241
Клиентская часть: HTML и сценарий JavaScript.............................................. 243
Разработка динамически редактируемой таблицы .......................................... 249
ЧАСТЬ V. jQuery ............................................................................................... 259
Глава 22. Знакомство с jQuery ....................................................................... 261
Установка библиотеки ........................................................................................ 262
Что такое $()? ....................................................................................................... 263
Глава 23. Функции ядра jQuery ..................................................................... 265
Доступ к объекту jQuery ..................................................................................... 270
Глава 24. Селекторы jQuery ........................................................................... 275
Базовые селекторы .............................................................................................. 275
Иерархические селекторы .................................................................................. 280
Основные фильтры.............................................................................................. 286
Фильтры содержимого........................................................................................ 298
Фильтры видимых и невидимых элементов ..................................................... 302
Фильтры атрибутов ............................................................................................. 306
Фильтры потомков .............................................................................................. 316
Селекторы в формах ........................................................................................... 323
Фильтры состояния элементов форм ................................................................ 326
VIII Оглавление
Глава 25. События в jQuery ............................................................................ 331
Помощники при работе с событиями................................................................ 332
Глава 26. Манипуляции элементами в jQuery ............................................ 352
Изменение содержимого элементов .................................................................. 352
Вставка содержимого внутрь элементов .......................................................... 358
Вставка содержимого снаружи элементов ....................................................... 364
Обертывание элементов ..................................................................................... 369
Замещение, удаление, копирование элементов ................................................ 371
Глава 27. AJAX-запросы в jQuery ................................................................. 378
Загрузка содержимого ........................................................................................ 378
Реализация GET-запросов................................................................................... 385
Реализация POST-запросов................................................................................. 392
Полный контроль над AJAX-запросами ........................................................... 395
Глава 28. События AJAX в jQuery ................................................................ 402
Глава 29. Расширения для jQuery ................................................................. 410
Плагин jQuery Form ............................................................................................ 410
Плагин Live Query ............................................................................................... 416
Резюме .................................................................................................................. 420
ПРИЛОЖЕНИЯ ................................................................................................ 421
Приложение 1. Установка Web-сервера Apache, модуля PHP 5
и сервера MySQL в Windows .............................................. 423
Установка сервера Apache .................................................................................. 423
Директивы конфигурации Apache ................................................................. 425
Установка модуля PHP ....................................................................................... 426
Установка сервера MySQL 5 .............................................................................. 428
Приложение 2. Отладка JavaScript. Использование Firebug ................... 431
Выполнение и отладка кода JavaScript ............................................................. 432
Просмотр HTTP-заголовков и AJAX-запросов ................................................ 434
Приложение 3. Описание компакт-диска..................................................... 436
Литература ......................................................................................................... 437
Предметный указатель .................................................................................... 439
Не секрет, что встречаются профессионалы-практики, которые на бумаге не
могут структурированно изложить свои знания и опыт, и есть те, кто пишет
много книг, но не обладает реальным практическим опытом. К счастью,
авторы книги не относятся ни к первым, ни ко вторым. В Елене Сергеевне
сочетается профессиональный опыт и умение донести этот опыт до аудито-
рии. Как и предыдущие книги автора, эту отличает от многих других то, что
изложенный материал апробирован на сотнях слушателей самых разных
компаний, которые проходили обучение у Елены Сергеевны. Для лучшего
закрепления материала, в книге приведены различные практические задания
для самостоятельной работы.
Мы считаем, что книга является качественным продуктом и заслуживает то-
го, чтобы ее прочитали те, кого интересует данная область. Как и многие
другие книги, написанные нашими преподавателями, данная книга выходит
в серии "Avalon.ru — советуют профессионалы".
Опыт предыдущих книг показал, как важен контакт между тем, кто написал
книгу, и тем, кто ее изучает. Книга не позволяет наладить диалог между ав-
тором и читателем, однако, для успешного восприятия материала, важно
иметь возможность задать преподавателю вопрос и получить квалифициро-
ванный ответ. Если после прочтения книги у вас появились вопросы, на ко-
торые вы не нашли ответа, если вы хотите получить еще больше практиче-
ских заданий, чтобы повысить свою квалификацию, если вы просто хотите
сказать спасибо автору данной книги, вы можете сделать это на форуме на-
шего учебного центра в соответствующем разделе: http://forums.avalon.ru.
На ваши вопросы ответят лично авторы книги.

Проректор Санкт-Петербургского государственного


политехнического университета
Александр Витальевич Речинский

Декан факультета переподготовки


специалистов СПбГПУ
Щукин Александр Валентинович
Введение

Предлагаемая книга является учебником в полном смысле этого слова, не


больше, но и не меньше.
Предполагается, что читатель знаком с основами JavaScript на среднем уров-
не, но ему не приходилось вдаваться в детали объектной модели этого языка.
Потребуются и знания основ PHP, но изложение объектной модели этого
языка приведено.
Кроме того, кратко излагаются основы XML. Затем рассказывается, как
именно создается AJAX-запрос к серверу и как на клиентской стороне обра-
ботать ответ сервера.
Большое внимание в настоящей книге уделено библиотекам JavaScript, по-
зволяющим упростить и ускорить процесс создания Web-приложений, глав-
ным образом рассматриваются библиотеки jQuery и ExtJS.

Терминология
В 2005 г. появился термин, обозначивший основное направление развития
Интернета — Web 2.0. Как часто случается в информационных технологиях,
термин есть, а точного определения нет. Но явление, которое он обозначил,
все-таки можно охарактеризовать некоторыми общими чертами. Главный
принцип Web 2.0 — усиление технологий за счет коллективного разума.
Ярким примером этого служит проект Wikipedia (http://wikipedia.org).
С другого ресурса, Google, началась популярность еще одной важной состав-
ляющей понятия Web 2.0 — AJAX.
AJAX (Asynchronous JavaScript and XML, асинхронный JavaScript и XML) —
это подход к построению пользовательских интерфейсов Web-приложений,
при котором данные, запрошенные пользователем, отображаются на уже за-
груженной странице. При этом не происходит полной перезагрузки страни-
цы, обновляется только ее часть. Использование AJAX стало наиболее попу-
лярно после того, как Google начала активно использовать его при создании
4 Введение

сайтов, таких как Gmail и Google Maps, а также Google Suggest — технологии
автозаполнения строки поискового запроса на основе общей статистики са-
мых популярных запросов.
AJAX в настоящее время пользуется популярностью горячих пирожков в хо-
лодный день. Правда, при ближайшем рассмотрении многие выясняют, что
разработчику Web-приложений с использованием AJAX требуется такой
объем знаний, что эта модная технология приближается по стоимости к су-
ши, а то и бутербродам с черной икрой. Многие авторы отмечают, что при-
менение термина "технология" по отношению к AJAX не является коррект-
ным: ведь он представляет собой просто совокупность нескольких ранее
известных технологий.
Для освоения AJAX важно хорошо разбираться в объектной модели доку-
мента — DOM (Document Object Model). Тема это обширная, местами зануд-
ная, а потому непопулярная среди авторов учебников по Web-технологиям.
Но именно описанию и примерам использования DOM уделено особое вни-
мание в данной книге.
Второй важный компонент AJAX — язык XML. Его в наше время надо знать
любому Web-мастеру. Понимание XML коренным образом изменяет видение
современного Интернета, да и в целом информационных технологий.
Правда, следует сказать, что упоминание XML именно в аббревиатуре AJAX
в настоящее время в значительной степени стало архаикой, там, где простой
текст недостаточен, XML все больше вытесняется форматом JSON.
Но главной составляющей AJAX все-таки является JavaScript, и данная книга
рассчитана на читателя, знакомого с основами этого языка.
Лучшей же доступной русскоязычному читателю книгой по JavaScript явля-
1
ется книга Д. Флэнагана "JavaScript. Подробное руководство" — вот где,
в частности, много примеров применения DOM в JavaScript.
Конечно, необходимо и знание CSS.
Кроме того, читателю потребуется знание языка PHP 5. Лучшей книги для
изучения PHP, чем книга Л. Веллинга, Л. Томсон "Разработка Web-приложений
2
с помощью PHP и MySQL" , предложить трудно. Хотя дочитывать ее до кон-
ца для изучения AJAX и необязательно. Правда, бросить ее из-за скуки вам
не удастся!

1
Флэнаган Д. JavaScript. Подробное руководство. // Пер. с англ. — СПб.: Символ-плюс, 2008.
2
Веллинг Л., Томсон Л. Разработка Web-приложений с помощью PHP и MySQL. — М.: Виль-
ямс, 2007.
Введение 5

Наконец, надо знать хотя бы основы работы с MySQL. Укажем только люби-
мую книгу Л. Аткинсона "MySQL. Библиотека профессионала"3. Опять же
там рассказано гораздо больше, чем потребуется для изучения AJAX.

Структура книги
Книга содержит пять частей и три приложения.
В части I представлен обзор технологий, составляющих AJAX:
принцип работы AJAX;
объектно-ориентированное программирование в интернет-приложениях.
Объектная модель PHP 5, работа с объектами в JavaScript;
объектная модель документа DOM, DOM в JavaScript и PHP;
основы XML и работа с форматом JSON;
обработка XML-документов с помощью DOM-функций в PHP;
проблемы русификации Web-приложений, использующих JavaScript,
XML, PHP и MySQL.
В части II изложены основы AJAX:
объект XMLHTTPRequest;
примеры создания Web-приложений: создание, отправка и обработка ре-
зультатов запроса;
генерация данных в форматах XML и JSON на сервере, обработка таких
данных на клиентской машине;
создание повторяющихся запросов, особенности разработки AJAX-
приложений;
запрос данных с сервера MySQL.
В части III обсуждаются библиотеки для работы с Ajax, в том числе:
приведен обзор библиотек для ускорения разработки Web-приложений;
библиотека Prototype: основы и примеры использования;
Scriptaculous: создание визуальных эффектов и развитие возможностей
библиотеки Prototype;
библиотека Jquery: все, что вы хотели узнать об этой библиотеке;
библиотека ExtJS, принципы построения и примеры применения.

3
Аткинсон Л. MySQL. Библиотека профессионала. — М.: Вильямс, 2002.
6 Введение

В части IV излагаются основы работы с библиотекой ExtJS:


архитектура и идеология библиотеки ExtJS;
реализация объектной модели в ExtJS;
генерация элементов Web-страницы и компоновка элементов с помощью
ExtJS;
создание элементов интерфейса с помощью ExtJS;
применение AJAX-запросов в ExtJS для получения данных с сервера;
создание редактируемой таблицы.
Часть V содержит описание библиотеки jQuery, а именно:
установка jQuery;
функции ядра jQuery, доступ к объекту jQuery;
селекторы jQuery;
обработка событий в jQuery;
манипуляции элементами в jQuery;
AJAX-запросы в jQuery;
глобальные и локальные события AJAX в jQuery;
расширения для jQuery, плагины jQuery Form и Live Query.
В приложении 1 обсуждается порядок установки сервера Apache и модуля
PHP в операционной системе Windows.
В приложении 2 кратко рассматривается Firebug — отладчик для Web-
приложений, работающий с Mozilla Firefox.
Приложение 3 — это описание компакт-диска, приложенного к книге.

Как работать с книгой


Книга ориентирована на разработчика, располагающего компьютером с опе-
рационной системой Windows, но пользователь UNIX также сможет выпол-
нить на своем компьютере все примеры.
В ходе чтения следует выполнять на компьютере примеры, описываемые в книге.
Каждый пример стоит изменять и переделывать самому с тем, чтобы лучше по-
нимать, как он работает или как сделать так, чтобы все перестало работать.
Авторы приложили все усилия, чтобы изложить материал с наибольшей точ-
ностью, но не исключают возможности ошибок и опечаток. Авторы также
не несут ответственности за последствия использования сведений, изложен-
ных в книге.
Введение 7

Источники информации
Как известно, в книге нельзя охватить все вопросы, и читателю нужно иметь
возможность получить дополнительные сведения, например, из Интернета.
Вот адреса, которыми вы можете воспользоваться:
http://apache.org;
http://php.net;
http://dev.mysql.com;
http://w3schools.com — школы W3C по XML;
http://www.w3.org/ — содержит всеобъемлющую информацию по стан-
дартам Интернета;
http://json.org;
http://developer.mozilla.org.
Можно рекомендовать следующие русскоязычные сайты:
http://phpclub.ru;
http://opennent.ru — этот сайт в основном посвящен операционным сис-
темам, в первую очередь семейству UNIX, но посмотрите внимательно —
здесь прекрасные статьи, отслеживаются все новости и даются ссылки на
оригиналы и переводы статей;
http://phpworld.ru — сайт посвящен PHP 5;
http://xml.nsu.ru — переводы на русский язык школ консорциума W3C по
XML;
http://zvon.org — учебник по XML, XSLT.

Благодарности
Авторы приносят свои благодарности Василию Руже за советы, позволившие
увидеть логику развития технологии.
Часть I
Технологии,
составляющие AJAX
Глава 1

Принцип работы AJAX

Идея AJAX (Asynchronous Javascript and XML) была изложена Джесси Гар-
реттом в его статье "AJAX: новый подход к Web-приложениям" (см.
http://www.adaptivepath.com/ideas/essays/archives/000385.php, русский пе-
ревод — http://ajax-development.narod.ru/ajax-article.html).
Web-приложения — это приложения, функциональные возможности которых
обеспечиваются сервером и доставляются пользователям по Интернету или
интрасети. Классическая модель Web-приложения действует следующим об-
разом. Клиентское приложение отправляет на сервер HTTP-запрос. Сервер
производит необходимую обработку: считывает и обрабатывает данные,
взаимодействует с различными системами, например, с базами данных или
другими серверами, и затем выдает HTML-страницу клиенту (рис. 1.1). Стра-
ница может содержать таблицы CSS и сценарии JavaScript.
Существенным недостатком такого алгоритма взаимодействия клиента с сер-
вером является то, что клиенту приходится ждать загрузки каждой после-
дующей страницы.
Суть идеи Гарретта состоит в том, что ожидание клиента сокращается или
становится совсем незаметным за счет нескольких усовершенствований
(рис. 1.2).

Запрос HTTP Сервер

Браузер Обработка запроса

Данные HTML + CSS Хранимые данные

Рис. 1.1. Классическая схема работы Web-приложения


12 Часть I. Технологии, составляющие AJAX

Браузер
Событие JavaScript Запрос HTTP Сервер
Интерфейс Сценарий Обработка запроса
пользователя JavaScript

HTML + CSS Данные XML Хранимые данные

Рис. 1.2. AJAX-модель Web-приложения

AJAX перераспределяет нагрузку между клиентом и сервером, разрешив им


общаться между собой, пока пользователь работает со страницей. Клиент
загружает в браузер страницу, содержащую сценарий JavaScript. Этот сцена-
рий включает в себя функции обработки событий, которые генерируют
HTTP-запрос на сервер. Запрос отправляется незаметно для пользователя.
В то время, когда запрос обрабатывается на сервере и происходит передача
ответа клиенту, последний продолжает работу, не ожидая полной перезагруз-
ки страницы. Клиентский сценарий отслеживает состояние этого запроса, и,
как только все данные, загруженные в качестве ответа сервера, получены
браузером, происходит обновление части Web-страницы, уже отображаемой
в окне браузера. Такое обновление происходит в результате работы того же
сценария JavaScript, который обрабатывает данные, полученные от сервера,
и отражает их в определенном фрагменте Web-страницы.
Ответ сервера в AJAX может представлять собой простой текст, текст
в XML-формате, в честь которого и добавлена последняя буква в название
технологии AJAX, или в формате JSON, который следует признать наиболее
удобным для многих Web-приложений.
Основной выигрыш в скорости работы получается за счет того, что запрос
к серверу отправляется незаметно для клиента, который продолжает работу,
не дожидаясь ответа сервера. Данные же ответа встраиваются в имеющуюся
на клиентской стороне страницу.
Итак, AJAX объединяет:
стандартизованное представление данных с использованием XHTML
и CSS;
динамическое отображение и обработку данных на стороне клиента в сце-
нарии JavaScript при помощи Document Object Model;
асинхронное получение данных с использованием объекта XMLHttpRequest,
создаваемого сценарием JavaScript;
обмен данными XML или данными в других текстовых форматах.
Глава 1. Принцип работы AJAX 13

AJAX не является чудесным средством, одним своим появлением украшаю-


щим Web-приложения, но разумное использование этой технологии может
сделать сайты более дружественными к пользователю. AJAX часто приме-
няют для решения следующих задач:
проверка правильности заполнения форм с привлечением возможностей
сервера;
подсказки для автодополнения;
создание динамических таблиц данных (girds), которые на лету обновляют
базы данных на сервере;
разработка приложений, которые требуют обновления информации в ре-
жиме реального времени, получая ее из различных источников.
Применение AJAX создает определенные трудности, а именно:
динамически создаваемые страницы могут иметь один и тот же адрес, по-
этому не работает кнопка Назад, предоставляющая пользователям воз-
можность вернуться к просмотренным ранее страницам;
изменение содержимого страницы при постоянном адресе приводит к то-
му, что сделать закладку на странице непросто;
поисковые машины не могут проиндексировать все страницы сайта, соз-
данного на основе AJAX;
на клиентской стороне JavaScript может быть отключен, в результате
AJAX-приложения перестанут работать.
Глава 2

Объектно-ориентированное
программирование
в серверных приложениях
Принципы объектно-ориентированного
программирования
В реальной жизни мы повседневно имеем дело с объектами. Любые предме-
ты, окружающие нас, можно представлять как объекты, характеризующиеся
некоторыми свойствами. Кроме того, объекты могут совершать действия или
над ними можно производить какие-либо действия.
Понятие объекта в программировании предоставляет программисту возмож-
ность оперировать данными как объектами реальной жизни. Объект пред-
ставляет собой совокупность свойств и операций, выполняемых объектом
или над объектом. Операции принято называть методами.
Объектно-ориентированное программирование базируется на трех основных
принципах: инкапсуляции, наследовании и полиморфизме.
Инкапсуляция подразумевает обращение с объектом как с черным ящиком:
при работе с объектом не видно его внутреннего устройства. Объект дает
возможность составить о нем представление по ограниченному числу дос-
тупных свойств и методов. Инкапсуляция как раз и означает, что объект
представляет собой совокупность данных и операций с ними. Инкапсуля-
ция — это механизм защиты свойств и операций объекта, ограждающий их
от неправильного использования. Данные внутри объекта могут быть дос-
тупны только для других частей этого объекта, но закрыты от программ, на-
ходящихся вне этого объекта.
Полиморфизм позволяет использовать одно и то же имя для решения разных
задач. Иначе говоря, одно и то же имя метода для разных объектов может
означать разные действия.
Глава 2. Объектно-ориентированное программирование в серверных приложениях 15

Наследование — это процесс, позволяющий объекту приобретать свойства


другого объекта. В процессе наследования объект может получить свойства
объекта-родителя, добавив к ним свои особенности.
Объектная модель представляет собой удобный инструмент для программи-
ста, ибо позволяет разделять работу над проектом между несколькими участ-
никами. Каждый работает над своим объектом, над его интерфейсами, при
этом минимально пересекаясь с другими программистами. Этот подход
обеспечивает также наилучшие условия для повторного использования соз-
данного кода.
В данной главе будут рассмотрены основные черты объектных моделей язы-
ков PHP и JavaScript. Читатель сможет увидеть сходства и отличия объектов
и операций с ними в этих языках. При построении объектной модели PHP
за образец была взята широко распространенная классовая модель, реализо-
ванная в C или Java. В JavaScript объекты наследуются иным способом — на
основе прототипов. В JavaScript нет классов как таковых, но все задачи объ-
ектного программирования, тем не менее, решаются. Понимание этих осо-
бенностей понадобится читателю в тех главах нашей книги, где обсуждаются
библиотеки JavaScript, применяемые при разработке Web-приложений
с использованием AJAX-запросов.

Объектная модель в PHP 5.


Классы и объекты
Объектная модель PHP базируется на понятии класса. Класс
определяет со-
вокупность свойств, которые имеет объект данного класса, а также и опера-
ций (методов), выполняемых над объектом.
Представьте себе класс млекопитающих. Принадлежность животного к этому
классу определяется по наличию у животного некоторых характеристик —
свойств. Например, кровь у млекопитающих теплая, при этом все млекопи-
тающие могут совершать действия, например, двигаться или кормить дете-
нышей молоком.
На языке программирования свойство или атрибут — это переменная,
имеющая некоторое значение. Действие, совершаемое объектом, — это
функция. Мы можем объявить несколько свойств:
public $blood, $legs;

Ключевое слово public указывает, что после него идет объявление элемента
класса (т. е. свойства или метода). Кроме того, это ключевое слово определяет
16 Часть I. Технологии, составляющие AJAX

механизм доступа к элементу, о чем пойдет речь далее в этой главе. Дадим
свойству значение:
$blood="теплая";

Определим метод move(), который принимает один аргумент $legs — коли-


чество лап у животного:
public function move($legs)
{
if ($legs) echo "$this->name двигается на $legs ногах <br>";
else echo "Животное плавает";
}

Значение переменной $legs надо указать при вызове метода — ничего


необычного в этом нет, так мы делали при вызове функции.
Специальный указатель $this применяется для обозначения объекта. Анало-
гично в процедурном программировании в определении функции указывает-
ся формальный аргумент. В приведенном здесь фрагменте кода распечатыва-
ется значение свойства name объекта, для которого вызывается метод move().
Можно сказать, что класс млекопитающих характеризуется совокупностью
свойств и методов. Какое-либо животное, относящееся к этому классу, будет
обладать этими свойствами и методами, но возможно, что методы будут реа-
лизовываться по-разному (ног-то может быть разное количество).

Конструктор класса
При создании объекта — экземпляра класса вызывается функция, которая
инициализирует все требуемые переменные, выполнит все действия, нуж-
ные для полного определения объекта. Эта функция называется конструк-
тором.
В PHP 5 конструктор — это метод, имеющий зарезервированное имя
__construct, и может быть определен так:
function __construct($name)
{
$this->name = $name;
$this ->blood='теплая';
echo "Запущен конструктор класса mammal <br>";
}

Свойства класса name и blood получают значения при вызове конструктора.


Глава 2. Объектно-ориентированное программирование в серверных приложениях 17

Создание объекта
Объект — экземпляр класса, который можно создать с помощью оператора
new, после которого указывается имя класса и параметры, передаваемые кон-
структору:
$cat = new mammal("кошка");

При создании объекта $cat для него устанавливаются значения свойств. По-
лучить доступ к ним можно так:
echo $cat->name;

Вызвать метод объекта $cat можно таким образом:


$cat->move(4);

Напишем определение класса mammal (листинг 2.1).


Листинг 2.1. Класс mammal
<?php
class mammal
{
public $blood, $legs;
public function __construct($name)
{
$this->name = $name;
$this->blood="теплая";
echo "Запущен конструктор класса mammal <br>";
}
public function move($legs)
{
if ($legs) echo "$this->name двигается на $legs ногах <br>";
else echo "Животное плавает";
}
}
?>

Деструктор объекта
Объект можно уничтожить в ходе выполнения сценария, вызвав функцию
unset() и передав ей в качестве параметра имя объекта. Но в любом случае
при завершении работы сценария память, занимаемая объектом, высвобож-
18 Часть I. Технологии, составляющие AJAX

дается, и объект из нее удаляется. В объектной модели PHP 5 определена


функция __destruct(), которая вызывается автоматически при уничтожении
объекта.
В листинге 2.2 вы можете увидеть определение конструктора и деструктора
класса, а также создание объекта как экземпляра созданного класса.

Листинг 2.2. Класс mammal и создание объекта


<?php
class mammal
{
public $blood, $legs;
public function __construct($name)
{
$this->name = $name;
$this->blood="теплая";
echo "Запущен конструктор класса mammal <br>";
}
public function move($legs)
{
if ($legs) echo "$this->name двигается на $legs ногах <br>";
else echo "Животное плавает";
}
function __destruct() {
echo "Вызван деструктор объекта <br>";
}
}
$cat = new mammal("кошка");
echo $cat->name."<br>";
$cat->move(4);
unset($cat);
echo "А теперь завершается работа сценария";
?>

При выполнении этого примера обратите внимание на то, что деструктор вы-
полняется именно при вызове функции unset(). Если же вы удалите из сце-
нария строку с этой функцией, то деструктор будет вызван в самом конце
работы после выполнения всех остальных операторов. Деструктор — это
подходящее место для действий, которые наводят порядок после выполнения
различных работ: закрывают соединения с серверами управления базами
данных, очищают память для предотвращения утечек памяти и т. п.
Глава 2. Объектно-ориентированное программирование в серверных приложениях 19

Копирование и клонирование объектов


При копировании объектов не происходит копирование данных — создается
только ссылка на область данных так же, как при создании ссылки на пере-
менную.
Попробуем создать два объекта класса млекопитающих — кошку и кита. Да-
дим кошке 4 лапы, а про кита скажем — нет у него лап ($legs=0). Посмот-
рим, что получилось (листинг 2.3).
Листинг 2.3. Копирование объектов
<?php
class simple_mammal
{
public $legs;
}
$cat = new simple_mammal;
$cat -> legs = 4;
$whale = $cat;
$whale -> legs = 0;
echo $cat -> legs;
echo $whale -> legs;
?>

А ничего хорошего не вышло! Поскольку при копировании объекта не копи-


ровалась область данных, то и у кошки лап не оказалось.
Чтобы скопировать свойства и методы объекта, надо применить клонирова-
ние (листинг 2.4).
Листинг 2.4. Клонирование объектов
<?php
class mammal
{
public $legs;
}
$cat = new mammal;
$cat -> legs = 4;
$whale = clone $cat;
20 Часть I. Технологии, составляющие AJAX
$whale -> legs = 0;
echo $cat -> legs;
echo $whale -> legs;
?>

Вот теперь все работает так, как задумывалось. Последние примеры призва-
ны пояснить отличительные черты работы с объектами в PHP 5: при созда-
нии копии объекта с помощью оператора присваивания ($whale=$cat) созда-
ется ссылка на объект $cat, а не копия всех свойств и методов объекта $cat.

Наследование
Можно определить класс beast, являющийся наследником ранее определен-
ного класса mammal:
class beast extends mammal

Класс-наследник может наследовать свойства и методы класса-родителя,


а может их переопределить (это называется перегрузкой):
public $fur; // Объявляем новое свойство

Метод move() перегрузим:


function move($legs)
{
if ($legs) echo "$this->name бегает, лазает по деревьям на ".
$legs." лапах <br>";
}

Создадим новый метод, присущий только этому классу-наследнику:


function description()
{
$this->fur="мягкая и пушистая";
echo $this->name, " ", $this->fur, " . ";
echo "Кровь - ", $this->blood, "<br>";
}

Конструктор родительского класса не вызывается автоматически при созда-


нии объекта — экземпляра класса-наследника. Его следует вызвать явно, ис-
пользуя символ двойного двоеточия. Конструктор класса beast определяется
следующим образом:
function __construct($name)
{
Глава 2. Объектно-ориентированное программирование в серверных приложениях 21

parent::__construct($name);
echo "Запущен конструктор класса beast <br>";
}

В итоге определение класса beast выглядит так, как показано в листин-


ге 2.5.
Листинг 2.5. Класс beast
<?php
class mammal{}
class beast extends mammal
{
public $fur;
function __construct($name)
{
parent::__construct($name);
echo "запущен конструктор класса beast <br>";
}
function move($legs)
{
if ($legs) echo "$this->name бегает, лазает по деревьям на ".
$legs." лапах <br>";
}
function description()
{
$this->fur="мягкая и пушистая";
echo $this->name, " ", $this->fur, " . ";
echo "Кровь - ", $this->blood, "<br>";
}
}
?>

Можем теперь создать объекты класса-наследника и посмотреть, как это все


работает:
$Murka = new beast("кошка");
$Murka-> move(4);
$Murka->description();
22 Часть I. Технологии, составляющие AJAX

Финальные классы
Теперь мы хотим создать класс cat — потомок класса beast. Класс cat явля-
ется также потомком класса mammal. Мы хотим указать, что у этого класса
cat никаких наследников быть не может. Для этого используется ключевое
слово final:
final class cat extends beast

Из-за применения этого ключевого слова попытки создать классы-наследники


класса cat будут вызывать сообщение об ошибке.
Объявим новое свойство $sound и определим его значение в конструкторе:
public $sound;
function __construct($name)
{
parent::__construct($name);
echo "Запущен конструктор класса cat <br>";
$this->sound="мурр";
}

Объявим и новый метод:


function speak()
{
echo $this->name, " говорит ", $this->sound."<br>";
}

Объявление класса и создание объекта, экземпляра этого класса, будет вы-


глядеть так, как показано в листинге 2.6.
Листинг 2.6. Класс cat
<?php
class mammal
{
public $blood, $legs;
public function __construct($name)
{
$this->name = $name;
$this->blood="теплая";
echo "Запущен конструктор класса mammal <br>";
}
Глава 2. Объектно-ориентированное программирование в серверных приложениях 23

public function move($legs)


{
if ($legs) echo "$this->name двигается на $legs ногах <br>";
else echo "Животное плавает";
}
function __destruct() {
echo "Вызван деструктор объекта <br>";
}
}
class beast extends mammal
{
public $fur;
function __construct($name)
{
parent::__construct($name);
echo "запущен конструктор класса beast <br>";
}
function move($legs)
{
if ($legs) echo "$this->name бегает, лазает по деревьям на ".
$legs." лапах <br>";
}
function description()
{
$this->fur="мягкая и пушистая";
echo $this->name, " ", $this->fur, " . ";
echo "Кровь - ", $this->blood, "<br>";
}
}

final class cat extends beast


{
public $sound;
function __construct($name)
{
parent::__construct($name);
echo "Запущен конструктор класса cat <br>";
$this->sound="мурр";
}
24 Часть I. Технологии, составляющие AJAX
function speak()
{
echo $this->name, " говорит ", $this->sound."<br>";
}
}
// Теперь создадим объект этого класса и вызовем его методы:
$Murka = new cat("кошка");
$Murka-> move(4);
$Murka->description();
$Murka->speak();
?>

Объект $Murka создается, и все методы работают, но попробуйте добавить


в конец этого сценария строки
class ChildClass extends cat {
public function moreTesting() {
echo "Вызван метод ChildClass::moreTesting()\n";
}
}

и вы увидите на экране сообщение об ошибке:


Fatal error: Class ChildClass may not inherit from final class (cat) in
C:\Program Files\Apache Group\Apache2\htdocs\13-6.php on line 64

Из приведенных примеров видно, когда стоит использовать объектно-


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

Доступ к свойствам и методам класса


В объектно-ориентированном программировании доступ к свойствам класса
осуществляется обычно с определенными ограничениями. Рекомендуется,
чтобы работа со свойствами велась через вызов методов — это называется
инкапсуляцией. Смысл инкапсуляции состоит в том, что внешний пользова-
тель не знает детали реализации объекта, работая с ним путем предоставлен-
ного объектом интерфейса.
Глава 2. Объектно-ориентированное программирование в серверных приложениях 25

Для реализации инкапсуляции надо ввести ограничения на доступ к свойст-


вам и методам класса (то и другое можно называть элементом класса) с по-
мощью следующих спецификаторов.
public — общедоступный или открытый, к свойствам и методам, помечен-
ным этим спецификатором, можно получить доступ без каких-либо ограниче-
ний. В PHP 4 все свойства и методы объявлялись только как общедоступные,
причем перед именем свойства писалось ключевое слово var. Объектный код,
созданный на языке PHP 4, будет работать и с модулем 5 версии, причем сло-
во var будет трактоваться как определение свойства с открытым доступом.
protected — защищенный или с ограничением доступа. Элементы этого
типа доступны внутри класса, в котором они объявлены, и в его классах-
наследниках. Оборот "внутри класса" означает, что прочесть защищенное
свойство можно только с помощью метода, определенного в этом же классе.
private — закрытый или частный. Элементы этого типа доступны только
внутри класса, в котором они объявлены.
Если не указывать ни один из спецификаторов, то по умолчанию элемент бу-
дет общедоступным. Посмотрим, как все это работает, на примере. Создадим
два класса (листинг 2.7).
Листинг 2.7. Типы доступа к свойствам класса
<?php
class mammal
{
public $blood = "теплая";
protected $legs = "4";
private $eat = "молоко";
public function printPrivate()
{
echo $this->eat;
}
}
class cat extends mammal
{
public function printProtected()
{
echo $this->legs;
}
}
?>
26 Часть I. Технологии, составляющие AJAX

Попробуем теперь создать экземпляр класса mammal и распечатать значения


его свойств:
$Murka = new mammal;
echo $Murka->blood;
echo $Murka->classname;

Работает. А если так?


echo $Murka->legs;

Не выходит, получаем сообщение об ошибке:


Fatal error: Cannot access protected property mammal::$legs

А так?
echo $Murka->eat;

Нет, нельзя:
Fatal error: Cannot access private property mammal::$eat

Получить значение частного свойства можно только через вызов метода:


$Murka->printPrivate();

Определим этот метод так:


public function printPrivate()
{
echo $this->eat;
echo $this->legs;
}

Вызов такого метода предоставит нам значения и частного, и защищенного


свойств. А в чем же между ними разница? Посмотрим, как обстоит дело
с классом-наследником cat. Создадим новый экземпляр класса и вызовем его
метод:
$Barsik = new cat();
$Barsik->printProtected();

Все отлично, значение защищенного свойства $legs читается. А теперь по-


пробуем с помощью этого метода получить значение частного свойства $eat,
добавив его вывод в определение метода printProtected():
public function printProtected()
{
echo $this->legs;
echo $this->eat;
}
Глава 2. Объектно-ориентированное программирование в серверных приложениях 27

Нет, видим в браузере:


Notice: Undefined property: cat::$eat

Теперь перечитайте определения спецификаторов доступа, и они станут вам


понятнее.

Статические свойства и методы класса


В дальнейшем, изучая классы для работы с XML-документами, вы обнару-
жите в них статические свойства. Мы с вами видели, как определяются ста-
тические переменные в процедурном программировании на PHP. Можно
объявить статические свойства класса так, как представлено в листинге 2.8.
Листинг 2.8. Статическое свойство класса
<?php
class mammal
{
static $feeding = "молоко";
}
?>

Статические свойства едины для всего класса и не могут принадлежать ни


одному из объектов класса. Кроме этого, можно обратиться к такому свойст-
ву, не создавая объекта:
echo mammal::$feeding;

Аналогично можно определить статический метод и использовать его без


создания объекта такого класса (листинг 2.9).
Листинг 2.9. Статический метод класса
<?php
class mammal
{
static function moving()
{
echo "Все звери двигаются ";
}
}
echo mammal::moving();
?>
28 Часть I. Технологии, составляющие AJAX

Однако в статическом методе становится невозможным использовать указа-


тель $this, т. к. при вызове статического метода неизвестно, в контексте ка-
кого объекта он вызывается.

Абстрактные классы и интерфейсы


Представьте себе, что вы создаете сетевую игру, участники которой ведут
борьбу с противниками. Каждый игрок представлен фигуркой того или иного
существа, обладающего разными видами оружия, защиты и пр. Для описания
этих разнообразных и многочисленных героев вам придется создавать много
классов — родителей и наследников, описывать в них методы, которыми мо-
гут воспользоваться участники игры. Возможно, в одном из базовых классов
вы захотите только указать наличие некоторого метода, а конкретную его
реализацию отложить до перегрузки его в классе-наследнике. Это можно
сделать с помощью абстрактного метода.
Абстрактным называется метод, который имеет только объявление, но не
имеет реализации. Реализация же выполняется в классе-наследнике. Класс,
который содержит абстрактный метод, объявляется как абстрактный. При
этом в нем могут содержаться и обычные, неабстрактные, методы.
Создать объект, являющийся экземпляром абстрактного класса, невозможно.
Но вот после того, как в классе-наследнике бывший абстрактный метод будет
переопределен, можно уже создавать экземпляры этого класса-наследника
(листинг 2.10).

Листинг 2.10. Абстрактный класс


<?php
abstract class voin {
abstract public function deistvie();
}
class super_voin extends voin {
public function deistvie() {
echo "Появилась реализация метода ";
}
}
$obj = new super_voin;
$obj->deistvie();
?>
Глава 2. Объектно-ориентированное программирование в серверных приложениях 29

В PHP невозможно описать класс, являющийся наследником сразу двух клас-


сов, даже абстрактных. Для решения этой проблемы существуют интерфей-
сы, представляющие собой абстрактные классы, не содержащие ни одного
неабстрактного метода. Класс может наследовать двум интерфейсам одно-
временно, переопределяя их методы. Можно создавать объекты — экземпля-
ры такого класса-наследника. В листинге 2.11 представлено объявление двух
интерфейсов и класса — их общего наследника.
Листинг 2.11. Интерфейсы
<?php
interface voin{
function shoot();
}
interface artist{
function paint();
}
class hero implements voin, artist {
public function shoot() {
echo "Герой умеет стрелять. ";
}
public function paint() {
echo "Герой умеет рисовать. ";
}
}
$obj = new hero;
$obj->shoot();
$obj->paint();
?>

Константа класса
В PHP 5 введен такой элемент класса, как константа (листинг 2.12).
Листинг 2.12. Константа класса
<?php
class baza
{
30 Часть I. Технологии, составляющие AJAX
const DBNAME = "taxi";
}
echo baza::DBNAME;
?>

Из приведенного листинга ясно, что использовать константу можно без соз-


дания объекта. Константы класса пригодятся нам для хранения параметров
подключения к серверу баз данных или других данных, не подлежащих из-
менениям в ходе выполнения сценария.

Ключевое слово instanceof


Ключевое слово instanceof позволяет определить, является ли объект эк-
земпляром определенного класса (назовем класс country) или экземпляром
класса-наследника (класс-наследник назовем city) определенного класса
(листинг 2.13).

Листинг 2.13. Ключевое слово instanceof


<?php
class country { }
$obj1 = new country();
if ($obj1 instanceof country) {
echo "\$obj1 - объект класса country";
}
class city extends country{ }
$obj2 = new city();
if ($obj2 instanceof country) {
echo "\$obj2 - объект класса, производного от country";
}
?>

Обработка ошибок
Посмотрим теперь, как в объектном коде реализована обработка ошибок. Мы
видели, что при написании процедурного кода не рекомендуется выводить
сообщения о возможных ошибках, генерируемые самим модулем PHP, на
экран. Лучше предусмотреть возможность ошибки и создать свой код ее об-
работки. Например, может возникнуть проблема с открытием файла, так
Глава 2. Объектно-ориентированное программирование в серверных приложениях 31

не будем заставлять пользователя созерцать неудобочитаемые сообщения,


а создадим свое, подходящее к случаю.
Для обработки ошибок в ядро PHP 5 встроен класс Exception (в листин-
ге 2.14 представлена структура класса, но не описана реализация, это не ра-
бочий пример, а демонстрация идеи). Конечно, нельзя разрешать пользовате-
лю переопределять методы этого класса, поэтому они объявлены как
финальные.

Листинг 2.14. Класс Exception


<?php
class Exception
{
protected $message = 'Unknown exception'; // сообщение об исключении
protected $code = 0; // код исключения, определяемый пользователем
protected $file; // имя файла-источника исключения
protected $line; // строка, в которой произошло исключение
function __construct($message = null, $code = 0);
final function getMessage(); // сообщение об исключении
final function getCode(); // код исключения
final function getFile(); // файл источника
final function getLine(); // строка источника
final function getTrace(); // массив backtrace()
final function getTraceAsString();// форматированная строка трассировки
/* Overrideable — можно перегружать */
function __toString(); // Создание строки для отображения на экране
?>

Во многих объектно-ориентированных языках реализована схема обработки


исключений с помощью конструкции try/catch/throw. Она позволяет весь
код обработки ошибок локализовать в одном месте сценария.
Попытаемся открыть несуществующий файл:
$fp = fopen("file.txt", "r");

Нельзя, получаем сообщение об ошибке. Скроем ее от пользователя:


@$fp = fopen("file.txt", "r");

Теперь напишем объектный код так, чтобы при возникновении ошибки, т. е.


в случае, когда $fp получила значение false, создавался новый экземпляр
$exception класса Exception. Создавать будем, конечно, оператором new.
32 Часть I. Технологии, составляющие AJAX

Передадим для его создания в качестве значения свойства $message этого


класса сообщение об исключении "Невозможно открыть файл!". Не будет
ошибки, ну и хорошо, запишем все, что нужно, в файл. Но если возникнет
ошибка, то она будет перехвачена конструкцией catch, которая напечатает
"Ошибка в строке ", затем вызовет метод getLine() для нашего объекта
и метод getMessage(). Эти методы вернут номер строки скрипта, в которой
произошла ошибка, и значение свойства $message. Синтаксис обработки
ошибок таков, как представлено в листинге 2.15.
Листинг 2.15. Обработка ошибок с помощью конструкции try/catch/throw
<?php
try
{
@$fp = fopen("file.txt", "w");
if (!$fp) throw new Exception("Невозможно открыть файл!");
// Запись данных в файл при отсутствии ошибок
fclose($fp);
}
catch (Exception $exception)
{
echo "Ошибка в строке ", $exception->getLine();
echo $exception->getMessage();
}
?>

Автозагрузка класса
Итак, есть у нас большой проект, при реализации которого мы решили ис-
пользовать объектный подход. Определили классы и записали каждый класс
в отдельный файл — так мы сможем соблюсти принцип модульности про-
граммного обеспечения и подключать объявления классов к любому скрипту,
используя, например, инструкцию include. Теперь каждый из описанных
ранее классов хранится в файлах mammal.php, beast.php и cat.php. Для их
подключения в начале скрипта пишем:
include("mammal.php");

и т. д. для всех классов. Забудем подключить — получим ошибку при созда-


нии объекта. В PHP 5 есть возможность загрузки классов, не подключенных
к скрипту, как указано ранее, с помощью глобальной функции __autoload.
Глава 2. Объектно-ориентированное программирование в серверных приложениях 33

Эта функция принимает один параметр — имя класса. Действия, которые бу-
дут выполняться при вызове функции автозагрузки, придется определить
(листинг 2.16).

Листинг 2.16. Автозагрузка класса


<?php
function __autoload($class)
{
include($class.".php");
}
$Barsik = new mammal("кошка");
echo $Barsik->move(4);
?>

При создании классов и файлов надо помнить о чувствительности к регистру


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

Итераторы: просмотр всех общедоступных


свойств объекта
В объектной модели определена возможность перебора всех открытых
свойств объекта с помощью оператора цикла foreach(). Это и выполнено
в листинге 2.17.
Листинг 2.17. Использование итератора
<?php
class mammal
{
public $blood, $legs;
public function __construct($name)
{
$this->name = $name;
34 Часть I. Технологии, составляющие AJAX
$this->blood = "теплая";
$this->legs = 4;
}
}
$Murka = new mammal("Кошка");
foreach ($Murka as $svoistvo => $znachenie) {
echo "$svoistvo => $znachenie";
}
?>

Подытоживая сказанное, следует отметить, что в PHP реализована объект-


ная модель на основе классов со всеми основными принципами объектно-
ориентированного программирования.

Синглетон
Синглетон — это шаблон проектирования, который следует применить в том
случае, когда в приложении необходим только один экземпляр какого-либо
класса. Посмотрите пример в листинге 2.18. Применение статического мето-
да doAction позволяет решить поставленную задачу.
Листинг 2.18. Реализация синглетона
<?php
class Singleton {
private static $instance;
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self;
}
return self::$instance;
}
public function doAction() {
echo "Вызов общедоступного метода doAction <br>";
}
}
Singleton::getInstance()->doAction();
?>
Глава 3

Объектно-ориентированное
программирование в JavaScript
Объект в JavaScript — это коллекция поименованных свойств. JavaScript
относится к языкам прототипного программирования, при котором отсут-
ствует понятие класса, а повторное использование (наследование) произ-
водится путем клонирования существующего экземпляра объекта — про-
тотипа.
В JavaScript существуют два метода создания нового объекта: клонирование
имеющегося объекта либо создание объекта "с нуля". Для создания объекта
с нуля программисту предоставляются средства добавления свойств и мето-
дов в объект. В дальнейшем, с получившегося объекта может быть получена
полная копия — клон. В процессе клонирования копия наследует все харак-
теристики своего прототипа, но с этого момента она становится самостоя-
тельной и может быть изменена.
Вообще же в JavaScript согласно спецификации ECMA Script определены три
типа объектов: базовые (native), объекты браузера (host object) и объекты,
создаваемые пользователем (user-defined).
Базовые объекты поддерживаются механизмом JavaScript, например, объек-
ты Object, Math или Number. Имена базовых объектов являются чувствитель-
ными к регистру и начинаются с заглавной буквы. Второй тип объектов под-
держивается браузером, обеспечивая, таким образом, взаимодействие
пользователя с загруженным документом. Имена таких объектов начинаются
со строчной буквы, например, document, window, frames.
Наконец, программист может сам создать объект и дать ему имя. Имя может
начинаться с любой буквы и также является чувствительным к регистру.
Объектом в JavaScript является даже функция.
Следует отметить, что JavaScript позволяет решать задачи объектно-
ориентированного программирования, т. е. реализовывать инкапсуляцию,
36 Часть I. Технологии, составляющие AJAX

полиморфизм и наследование. Для демонстрации справедливости последнего


утверждения, нам потребуется показать, как в JavaScript можно:
определить класс;
определить и вызывать методы класса;
определить класс-наследник;
вызывать конструктор родительского класса из класса-наследника;
переопределить методы родительского класса в классе-наследнике;
вызывать методы родительского класса из класса-наследника.

Создание объекта
с помощью оператора new
В JavaScript определен тип данных Object, который и применяется при соз-
дании объектов. Простейший способ создания объекта состоит в вызове опе-
ратора new:
obj = new Object;
obj.x = 1;
obj.y = 2;

При создании объекта к указанным нами явно свойствам добавляется еще


одно — constructor. Вначале создается объект obj, а затем ему добавляются
свойство и метод.

Создание объектов
с помощью объектных литералов
Другой путь состоит в использовании объектных литералов, как в листин-
ге 3.1.

Листинг 3.1. Создание объекта в JavaScript


<html>
<head>
<script>
var nash_object = {
svoistvo: "значение свойства",
drugoe_svoistvo: "придумаем еще какое-нибудь значение",
Глава 3. Объектно-ориентированное программирование в JavaScript 37

message: function(){ alert("Привет!"); },


nash_metod: function(){ alert("Наш метод!"); }
};
alert(nash_object["svoistvo"]);
nash_object.nash_metod();
</script>
</head>
<body></body>
</html>

Литерал может содержать массивы и выражения JavaScript.


Мы создали объект nash_object и теперь можем получить доступ к его свой-
ствам с помощью точки или квадратных скобок:
alert(nash_object. svoistvo);
alert(nash_object['svoistvo']);

Второй вариант иногда дает более гибкий способ обращения к свойствам,


например, зная о том, что к свойствам можно обращаться с помощью квад-
ратных скобок, мы можем определить, например, функцию, меняющую
свойства объектов, вот так:
function changeValue(property, value) {
nash_object[property] = value;
}

Каков бы ни был способ создания объекта, с помощью оператора new или


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

Конструктор объекта
Для создания однотипных объектов требуется сначала заполучить объект,
который бы обладал нужными чертами, но который можно было бы исполь-
зовать для того, чтобы по его подобию конструировать другие объекты.
В JavaScript для этой цели используется функция конструктора объекта.
Конструктор — это функция языка, отличающаяся тем, что вызывается она
с помощью оператора new. Ей передается в виде значения ключевого слова
this ссылка на создаваемый пустой объект. Действие конструктора состоит
в наполнении созданного пустого объекта свойствами и методами.
38 Часть I. Технологии, составляющие AJAX

Вот фрагмент кода, который демонстрирует сказанное:


function cr()
{
this.x = 1;
this.y = 2;
}
Obj = new cr;

Можно создать сколько угодно объектов, применяя конструктор. Каждый из


таких объектов будет иметь свойства x и y, имеющие значения 1 и 2 соответ-
ственно.

Функции как объекты


Функция в JavaScript является объектом, как и все остальное. Функцию можно
создать следующим образом:
function a(x) { return 1 + x; }

или:
var a = function(x) { return 1 + x; }

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


Например, присвоить ее переменной. Во втором варианте оператор function
создает новый объект функции, который присваивается переменной а.
Как и у любого объекта, у функции могут быть свойства. Допустима, напри-
мер, следующая запись:
b.someVar = 15;

Функцию можно присвоить не только переменной, но и свойству объекта.


Причем допустимо использовать как нотацию с точкой, так и квадратные
скобки:
var q = {};
q["someFunc"] = b;
q.someOtherFunc = function(){}

Добавление методов при помощи прототипа


Мы видели, что метод можно добавить в функцию-конструктор, просто впи-
сав его внутрь при определении. Но в JavaScript обычно используют другой
путь: создание прототипа.
Глава 3. Объектно-ориентированное программирование в JavaScript 39

Прототип дает возможность добавлять в объект новые свойства или менять


значения имеющихся свойств уже после создания объекта. Понятию прото-
типа трудно подобрать аналогию в нашем житейском мире. Скажем, автомо-
биль, выпускаемый с конвейера, имеет все те свойства, что заложил в него
человек-конструктор. Если человек решит изменить, например, параметры
двигателя и внесет эти изменения в чертежи, то лишь новые автомобили,
созданные по этим новым чертежам, будут обладать двигателем с новыми
параметрами. С объектами JavaScript иное дело: если мы изменим прототип
уже созданного объекта, то и объект мгновенно изменится, получит новые
свойства, которые не существовали в тот момент, когда объект создавался.
Это, скорее, напоминает рассказ Бредбери, где человек, попав в прошлое,
нечаянно давит ногой бабочку. Он возвращается в настоящее время и обна-
руживает, что существующая вокруг него реальность пересоздана заново
в соответствии с последствиями его неосторожного движения.
У каждого объекта в JavaScript есть свойство prototype, которое, в свою
очередь, является объектом со своими свойствами. К этим свойствам в любой
момент можно добавить новое свойство, например, так, как показано в лис-
тинге 3.2.
Листинг 3.2. Добавление метода в прототип объекта
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Использование прототипа/title>
<script>
function person(name) {
this.name = name;
this.age = 22;
}
var girl = new person("Анна");
person.prototype.addFName = function(fname) {
this.f_name = fname
}
girl.addFName("Николаева");
document.write(girl.f_name);
</script>
</head>
<body></body>
</html>
40 Часть I. Технологии, составляющие AJAX

Как видите, вызов метода addFName для объекта girl не вызвал ошибки и дал
необходимый результат: свойство f_name был добавлено в объект. В резуль-
тате добавления метода addFName в прототип все экземпляры объектов, соз-
данные на основе этого прототипа, получат новый метод.

Наследование при помощи прототипа


В JavaScript каждый объект может унаследовать свойства от другого объекта,
называемого прототипом. Пытаясь определить значение запрошенного свой-
ства, JavaScript сначала смотрит, определено ли это свойство для данного
объекта. Если нет, то проверяется, определено ли это свойство для прототипа
данного объекта. Цепочка поиска по прототипам может продолжаться до
корневого прототипа языка. Каждый объект связан со своим прототипом, ко-
торый создается функцией-конструктором данного объекта. Посмотрим, как
создается цепочка прототипов на примере, приведенном в листинге 3.3.
Листинг 3.3. Цепочка прототипов
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Цепочка прототипов</title>
<script>
Object.prototype.inObj = 1;
function A()
{ this.inA = 2; }
A.prototype.inAProto = 3;
// Конструктор А подключается в цепочку прототипов:
B.prototype = new A;
B.prototype.constructor = B;

function B()
{ this.inB = 4; }
B.prototype.inBProto = 5;

x = new B;
document.write(x.inObj + ',' + x.inA + ', ' + x.inAProto + ',' +
x.inB + ',' + x.inBProto);
Глава 3. Объектно-ориентированное программирование в JavaScript 41

</script>
</head>
<body></body>
</html>

Объект x создается функцией-конструктором B, получая при этом свойство


inB. Кроме того, объект x получает еще и свойство inBProto, поскольку оно
было добавлено в прототип уже после создания конструктора. Сам же конст-
руктор B имеет свойство prototype, значение которого — функция-
конструктор A.
В браузере Firefox можно обратиться к прототипу посредством нестандарт-
ного свойства _proto_.

Создание класса-наследника
Идея состоит в том, чтобы использовать цепочку прототипов для организа-
ции наследования методов от базового класса. Посмотрите, как это делается
в листинге 3.4.
Листинг 3.4. Наследование
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Наследование</title>
<script>
function A() // Определение базового класса
{ this.x = 1; }
A.prototype.DoIt = function() // Определение метода
{ this.x += 1; }
B.prototype = new A; // Определение класса-наследника
B.prototype.constructor = B;
function B()
{ A.call(this); // Вызов конструктора базового класса
this.y = 2;
}
B.prototype.DoIt = function() // Определение метода
{ A.prototype.DoIt.call(this); // Вызов метода базового класса
this.y += 1;
42 Часть I. Технологии, составляющие AJAX
}
b = new B;
document.write((b instanceof A) + ', ' + (b instanceof B) +
'<BR/>');
b.DoIt();
document.write(b.x + ', ' + b.y);
</script>
</head>
<body></body>
</html>

В классе-наследнике мы явным образом вызываем конструктор базового


класса для того, чтобы включить его в цепочку прототипов.

Полиморфизм
Полиморфизм обеспечивается тем, что различные классы объектов содержат
коллекцию методов с одинаковыми именами. Таким образом, при вызове на-
до только корректно задать имя вызываемого метода, например, так, как по-
казано в листинге 3.5.
Листинг 3.5. Полиморфизм
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Полиморфизм</title>
<script>
function A(){ this.x = 1; }
A.prototype.DoIt = function(){this.x += 1; }

function B() { this.x = 1; }


B.prototype.DoIt = function(){this.x += 2; }

a = new A;
b = new B;
a.DoIt();
b.DoIt();
Глава 3. Объектно-ориентированное программирование в JavaScript 43

document.write(a.x + ', ' + b.x);


</script>
</head>
<body></body>
</html>

Частные элементы классов


JavaScript поддерживает даже частные элементы классов. Переменные, опре-
деленные в конструкторе, имеют локальную область видимости, если они
определены с помощью ключевого слова var. Для доступа к таким перемен-
ным необходимо только создать локальные функции с областью видимости
внутри конструктора (листинг 3.6).

Листинг 3.6. Частные элементы класса


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Частные методы</title>
<script>
function A() {
var x = 7;
this.GetX = function() { return x;}
this.SetX = function(xT) { x = xT; }
}
obj = new A;
obj2 = new A;
document.write(obj.GetX() + ' ' + obj2.GetX());
obj.SetX(14);
document.write(' ' + obj.GetX() + ' ' + obj2.GetX());
</script>
</head>
<body></body>
</html>

Конструктор A() создает частное свойство x. Оно — часть объекта, но его


значение невозможно получить вне объекта. Но с помощью метода GetX
можно прочитать это свойство, а методом SetX — изменить.
44 Часть I. Технологии, составляющие AJAX

Пространства имен
В JavaScript нет никакой встроенной поддержки пространств имен, но их
легко воспроизвести, используя объекты. Допустим, нужно создать библио-
теку на JavaScript. Вместо создания глобальных функций и классов их можно
обернуть в пространство имен следующим образом (листинг 3.7).
Листинг 3.7. Пространство имен
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Пространства имен</title>
<script>
var NS = {};
NS.Pet = function(name) { this.name = name; };
NS.Pet.prototype.toString = function() { alert(this.name); };
var pet = new NS.Pet("Кролик");
pet.toString();
</script>
</head>
<body></body>
</html>

Одного уровня пространства имен может быть недостаточно, так что можно
создавать вложенные пространства имен. Как легко можно себе представить,
написание этих длинных вложенных имен довольно быстро становится уто-
мительным. К счастью, пользователи могут создать более короткий псевдо-
ним для пространства имен (листинг 3.8).
Листинг 3.8. Вложение пространств имен
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Пространства имен</title>
<script>
var NS = {};
NS.Examples = {};
var Eg = NS.Examples;
Глава 3. Объектно-ориентированное программирование в JavaScript 45

Eg.Pet = function(name) { this.name = name; };


Eg.Pet.prototype.toString = function() { alert(this.name); };
var pet = new Eg.Pet("Кролик");
pet.toString();
</script>
</head>
<body></body>
</html>

Обработка ошибок
Оператор try...catch используется в тех фрагментах сценария, где может
возникнуть исключение, для его обработки. Он имеет вид:
try {
оператор1
}
catch (исключение) {
оператор2
}

Здесь исключение — любое имя переменной, а оператор1 и оператор2 — лю-


бые группы операторов JavaScript, заключенные в фигурные скобки.
Оператор1 содержит программный код, в котором возможно возникновение
исключения. Если исключение не возникло, то после исполнения оператора1
управление передается обычным образом оператору, следующему за
try...catch. Если же оно возникло, то информация об исключении заносится
в локальную переменную исключения, и управление передается оператору2,
который должен содержать код обработки этого исключения (листинг 3.9).
Листинг 3.9. Обработка исключений
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>try_catch</title>
<script language="JavaScript">
function f(i) {
if (i<0) throw "i меньше 0";
else throw "i больше 0";
46 Часть I. Технологии, составляющие AJAX
}
try { f(2); }
catch (e) { alert(e); }
</script>
</head>
<body></body>
</html>

Синглетоны
В некоторых случаях необходимо иметь единственный экземпляр объекта.
Вы уже встречались с такой ситуацией в главе 2. Аналогично в JavaScript
можно создать объект, который не сможет послужить прототипом. Посмот-
рите в листинге 3.10, как это делается с помощью объектного литерала.
Листинг 3.10. Создание синглетона
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Синглетон</title>
<script>
var single_obj = new function()
{
this.chislo = 30;
this.start = function() { alert("some function"); }
}
document.write(single_obj.chislo);
single_obj.start();
</script>
</head>
<body></body>
</html>

Мы вызываем анонимную функцию и используем this для установки


свойств и методов. Создание нового объекта этим путем позволяет избежать
потенциального использования объекта как шаблона.
В нашем синглетоне даже можно создать частные свойства (листинг 3.11).
Глава 3. Объектно-ориентированное программирование в JavaScript 47

Листинг 3.11. Синглетон с частными свойствами


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Синглетон</title>
<script>
var single_obj = new function()
{
var svoistvo = 30;
function construct()
{
this.metod = function() {}
this.getsvoistvo = function() { return svoistvo; }
this.setsvoistvo = function(sv) { svoistvo = sv; }
}
return new construct();
}
single_obj.setsvoistvo(50);
alert(single_obj.getsvoistvo());
</script>
</head>
<body></body>
</html>

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

Замыкания
Фундаментальная статья на эту тему принадлежит Ричарду Корнфорду (Ri-
chard Cornford) — см. http://www.jibbering.com/faq/faq_notes/closures.html.
По-русски на настоящий момент лучшая статья В. Агафонкина на
http://habrahabr.ru/blogs/webdev/38642/.
Как известно, в JavaScript областью видимости локальных переменных (объ-
являемых словом var) является тело функции, внутри которой они определе-
ны. Посмотрите, что происходит, если внутри одной функции определена
другая (листинг 3.12).
48 Часть I. Технологии, составляющие AJAX

Листинг 3.12. Замыкание


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Замыкание</title>
<script>
function createCounter() {
var numberOfCalls = 0;
return function() {
return ++numberOfCalls;
}
}
var fn = createCounter();
document.write(fn());
document.write(fn());
document.write(fn());
</script>
</head>
<body></body>
</html>

Откройте эту страницу в браузере, и вы увидите на экране текст "1 2 3".


Это означает, что переменная numberOfCalls доступна для чтения и записи
во внутренней анонимной функции. Но это еще не все: после того как функ-
ция createCounter была выполнена, переменная numberOfCalls сохранила
свое значение, и это значение было использовано при втором вызове функ-
ции numberOfCalls.
Именно за эти свойства такие вложенные функции в JavaScript называют за-
мыканиями (от англ. closure) — они замыкают на себя переменные той
функции, внутри которой определены.
Применение замыканий
Упростим немножко пример из листинга 3.12 — уберем необходимость от-
дельно вызывать функцию createCounter, сделав ее анонимной и вызвав
сразу же после ее объявления:
var fn = (function() {
var numberOfCalls = 0;
Глава 3. Объектно-ориентированное программирование в JavaScript 49

return function() {
return ++ numberOfCalls;
}
})();

Такая конструкция позволила нам привязать к функции данные, сохраняю-


щиеся между ее вызовами — это одно из применений замыканий. Иными
словами, с их помощью мы можем создавать функции, имеющие свое изме-
няемое состояние.
Обычно замыкания используются для того, чтобы передать параметры
в функцию до ее запуска. Например, для передачи параметров в функцию
setTimout.
setTimeout запускает на выполнение функцию (назовем ее funct), указанную
в ее первом параметре через интервал времени, заданный в миллисекундах во
втором параметре. Но неудобство при работе с этой функцией состоит в том,
что в функцию funct невозможно передать параметр. Посмотрите, что мож-
но сделать, как это представлено в листинге 3.13.
Листинг 3.13. Применение замыкания для запуска функции setTimout
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Замыкание с SetTimeout</title>
<script>
function ok(){
function callLater(paramA, paramB, paramC){
return (function(){
paramA[paramB] = paramC;
});
}
var el = document.getElementById("ok");
var functRef = callLater(el.style, "display", "none");
hideMenu=setTimeout(functRef, 500);
}
</script>
</head>
50 Часть I. Технологии, составляющие AJAX
<body onload = "ok();">
<div id="ok" style="display:block;">Текст</div>
</body>
</html>

Но мы можем вызвать другую функцию callLater, которая вернет ссылку на


внутреннюю анонимную функцию. Ссылка functRef на функцию и будет
передана функции setTimeout в качестве параметра. Параметры, которые
потребуются для выполнения внутренней функции, передаются в вызове
функции callLater. setTimout выполняет внутреннюю функцию, не тре-
бующую параметров, но эта внутренняя функция имеет доступ к параметрам
внешней функции.
Глава 4

XML и JSON

Web-сервер отвечает на запрос клиента, отправляя запрошенные данные. Это


может быть простой текст, но обычно используется другой формат: либо
XML, либо JSON.
Язык XML разрабатывался для того, чтобы обеспечить передачу данных ме-
жду различными приложениями и даже между разными платформами.
JSON предоставляет возможность передавать данные в более компактном
виде, чем XML. К тому же формат JSON предназначен для передачи данных
в виде, наиболее легко обрабатываемом сценариями JavaScript.
В этой главе мы рассмотрим синтаксис обоих форматов.

Язык XML
XML (EXtensible Markup Language) — расширяемый язык разметки был соз-
дан для описания данных, т. е. каждый тег языка XML предназначен для
того, чтобы объяснить, какой смысл имеет текст, стоящий между открываю-
щим и закрывающим тегами. XML-документ — это просто текст с тегами.
В отличие от языка HTML, в XML теги не определены, при создании XML-
документов автор создает собственные теги.

Синтаксис XML.
Правильно оформленный XML
Программы, читающие и анализирующие XML-документы, называют парсе-
рами (от англ. parse — обрабатывать). Современные браузеры, такие как
Internet Explorer (начиная с версии 6) или Mozilla Firefox, включают в себя
парсеры XML-документов, отвечающие стандартам W3C.
52 Часть I. Технологии, составляющие AJAX

XML-документ, созданный в соответствии с синтаксическими правилами


языка, называется правильно оформленным (well formed) XML-документом.
Если браузер обнаруживает несоответствие XML-документа этим правилам,
появляется сообщение о найденной ошибке, и содержимое документа не ото-
бражается. Спецификация языка XML консорциума W3C указывает, что про-
грамма не должна продолжать обработку XML-документа, если она обнару-
жит ошибку. Критерии правильности XML-документа таковы.
Каждый открывающий тег должен иметь соответствующий закрывающий
тег. В отличие от HTML, в XML все теги должны быть парными:
<p> Текст параграфа. </p>

Пустой тег может записываться следующим образом: <br />.


Теги не могут перекрывать друг друга. В XML все элементы должны быть
правильно вложены друг в друга:
<parent>
<child>Текст </child>
</parent>
XML-документы должны иметь единственный корневой элемент (root
element).
XML-элементом является все, что заключено между открывающим тегом
элемента и закрывающим тегом элемента, включая сами теги.
Все XML-документы должны иметь единственную пару тегов, задающую
корневой элемент. Все остальные элементы должны быть вложены в кор-
невой элемент.
Элемент может иметь элементное содержимое, смешанное содержимое,
простое текстовое содержимое, или он может быть пустым. Все элементы
могут иметь дочерние элементы.
Имена элементов должны подчиняться соглашениям XML о названиях:
• названия могут содержать буквы, цифры и другие символы;
• названия не могут начинаться с цифры или знака препинания;
• названия не могут начинаться с букв xml (или XML, или Xml и т. д.);
• в названии не должно быть пробелов.
Регистр символов (верхний/нижний) для XML существенен. Тег <Letter>
отличается от тега <letter>. Таким образом, начальные и конечные теги
должны писаться в одном регистре.
XML сохраняет пробелы внутри текста.
Глава 4. XML и JSON 53

XML-декларация
В начале документа бывает полезно указать, каков конкретный тип этого до-
кумента. XML предоставляет в наше распоряжение специальную декларацию
для того, чтобы пометить документы как документы XML.
XML-декларация всегда начинается с символов <?xml и заканчивается сим-
волами ?>. Декларация должна располагаться в самом начале файла, т. е.
первым символом файла должна быть угловая скобка и никаких концов
строки и пробелов. Декларация сама по себе не является частью XML-
документа. Она не является XML-элементом и может не иметь закрыва-
ющего тега.
Типичная декларация выглядит так:
<?xml version='1.0' ?>

XML-документ может содержать символы различных языков мира. Чтобы пар-


сер корректно обрабатывал эти символы, нужно сохранять документ с указани-
ем используемой кодировки. Без указания кодировки в XML-документ нельзя
вставлять буквы русского языка, в этом случае ошибка неминуема.
В настоящее время при использовании XML в Web-приложениях целесооб-
разно применять кодировку UTF-8. Для этого в XML-декларацию необходи-
мо добавить атрибут encjding:
<?xml version="1.0" encoding="UTF-8"?>

Атрибуты
Элементы XML могут содержать атрибуты в начальном теге. Атрибуты —
это пары "имя = значение", поставленные в соответствие одному из элемен-
тов. Атрибуты применяются для предоставления дополнительной информа-
ции об элементах. Они должны находиться при открывающем теге.
Атрибуты всегда должны иметь значение, даже если оно — всего лишь пус-
тая строка, и эти значения должны заключаться в кавычки. Допускаются как
двойные, так и одинарные кавычки.
Пример:
<file type="gif">computer.gif</file>

Комментарии
Комментарии позволяют вставлять в XML-документ текст, который на самом
деле не является частью документа, а предназначен для тех, кто будет читать
сам исходный XML. Комментарии не могут располагаться внутри тега.
54 Часть I. Технологии, составляющие AJAX

Стандарт XML устанавливает, что XML-анализатор не должен передавать


эти комментарии приложению.
Комментарии всегда начинаются строкой <!-- и заканчиваются строкой -->.
В листинге 4.1 представлен XML-документ, а на рис. 4.1 — вид его в брау-
зере.
Листинг 4.1. XML-документ
<?xml version="1.0" encoding="UTF-8"?>
<car id="f327" xmlns="http://myford.ru">
<make>Ford</make>
<model>Focus</model>
<date>
<year>2008</year>
<month>май</month>
</date>
<!-- текст комментария -->
<color>красный</color>
</car>

Рис. 4.1. Вид XML-документа в браузере


Глава 4. XML и JSON 55

Процессуальная инструкция
Процессуальная инструкция (т. е. инструкция по обработке вкладываемого
в нее текста) имеет следующую общую форму записи:
<?Имя_приложения инструкция ?>
Инструкция передает информацию указанному в ее начале приложению.
Пространства имен XML
Поскольку имена элементов в XML не определены, возможны конфликты,
когда два различных документа используют одно и то же имя для описания
двух различных типов элементов.
В этом XML-документе информация заключена в таблицу (table):
<table>
<tr>
<td>sun</td>
<td>rain</td>
</tr>
</table>

А в этом XML-документе содержится информация о столе как части мебели


(table):
<table>
<name>Kitchen table</name>
<width>80</width>
<length>120</length>
</table>

Если эти два XML-документа сложить вместе, возникнет конфликт имен


элементов, потому что оба документа содержат элементы <table> с разным
содержанием и определением.
Пространства имен (namespaces) XML позволяют избегать конфликтов
имен элементов. Пространство имен XML — это коллекция имен, исполь-
зуемых в XML-документах для обозначения элементов и атрибутов. Эта кол-
лекция идентифицируется именем ресурса в Интернете (URI).
Пользуясь терминами интернет-технологий, можно сказать, что русский
язык — это коллекция слов. О принадлежности слова к тому или иному язы-
ку говорят так: это слово японского языка. Название языка идентифицирует
коллекцию слов, так же как URI идентифицирует пространство имен. Часто
смысл слова можно понять только тогда, когда известно, из какого оно языка.
"А-а, это по-японски значит «здравствуйте»!" — говорим мы.
56 Часть I. Технологии, составляющие AJAX

Для указания пространства имен в начальный тег элемента помещается атри-


бут с использованием следующего синтаксиса:
xmlns: prefix="namespace"

Здесь xmlns — ключевое слово, означающее, что начинается определение


пространства имен; prefix — префикс, который будет использоваться во
всем документе для обозначения принадлежности тега, перед которым он
ставится, к этому пространству имен; "namespace" — идентификатор про-
странства имен.
Когда пространство имен задается в корневом теге элемента, все дочерние
его элементы, обладающие тем же префиксом, относятся к тому же про-
странству имен.
Адрес ресурса, участвующий в идентификации пространства имен, не ис-
пользуется парсером для поиска информации. Единственная задача этого ад-
реса — дать пространству имен уникальное имя. Тем не менее, очень часто
пространство имен используют как указатель на реальную Web-страницу,
содержащую информацию об этом пространстве имен.
В следующем XML-документе (листинг 4.2) пространство имен было опре-
делено с помощью URI http://www.w3.org/TR/html4/.
Листинг 4.2. Пространство имен
<h:table xmlns:h="http: //www.w3.org/TR/html4/">
<h:tr>
<h:td>Apples</h:td>
<h:td>Bananas</h:td>
</h:tr>
</h:table>

Кроме применения самих префиксов h в этих примерах к тегу table был до-
бавлен атрибут xmlns. Это сделано для того, чтобы дать префиксу имя, свя-
занное с пространством имен. Префикс h в приведенном примере играет та-
кую же роль, как фамилия для ребенка: "Он из семьи Петровых" — могут
сказать про ребенка, определив тем самым его принадлежность к некоторому
множеству (семье).
Особые символы
Некоторые особые XML-символы не могут быть корректно обработаны
и должны заменяться ссылками на сущности.
Глава 4. XML и JSON 57

Если вы поместите внутрь XML-элемента символ левой угловой скобки (<),


это вызовет ошибку, потому что парсер интерпретирует его как начало ново-
го элемента. Для того чтобы избежать ошибки, нужно заменить этот символ
ссылкой на сущность:
<message>if salary &lt; 1000 then</message>

В XML есть пять изначально заданных сущностей:


&lt; — меньше, чем (<);
&gt; — больше, чем (>);
&amp; — амперсанд (&);
&apos; — апостроф (');
&quot; — двойная кавычка (").
Ссылки на сущности всегда начинаются со знака амперсанда (&) и заканчи-
ваются точкой с запятой (;).

CDATA
Если XML-элемент содержит много символов левой угловой скобки или знак
амперсанда (например, надо вывести программный код), то можно задать секцию
CDATA, очень похожую по смыслу ее применения на тег <pre> в HTML.
CDATA (Character DATA) переводится как "символьные данные". Все, что на-
ходится внутри раздела CDATA, игнорируется парсером. Секция CDATA начинает-
ся символами <![CDATA[ и заканчивается символами ]]> (листинг 4.3).
Листинг 4.3. Раздел CDATA
<script>
<! [ CDATA [
function matchwo(a,b)
{
if (a < b && a < 0) then
{
return 1
} else
{
return 0
}
]]>
</script>
58 Часть I. Технологии, составляющие AJAX

В этом примере все, что расположено внутри секции CDATA, не будет анали-
зироваться парсером.

JSON
JSON (JavaScript Object Notation) дословно переводится как запись объектов
в формате JavaScript. JSON оперирует следующими понятиями.
Объект — неупорядоченный набор пар "ключ/значение". Объект начи-
нается с открывающей фигурной скобки и заканчивается закрывающей
фигурной скобкой. Каждое имя сопровождается двоеточием, пары "ключ/
значение" разделяются запятой.
Массив — упорядоченная коллекция значений. Массив начинается с от-
крывающей квадратной скобки и заканчивается закрывающей квадратной
скобкой. Значения разделены запятой.
Значение может быть строкой в двойных кавычках, числом, true, false,
null, объектом или массивом. Эти структуры могут быть вложенными.
Строка — коллекция нуля или больше символов Unicode, заключенная
в двойные кавычки. Используется обратная косая черта в качестве симво-
ла экранирования.
Число представляется так же, как в C или Java, кроме того, что использу-
ется только десятичная система счисления.
Пробелы могут присутствовать между любыми лексемами.
Вспомните синтаксис создания объекта в JavaScript, и вам станет ясно, что
в приведенном ранее описании рассказывается именно про этот синтаксис
описания данных.
В PHP, начиная с версии 5.2, существуют функции, преобразующие объекты
PHP в объекты JSON и обратно. Рассмотрим пример их использования (лис-
тинг 4.4).
Листинг 4.4. Передача данных в формате JSON
<?php
class car {
public $make = "Ford";
public $model = "Focus";
public $color = "red";
public $date = array("year"=>2008, "month"=>"may");
Глава 4. XML и JSON 59

}
$test = new car;
echo json_encode($test);
?>

Запустив этот пример, вы увидите в браузере следующую строку:


{"make":"Ford","model":"Focus","color":"red","date":{"year":2008,"month":
"may"}}

Вот и все стало ясно, не правда ли? Функция json_encode преобразовала


данные объекта $test в формат, которым пользуется JavaScript, а мы эти
данные передали в клиентский браузер.
Функция json_decode является парой только что описанной.
Для более ранних версий PHP существуют различные варианты преобразова-
ния в формат JSON, вы найдете их множество на просторах Интернета, но мы
не будем на них останавливаться в этой книге.
В JavaScript определена функция eval, которая преобразует строку в формате
JSON в объекты. Синтаксис ее использования немного замысловат, посмот-
рите листинг 4.5.

Листинг 4.5. Обработка ответа функцией eval


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>eval JSON</title>
<script>
var response = '{"make":"Ford", "model":"Focus", "color":"red",
"date":{"year":2008, "month":"may"}}';
var data = eval("(" + response + ")");
document.write(data.make);
</script>
</head>
<body></body>
</html>

Обратите внимание на скобки, которыми окружены данные JSON. Их при-


сутствие заставляет функцию eval рассматривать аргумент именно как вы-
ражение, подлежащее обработке. Функция eval возвращает объект, содер-
жащий все свойства того объекта, что был создан на сервере.
60 Часть I. Технологии, составляющие AJAX

Если вам пока трудно оценить выгоды применения JSON, то представьте се-
бе, что вы пишете сетевую игру, герои которой действуют на клиентских
машинах. На клиентской машине герои фигурируют в виде объектов Java-
Script, их действиями управляет пользователь. Клиентская программа от-
правляет запросы на сервер для того, чтобы сделать эти действия известными
другим участникам. Действия героев приводят к тому, что их свойства меня-
ются, и, возможно, они приобретают новые возможности, описываемые
функциями. На сервере герой представлен в виде объекта PHP. Обработка
данных в случае использования JSON существенно упростится по сравнению
с XML-вариантом.
Глава 5

Объектная модель документа

Document Object Model (DOM) определяет стандартные способы доступа


к данным XML-документа и оперирования этими данными. В дальнейшем
данные из документа могут быть включены в Web-страницу. XML-документ
должен быть правильно оформленным, а значит, с XHTML-документами
также можно обращаться в соответствии с DOM.
Парсер браузера или любой другой парсер XML, отвечающий стандартам
DOM, рассматривает документ как дерево, состоящее из узлов (Node). Узла-
ми являются элементы документа, атрибуты, текстовое содержимое элемен-
та, даже сам документ в целом является узлом. В табл. 5.1 представлены все
допустимые типы узлов.

Таблица 5.1. Типы узлов


nodeType Тип узла Описание nodeValue

1 element Элемент null

2 attr Атрибут Значение ат-


рибута
3 text Текстовое содержимое Содержимое
элементного узла узла
4 cdatasection Раздел CDATA (его со- Содержимое
держимое не будет об- узла
рабатываться парсе-
ром)
5 entityreference Имя ссылки на сущность null
6 entity Сущность null

7 processinginstruction Процессуальная инст- Содержимое


рукция узла
62 Часть I. Технологии, составляющие AJAX
Таблица 5.1 (окончание)
nodeType Тип узла Описание nodeValue

8 comment Комментарий Текст коммен-


тария
9 document Весь документ null
10 documenttype Имя типа документа null
11 documentfragment Объект, который содер- null
жит часть документа
12 notation Имя нотации null

Каждый узел является объектом некоторого класса, для которого стандартом


DOM определен набор свойств и методов. Скажем, все элементные узлы отобра-
жаются в дереве в виде объектов класса Element, а атрибуты — в виде объектов
Attr. Иначе говорят, что Element, Attr и др. — это интерфейсы элементов доку-
мента. Термин "интерфейс" используется для того, чтобы указать, что некоторый
объект играет определенную роль. Эта роль определяет, какие свойства имеет
объект, какие методы для манипулирования с ним можно использовать.
Рассмотрим XML-документ, представленный в листинге 5.1, и дерево, кото-
рое строится в браузере из данных этого документа (рис. 5.1).
Листинг 5.1. XML-документ

<?xml version="1.0" encoding="UTF-8" ?>


<book edition="bhv" pubdate="2008">
<author>
<name>Василий </name>
<fname>Пупкин</fname>
</author>
<title>Как я стал героем русскоязычного Интернета.</title>
<genre>мемуары</genre>
</book>

Графическое представление дерева документа демонстрирует узлы и взаимо-


связи между ними. Дерево начинается с корня, к которому крепится весь до-
кумент. Это первый узел, но узел, не представляющий некоторый HTML-
элемент, а специальный, указывающий на начало документа. Положение ка-
ждого узла в дереве документа определяется относительно корня. Корень
документа следует отличать от корневого элемента. Корневой элемент за-
ключает в себя все остальные элементы документа.
Глава 5. Объектная модель документа 63

Root

Nodelist
Root Element <book>

Nodelist NamedNodemap

Element Element Element Attr Attr


<author> <title> <genre> edition pubdate

Nodelist Nodelist Nodelist

Element Element TextNode


<name> <fname> мемуары
TextNode
Как я стал
героем
Nodelist Nodelist Интернета
TextNode TextNode
Василий Пупкин

Рис. 5.1. Дерево XML-документа

Каждый узел, за исключением корня, имеет один родительский узел. У любо-


го узла может быть несколько узлов-наследников.
Элементы-наследники — это те элементы, которые вложены в родительские
элементы. Дети одного и того же родителя называются siblings (братья и се-
стры). В приведенном выше примере узел author является первым ребенком
(first child) узла book, а узел genre — последним (last child). Старшинство уз-
лов одного поколения определяется положением в документе: чем выше
строчка, где описывается узел, тем этот ребенок старше.
Элементы-наследники одного родителя составляют упорядоченный список
узлов (NodeList). Вообще списки узлов могут составляться из узлов, ото-
бранных на основании различных критериев: положения в дереве документа,
имени тега и т. д.
Корневой элемент <book>, несмотря на то, что является единственным на-
следником корня, входит в список узлов (NodeList). Это общее правило:
элементы-наследники любого родителя объединяются в список. Объект
NodeList предоставляет программисту набор свойств и методов для работы
с таким списком.
64 Часть I. Технологии, составляющие AJAX

Список атрибутов какого-либо элемента носит специальное название —


NamedNodeMap.
Браузеры конвертируют полученные с сервера XML-документы в дерево
DOM. Объекты DOM уже доступны сценариям JavaScript. Серверные сцена-
рии также могут оперировать объектными деревьями, создавая, читая или
изменяя документы.

Объект Node
Базовым объектом DOM является объект Node, методы которого наследуют
прочие объекты — элементные узлы, атрибуты и др. Объекты-наследники
имеют также характерные только им свойства и методы.
В DOM для объекта Node, представляющего узлы документа, определены
свойства и методы, которые могут быть использованы для обработки доку-
ментов. Вот некоторые из них:
nodeName — имя узла;
nodeValue — значение узла;
nodeType — тип узла в виде числа;
textContent — текстовое содержимое узла и его наследников;
parentNode — родитель данного узла;
childNodes — список детей данного узла;
attributes — список атрибутов данного узла;
firstChild — первый дочерний узел данного узла;
lastChild — последний дочерний узел данного узла;
nextSibling — следующий узел, дочерний элемент того же родительско-
го элемента;
previousSibling — предыдущий дочерний элемент, обладающий тем же
родительским элементом, что и данный узел.
Свойство childNodes возвращает упорядоченный список узлов (т. е. объект
класса NodeList). Нумерация узлов в списке начинается с 0. Свойство масси-
ва length показывает количество узлов в списке. Список узлов перестраива-
ется в случае удаления или добавления узла автоматически.
Свойство attributes элементного узла возвращает список атрибутных узлов
(NamedNodeMap), подобный списку узлов за исключением некоторых отличий
методов и свойств. Аналогично массиву элементных узлов массив атрибутов
перестраивается автоматически при удалении или добавлении атрибутов.
Глава 5. Объектная модель документа 65

Отметим некоторые методы объекта Node:


appendChild(node) — добавляет узел-наследник. Привязывает новый узел
к дереву, ставя его последним в списке дочерних узлов данного узла;
removeChild(node) — удаляет указанный узел-наследник;
hasChildNodes() — возвращает значение true, если узел имеет хотя бы
один дочерний узел;
insertBefore(newNode, refNode) — вставляет новый узел, newNode, пе-
ред существующим узлом refNode;
replaceChild(newNode, oldNode) — заменяет старый узел oldNode новым
узлом newNode.

Свойства и методы объекта Document


Объект Document представляет корень документа и предоставляет доступ
к данным документа. Поскольку элементы, текстовые узлы, комментарии и дру-
гие узлы являются частью документа, то объект Document также содержит мето-
ды создания узлов различных типов. Опишем теперь свойства и методы (табл. 5.2
и 5.3) объекта Document, представляющего весь XML-документ в целом.
Напомним, что объект Document наследует объекту Node и не будет перечис-
лять наследуемые свойства и методы. Среди свойств данного объекта
в табл. 5.2 указано и свойство async, не являющееся частью стандарта W3C,
но играющее важную роль в технологии AJAX.
Таблица 5.2. Свойства объекта Document
Свойство Описание
async Определяет, будет ли загрузка XML-документа с сервера
осуществляться асинхронно или нет
documentElement Возвращает корневой элемент документа
inputEncoding Возвращает входную кодировку документа
xmlEncoding Возвращает XML-кодировку документа
Таблица 5.3. Методы объекта Document
Метод Описание
createAttribute(name) Создает атрибутный узел с указанным именем
и возвращает новый объект Attr
createComment() Создает узел комментария
66 Часть I. Технологии, составляющие AJAX
Таблица 5.3 (окончание)
Метод Описание
createDocumentFragment() Создает пустой объект DocumentFragment
и возвращает его
createElement() Создает элементный узел
createProcessingInstruction Создает узел ProcessingInstruction
(target, data) и возвращает его
createTextNode() Создает текстовый узел
getElementById(id) Возвращает элемент с указанным значением
идентификатора ID, если подходящего эле-
мента нет, то возвращает null
getElementsByTagName() Возвращает список NodeList элементов
с указанным тегом

Доступ к узлу DOM


Получить доступ к узлу можно одним из следующих способов:
используя метод getElementsByTagName();
перебирая поочередно в цикле все узлы дерева;
двигаясь по дереву, используя отношения между узлами.

Объект Element
Объект Element наследует свойства объекта Node и имеет ряд своих специ-
фических свойств, которые можно читать и изменять (табл. 5.4 и 5.5).
Таблица 5.4. Свойства объекта Element
Свойство Описание
Attributes Возвращает список атрибутов элемента в виде объекта
NamedNodeMap

tagName Возвращает имя элемента


textContent Устанавливает или возвращает текстовое содержимое узла
и вложенных в него элементов
Глава 5. Объектная модель документа 67

Таблица 5.5. Методы объекта Element


Метод Описание
getAttribute() Возвращает значение атрибута
getElementsByTagName() Возвращает список элементов, указанных по имени,
и их детей
hasAttribute(name) Возвращает true или false в зависимости от того,
есть ли у элемента атрибут name
hasAttributes() Возвращает true или false в зависимости от того,
есть ли у элемента атрибуты
hasChildNodes() Возвращает true или false в зависимости от того,
есть ли у элемента вложенные элементы
removeAttribute() Удаляет указанный атрибут
removeAttributeNode() Удаляет указанный атрибутный узел
setAttribute() Добавляет новый атрибут

Объект NodeList
Объект NodeList представляет упорядоченный список узлов. Узлы в списке
можно перебрать по их номерам, начиная с нуля.
Объект NodeList всегда содержит актуальную информацию. Если элемент
удаляется из документа или, наоборот, добавляется к документу, список уз-
лов автоматически обновляется. Элементы указываются в списке в том по-
рядке, в котором указаны в самом документе.
Свойство length объекта NodeList возвращает число узлов в списке.
Метод item возвращает тот узел, номер которого указан в качестве аргумента
этого метода.

Объект NamedNodeMap
Атрибуты элемента перечислены в NamedNodeMap — списке атрибутов. Этот
объект представляет собой неупорядоченный список узлов. Доступ к узлам
в объекте NamedNodeMap осуществляется по имени атрибута.
Объект NamedNodeMap всегда содержит актуальную информацию: если ат-
рибут добавляется или удаляется, то список атрибутов обновляется автома-
тически.
68 Часть I. Технологии, составляющие AJAX

Объект NamedNodeMap располагает свойством length. Это свойство возвра-


щает количество атрибутных узлов в списке.
Перечислим некоторые методы объекта NamedNodeMap:
getNamedItem — возвращает указанный по имени узел;
item — возвращает указанный по номеру узел, номер отсчитывается с нуля;
removeNamedItem — удаляет указанный по имени узел.

Объект Attr
Объект Attr возвращает атрибут элементного объекта как атрибутный узел.
Объект Attr имеет те же самые свойства и методы, что и остальные узлы
в целом. Свойства, специфичные для объекта Attr, таковы:
name — возвращает или устанавливает имя атрибута;
value — возвращает или устанавливает значение атрибута.

Объект Text
Этот объект представляет собой текстовое содержимое элемента или атрибу-
та. Для дальнейшего разговора нам понадобятся его:
свойство data, возвращающее или устанавливающее текстовое содер-
жимое;
метод appendData, добавляющий текст к содержимому узла.

Объект DOMImplementation
Объект DOMImplementation доступен через свойство implementation объекта
Document. Нам понадобится его метод createDocument, который создает до-
кумент в виде дерева DOM.
Глава 6

DOM в JavaScript

Объект Element
Объект Element в JavaScript представляет собой реализацию одноименного
объекта DOM с добавкой свойств и методов, применяемых для манипуляции
с элементом и для его отображения на странице. Укажем в табл. 6.1 те свой-
ства, которые поддерживает объект Element в добавление к свойствам, при-
веденным в главе 5.
Таблица 6.1. Свойства объекта Element
Свойство Описание
style Объект, представляющий стиль отображения элемента
tagName Имя тега элемента
textContent Возвращает или устанавливает текстовое содержимое элемен-
та и его потомков
innerHTML Возвращает или устанавливает содержимое и разметку внутри
элемента
className Возвращает или устанавливает стилевой класс
name Возвращает или устанавливает значение атрибута name
элемента
id Возвращает или устанавливает идентификатор элемента
clientHeight Внутренняя высота элемента
clientLeft Ширина левой границы элемента
clientTop Ширина верхней границы элемента
clientWidth Внутренняя ширина элемента
offsetHeight Высота элемента в скомпонованной странице
70 Часть I. Технологии, составляющие AJAX
Таблица 6.1 (окончание)
Свойство Описание
offsetLeft Расстояние от левой границы элемента до левой границы эле-
мента offsetParent
offsetParent Элемент, от которого ведутся расчеты сдвига текущего элемента
offsetTop Расстояние от верхней границы элемента до верхней границы
элемента offsetParent
offsetWidth Ширина элемента в скомпонованной странице
scrollHeight Видимая высота прокручиваемого элемента
scrollLeft Возвращает или устанавливает размер прокрутки в окне влево
для элемента
scrollTop Возвращает или устанавливает размер прокрутки в окне вниз
для элемента
scrollWidth Видимая ширина прокручиваемого элемента

Полезность полного перечня свойств и методов любого из объектов, описы-


ваемого стандартом DOM, ограничена тем, что не все свойства и методы
поддерживаются браузерами. Здесь и в дальнейшем в сводных таблицах бу-
дут приводиться только методы, поддерживаемые хотя бы несколькими из
браузеров. Наилучшую поддержку стандартов обеспечивает браузер Opera,
но при создании Web-страниц необходимо предусматривать корректные ре-
шения для всех популярных браузеров.
Рассмотрим теперь методы объекта Element (табл. 6.2).
Таблица 6.2. Методы объекта Element, определенные для HTML-документов
Имя Описание
Blur() Переносит фокус с текущего элемента
click() Симулирует щелчок по текущему элементу
focus() Переносит фокус на текущий элемент
addEventListener() Задает обработчик некоторого события для эле-
мента
dispatchEvent(event) Передает событие, связанное с этим элементом,
в DOM
getElementsByClassName() Отбирает элементы по имени стилевого класса
removeEventListener() Удаляет обработчик события для данного элемента
Глава 6. DOM в JavaScript 71

Пример из листинга 6.1 демонстрирует применение метода getElementById()


объекта Document и установку значения свойства innerHTML объекта Element.
Функция poisk() ищет в текущем документе HTML-элемент по указанному
значению атрибута id и изменяет содержимое найденного элемента.
Листинг 6.1. Использование метода и свойства
<html>
<head>
<title>Example 1</title>
<script type="text/javascript">
function poisk()
{
myDiv = document.getElementById("myElement");
myDiv.innerHTML = "Привет!";
}
</script>
</head>
<body onload="poisk()">
<div>Какой-то контент страницы.</div>
<div id="myElement">
</body>
</html>

Приведенный сценарий является отправной точкой для использования техно-


логии AJAX. Суть его в том, что в результате действий клиента оказывается
возможным изменить фрагмент Web-страницы, не обновляя страницу в целом.

Создание HTML-элемента с помощью


методов DOM и включение его в дерево
документа
С помощью методов DOM можно динамически создавать ветки элементов и
подключать их к дереву документа на странице, как это делается в листинге 6.2.
Листинг 6.2. Создание элемента и включение его в документ
<html>
<head>
<title>AJAX</title>
72 Часть I. Технологии, составляющие AJAX
<script type="text/javascript" >
function obrabotka()
{
paragraf = document.createElement("b");
soderzanie = document.createTextNode("Какой угодно текст.");
paragraf.appendChild(soderzanie);

myDiv = document.getElementById("my");
myDiv.appendChild(paragraf);
}
</script>
</head>
<body onclick="obrabotka()">
<div id="my" />
</body>
</html>

В этом примере использован метод createElement() объекта Document. С его


помощью создается элемент документа, имя которого указывается в качестве
параметра метода.
Метод appendChild() применяется к тому элементу, после которого он пи-
шется, этот метод присоединяет указанный в качестве аргумента элемент
к списку дочерних элементов.
При щелчке по любой области документа в браузере вызывается функция
obrabotka(). В ней сначала методом createElement() создается новый параграф,
затем методом createTextNode() формируется текстовый узел. Этот узел включа-
ется в список дочерних элементов элемента paragraf методом appendChild().
Далее уже известным нам методом getElementById() ищем тот элемент,
в список дочерних элементов которого мы хотим включить элемент paragraf.
Делаем это опять же методом appendChild().
Метод createElement создает элемент документа, имя которого указывается
в качестве параметра метода. Затем методом createTextNode создается тек-
стовый узел. Метод appendChild() присоединяет указанный в качестве ар-
гумента элемент к списку дочерних элементов.
Методом getElementById() ищем тот элемент, в список дочерних элементов
которого мы хотим включить элемент paragraf, и присоединяем созданную
ветку к исходному документу.
Обратите внимание, что создать элемент недостаточно, он не будет отра-
жаться в окне браузера, необходимо подсоединить его к имеющемуся дереву.
Глава 6. DOM в JavaScript 73

Чтение данных из XML-документа


Посмотрим, как сценарий JavaScript может анализировать XML-документ
и добавлять на страницу данные из этого документа. Возьмем знакомый уже
файл car.xml (см. листинг 4.1), загрузим его в DOM и прочитаем значение
какого-либо узла из этого документа (листинг 6.3).
Используем здесь функцию loadXMLDoc, которая принимает в качестве пара-
метра имя файла с XML-документом. Она загружает указанный документ
в DOM. Функция корректно работает в различных браузерах за счет того, что
в ней предусмотрены разные способы создания документа: в Internet
Explorer — за счет библиотеки ActiveXObject, в остальных браузерах обес-
печена поддержка метода createDocument объекта DOMImplementation, что
и используется. Функция loadXMLDoc возвращает объект Document.
Листинг 6.3. Загрузка XML-документа и его анализ
<html>
<head>
<title>firstChild.data</title>
<script type="text/javascript">
function getText(){
function loadXMLDoc(dname)
{
try { // Internet Explorer
xmlDoc=new ActiveXObject("Microsoft.XMLDOM"); }
catch(e)
{
try //Firefox, Mozilla, Opera, etc.
{
xmlDoc=document.implementation.createDocument("","",null);
}
catch(e) {alert(e.message)}
}
try
{
xmlDoc.async=false;
xmlDoc.load(dname);
return(xmlDoc);
}
74 Часть I. Технологии, составляющие AJAX
catch(e) { alert(e.message) }
return(null);
}
var el = document.getElementById("ok");
xmlDoc = loadXMLDoc("car.xml");
x=xmlDoc.getElementsByTagName('color');
el.innerHTML = x[0].firstChild.data;
}
</script>
</head>
<body onload="getText();">
<div id="ok"/>
</body>
</html>

Из полученного документа методом getElementsByTagName извлекаем эле-


мент color. Нам интересно его текстовое содержимое, которое является пер-
вым наследником элемента, чем мы пользуемся, выбирая значение свойства
firstChild. Полученный узел будет текстовым, поэтому его содержание
можно прочитать из свойства data.
В заключение хочется привести список методов, используемых наиболее ши-
роко и составляющих, как выразились на просторах Рунета, малый джентль-
менский набор Web-программиста:
getElementById;
getElementsByTagName;
createElement;
createTextNode;
appendChild;
removeChild.
Глава 7

DOM- функции в PHP


Объектная модель реализована в PHP 5 в соответствии со стандартом DOM
Level 3, как выражаются в документации, "настолько, насколько это оказа-
лось возможным".
Для уверенного владения методами создания HTML- и XML-документов
придется обстоятельно разобраться с классами, описанными в разделе "DOM
functions" документации по PHP. Это семейство функций позволяет извле-
кать данные из XML-документов, создавать, изменять и сохранять XML-
документы.
Создание XML- или XHTML-документа следует представлять себе как по-
строение дерева узлов, при этом для присоединения нового узла или целой
ветви узлов нужно их сначала создать, указав все необходимые параметры.
Затем созданные элементы следует включить в уже имеющееся дерево, ука-
зав точку прикрепления нового узла или ветки к существующему дереву.
Основой работы с XML в PHP является библиотека libxml, информацию
о которой можно найти на сайте http://www.xmlsoft.org. Она поддерживает
основные стандарты XML: пространства имен, XML-схемы, XPath и многие
другие. На том же сайте можно найти информацию о библиотеке libxslt.
Обе эти библиотеки поддерживают ограниченное количество кодировок, из
которых интерес с точки зрения русскоязычного программиста представля-
ют: UTF-8, UTF-16 и ISO-8859-1. Следовательно, текстовые данные в созда-
ваемых с помощью этих библиотек XML-документах обычно записываются
в кодировке UTF-8. Если же вы указываете входные данные в иной кодиров-
ке, то функции библиотеки обычно правильно распознают кодировку и пре-
образуют текст в UTF-8.
При работе с этой библиотекой используются предопределенные константы,
позволяющие управлять обработкой XML-документа, в табл. 7.1 указаны
некоторые из них, существенные для описываемых в настоящей книге задач.
76 Часть I. Технологии, составляющие AJAX
Таблица 7.1. Константы библиотеки libxml
Константа Описание
LIBXML_NOENT Замена всех сущностей в документе на значение этих сущ-
ностей
LIBXML_NOERROR Подавлять сообщения библиотеки libxml об ошибках, кото-
рые могут возникнуть при обработке
LIBXML_NOWARNING Подавлять предупреждения библиотеки libxml, которые мо-
гут возникнуть при парсировании
LIBXML_NOBLANKS Удалять пробельные символы из документа
LIBXML_NOCDATA Преобразовывать узлы CDATA в текстовые

Классы, определенные в расширении DOM-функций, являются реализацией


DOM, а поэтому их имена повторяют названия объектов DOM с приставкой
DOM. Исключение составляет класс DOMDocumentFragment, не являющийся
частью модели DOM. Нужен этот класс для того, чтобы в документ можно
было импортировать целый фрагмент документа, а не только один узел.
Метод у него один: appendXML() — вставить данные в формате XML (raw
data, как говорится в документации, т. е. эти данные не оформлены в виде
правильного XML-документа).

Создание XML-документа с помощью


DOM-функций
Создадим простой XML-документ, содержащий только пустой корневой эле-
мент (листинг 7.1). В начале следует вызвать конструктор класса
DOMDocument и передать ему два параметра — версию языка XML и кодиров-
ку создаваемого документа. Если в документе хочется писать по-русски, не-
обходимо указать кодировку UTF-8, иначе будет применяться кодировка по
умолчанию — iso-8859-1. Это приведет к тому, что русские буквы в XML-
документе будут заменены их сущностями (например, буква "а" будет заме-
нена кодом &#x430;), что нисколько не поможет нам в дальнейшем. Объект
$doc, который будет создан, представляет документ в целом, но пока доку-
мент пуст. Единственное, что в нем имеется, — корень документа, на кото-
рый и указывает $doc.
Метод createElement позволяет создать элемент с указанным именем car.
Созданный в результате объект $node является объектом класса DOMElement.
Следующее, что нужно сделать с объектом $node, — это включить в дерево
документа, присоединив к корню методом appendChild.
Глава 7. DOM-функции в PHP 77

Листинг 7.1. Создание XML-документа


<?php
$doc = new DOMDocument('1.0', 'utf-8');
$node = $doc->createElement("car");
$doc->appendChild($node);
header('Content-Type: text/xml');
echo $doc->saveXML();
?>

Для того чтобы передать клиенту созданный документ, нам подойдет метод
saveXML класса DOMDocument, который возвращает документ в виде строки.
Для корректного отображения XML-документа в браузере необходимо пере-
дать заголовок 'Content-Type: text/xml'.
Создадим теперь XML-документ, похожий на тот, что описан в главе 4 в лис-
тинге 4.1. Сначала создадим корневой элемент методом createElementNS
сразу с указанием пространства имен (листинг 7.2).
Затем создадим атрибут методом setAttribute класса DOMElement.
Воспользуемся далее оператором new, чтобы вызывать конструкторы нужных
нам классов DOMElement и DOMText. Последний отвечает за создание тексто-
вого узла.
Листинг 7.2. XML-документ
<?php
$doc = new DOMDocument("1.0","utf-8");
$root = $doc->createElementNS('http://myford.ru', 'car');

$doc->appendChild($root);
$root->setAttribute('id', 'f327');

$make = $root->appendChild(new DOMElement("make"));


$make->appendChild (new DOMText("Ford"));

$model = $root->appendChild(new DOMElement("model"));


$model->appendChild(new DOMText("Focus"));

$date = $root->appendChild(new DOMElement("date"));


$year = new DOMElement("year");
78 Часть I. Технологии, составляющие AJAX
$date->appendChild($year);
$year->appendChild(new DOMText("2008"));

$month = new DOMElement("month");


$date->appendChild($month);
$month->appendChild(new DOMText("Май"));

$color = $root->appendChild(new DOMElement("color"));


$color->appendChild(new DOMText("красный"));

header('Content-Type: text/xml');
echo $doc->saveXML();
?>

Вот и все, скрипт получился однообразным, но вполне читабельным, в брау-


зер отправляем правильный XML-документ, корректно отображающий рус-
ский текст. Наша главная задача по генерации XML-документа на серверной
стороне успешно выполнена. Конечно, данные для текстовых узлов могут
браться из базы данных, запрашиваться с других серверов и т. д.
Осталось для лучшего понимания работы с DOM-функциями посмотреть, как
с их помощью можно читать и редактировать готовый XML-документ.
В следующем сценарии (листинг 7.3) мы загрузим документ из файла car.xml
и прочитаем значения всех его текстовых узлов.
Листинг 7.3. Чтение текстовых узлов XML-файла
<?php
$doc = new DOMDocument('1.0', 'utf-8');
$doc->load("car.xml", LIBXML_NOBLANKS);
$root = $doc->documentElement;

if ($root->hasChildNodes()) {
$children = $root->childNodes;
foreach($children as $node) {
print $node->nodeValue."<br />";
}
}
?>
Глава 7. DOM-функции в PHP 79

Сначала создаем новый пустой документ в кодировке UTF-8, указав его ко-
рень в переменной $doc. Применим здесь метод load объекта DOMDocument
для загрузки документа из файла. Для того чтобы удалить лишние пробель-
ные символы, укажем константу LIBXML_NOBLANKS. Нам это удобно потому,
что в противном случае символы перехода на новую строку, которых предос-
таточно в нашем файле, будут преобразованы в текстовые узлы. Метод load
прочитает файл, построит из него дерево и вернет готовое дерево, подключив
его к корню нашего пустого документа.
Затем мы хотим перейти к корневому элементу и перебрать все вложенные
в него элементы. Для этого читаем свойство documentElement объекта
DOMDocument и записываем полученный объект в переменную $root.
Метод hasChildNodes поможет нам выяснить, есть ли элементы, вложенные
в корневой элемент $root. Такая проверка не является необходимой для дос-
тупа к узлам, но служит защитой от бессмысленных действий в случае каких-
либо ошибок при загрузке документа.
Метод childNodes объекта DOMNode, а корневой элемент, конечно, является та-
ким объектом, позволяет нам получить список элементов-наследников. Список
этот будет объектом класса DOMNodeList, и мы сохраним его в переменной
$children. Список же можно перебрать в цикле foreach, что мы и делаем, из-
влекая при этом значения свойства nodeValue всех отбираемых узлов.
Пример получился простым, но обратите внимание на одну деталь, на экране
вы увидите вот что:
Ford
Focus
2008май
текст комментария
красный
Почему в этом выводе на экран текстовые узлы "2008" и "май" оказались на од-
ной строке? Дело в том, что мы отбирали только узлы-наследники первого поко-
ления, не разбирая, что вложено в них. Поэтому и получилось, что значением
свойства nodeValue узла date оказалось все текстовое содержимое этого узла.
Посмотрим, как можно отобрать значения элементов, задаваясь конкретными
именами тегов (листинг 7.4).
Листинг 7.4. Перебор элементов списка NodeList
<?php
$dom = new DOMDocument('1.0', 'utf-8');
$dom->load("car.xml", LIBXML_NOBLANKS);
80 Часть I. Технологии, составляющие AJAX
$date = $dom->getElementsByTagName("date")->item(0);
$list=$date->childNodes;
$length = $list->length;
for ($x=0; $x < $length; $x++) {
print "Element Value: ".$list->item($x)-> textContent"<br />";
}
?>

Загрузим наш XML-документ и вызовем метод getElementsByTagName, кото-


рый позволит нам получить все элементы, созданные тегом <date>. Такой
элемент у нас один, но метод все равно возвращает список узлов в виде объ-
екта DOMNodeList. Поэтому для дальнейшей работы придется вызвать метод
item и указать ему, что следует выбрать нулевой элемент из списка.
Затем уже знакомым свойством childNodes отберем элементы-наследники.
У полученного списка $list есть длина, значение которой содержится
в свойстве length. Теперь все готово для того, чтобы в цикле перебрать тек-
стовое содержимое всех элементов из полученного списка. На этот раз вос-
пользуемся свойством textContent объекта DOMNode, чтобы прочитать нуж-
ную нам информацию.
Глава 8

Проблема русификации
Web-приложений
Чем отличается русскоязычный пользо-
ватель Интернета? Он думает "www",
набирает "ццц", а произносит "ввв".
Народная мудрость
Каждая технология, используемая при создании Web-приложений, имеет
свои особенности русификации. Эти особенности таковы, что требуют спе-
циальной настройки серверной и клиентской частей приложений и заслужи-
вают подробного разговора.

Кодировки
Набор символов (character set) — определенная таблица кодировки конечного
множества знаков. Такая таблица сопоставляет каждому символу последова-
тельность длиной в один или несколько байтов.
Кодовая страница (code page) — таблица, сопоставляющая каждому значе-
нию байта некоторый символ. Кодовая страница может содержать максимум
256 символов, из чего вытекает недостаточность всякой 8-битной кодовой
страницы для представления многоязычных текстов.
Есть несколько кодировок, знакомства с которыми при разработке Web-
приложений не избежать. Приведем пусть и широко известные, но необхо-
димые сведения о них.
ASCII (American Standard Code for Information Interchange, американский
стандартный код для обмена информацией) — 7-битная компьютерная ко-
дировка для представления латинского алфавита, десятичных цифр, зна-
ков препинания, арифметических операций и управляющих символов.
ISO-8859-1 — кодовая страница, предназначенная для западноевропейских
языков. Кодовые позиции 0—31 и 127—159 здесь заполнены управляющи-
ми символами. В HTML ISO-8859-1 является кодировкой по умолчанию.
82 Часть I. Технологии, составляющие AJAX

Альтернативная кодировка, или кодировка IBM CP866, поддержка кото-


рой была добавлена в MS-DOS версии 6.22. В этой кодировке записыва-
ются имена файлов в системе FAT. CP866 до сих пор используется в кон-
соли русифицированных систем семейства Windows NT.
Windows-1251 — набор символов и кодировка, являющаяся стандартной
8-битной кодировкой для всех русских версий Microsoft Windows.
Часто встречается также обозначение ANSI Cyrillic — одно из названий
кодовой страницы Windows-1251. Происходит оно от названия стандар-
тов, разрабатываемых американским национальным институтом стандар-
тов (American National Standards Institute, ANSI).
КОИ-8 — восьмибитовая ASCII-совместимая кодовая страница, разрабо-
танная для кодирования букв кириллических алфавитов. Существует
несколько вариантов кодировки КОИ-8 для различных кириллических ал-
фавитов. Русский алфавит описывается в кодировке KOI8-R.
Юникод, или Уникод (англ. Unicode) — стандарт кодирования символов,
позволяющий представить знаки практически всех письменных языков.
Коды в стандарте Unicode разделены на несколько областей. Область
с кодами от +0000 до +007F содержит символы набора ASCII с соответст-
вующими кодами. Далее расположены области знаков различных пись-
менностей, знаки пунктуации и технические символы. Часть кодов заре-
зервирована для использования в будущем. Под символы кириллицы
выделены коды от +0400 до +052F. Поскольку в ряде компьютерных сис-
тем уже были реализованы 16-битные символы, было решено все наибо-
лее важное кодировать только в пределах первых 65 536 позиций.
UTF-8 — это представление Юникода, обеспечивающее наилучшую со-
вместимость со старыми системами, использовавшими 8-битные символы.
Текст, состоящий только из символов с номером меньше 128, при записи
в UTF-8 превращается в обычный текст ASCII.
Символы UTF-8 получаются из Unicode следующим образом (табл. 8.1).
Таблица 8.1. Кодирование символов в UTF-8
Unicode UTF-8
0x00000000 — 0x0000007F 0xxxxxxx
0x00000080 — 0x000007FF 110xxxxx 10xxxxxx
0x00000800 — 0x0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
0x00010000 — 0x001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
Глава 8. Проблема русификации Web-приложений 83

Передача локализованных данных


в протоколе HTTP
Клиент может передать в HTTP-запросе серверу информацию о поддержи-
ваемых типах данных и кодировках в заголовках (табл. 8.2).
Таблица 8.2. Заголовки HTTP, устанавливающие локализационные данные
Заголовок Описание
Accept Задает типы файлов MIME, приемлемые в отклике. Пример:
Accept: text/*, text/html
Accept-Charset Задает кодировки символов, приемлемые в отклике. Пример:
Accept-Charset: iso-8859-5.
Сервер должен по возможности передавать данные в указан-
ной клиентом кодировке
Accept-Language Задает естественные языки, приемлемые в отклике. Пример:
Accept-Language: ru, en-us, en

При запросе методом GET данные передаются в виде значений переменных.


Переменные добавляются к URL запрашиваемого ресурса в следующем виде:
http://mysite.ru?name=Vasya

В стандарте URL можно использовать только ограниченный набор символов:


латинские буквы, цифры и лишь некоторые знаки препинания. Для передачи
в URL символов кириллицы нужно перекодировать эти символы особым об-
разом. Преобразование происходит в два этапа: сначала каждый символ ки-
риллицы кодируется в Unicode (UTF-8) в последовательность из двух байтов,
а затем каждый байт этой последовательности записывается в шестнадцате-
ричном представлении:
М → D0 и 9C → %D0%9C
и → D0 и B8 → %D0%B8
к → D0 и BA → %D0%BA
р → D0 и 80 → %D0%80

и т. д.
Перед каждым шестнадцатеричным кодом байта ставится знак процента. При
таком кодировании на один символ уходит 3 байта.
Альтернативой URL-кодированию является схема base64. Суть этой схемы
состоит в кодировании передаваемой информации 6-битными символами.
84 Часть I. Технологии, составляющие AJAX

Алгоритм преобразований в base64 таков: символы представляются в виде их


двоичных кодов. Алгоритм разбивает эту последовательность бит на группы
по 6 бит, а если бит не хватает, то дописывает в конец нули. В этом алгорит-
ме три 8-битных символа преобразуются в четыре 6-битных.
Если браузер не сообщает кодировку в заголовке Accept-Charset, то сервер
использует свою кодировку по умолчанию или ту, что сообщит браузеру сер-
верное приложение.
Кодировка сервера Apache по умолчанию устанавливается директивой
AddDefaultCharset в конфигурационном файле httpd.conf. По причинам,
подробно изложенным в этой главе далее, в настоящее время следует реко-
мендовать использовать только кодировку UTF-8, т. е. директива конфигура-
ции должна выглядеть так:
AddDefaultCharset UTF-8

Сервер может передать клиенту информацию о языке и кодировке в следую-


щих заголовках (табл. 8.3).
Таблица 8.3. Заголовки ответа сервера
Поле Описание
Content-Language Задает естественный язык, на котором написано тело со-
общения. Пример: Content-Language: ru
Content-Type Содержит тип MIME тела сообщения. Пример:
Content-Type: text/html; charset=windows-1251

При передаче данных в формате plain text заголовок Content-type является


единственным способом передачи сведений о кодировке.
В XML-документе кодировка может передаваться в XML-декларации. На-
пример:
<?xml version="1.0" encoding="windows-1251"?>

В HTML-документе кодировку следует указывать в теге meta:


<meta http-equiv="content-type" content="text/html; charset=windows-
1251"/>

П РИМЕЧАНИЕ ДЛЯ НАЧИНАЮЩИХ


Само по себе указание кодировки в теге meta не приводит к тому, что весь
текст документа буден записан в этой кодировке, т. е. указать-то можно UTF-8,
а писать при этом, например, в Windows-1251. Надо еще следить за настрой-
ками текстового редактора, который используется для написания программы.
Используйте только те редакторы, которые явно указывают, в какой кодировке
Глава 8. Проблема русификации Web-приложений 85

записывается текст, например, Notepad++. Как советуют опытные люди (см.


http://webmastak.com/article.aspx?id=300), при сохранении файла многие тек-
стовые редакторы предлагают флажок Include Unicode Signature (BOM), Add
Byte Order Mark или нечто подобное. Прежде всего, убедитесь, что в вашем ре-
дакторе это есть. Если похожей настройки не обнаружено — пользоваться таким
редактором для серьезных задач не стоит. Найдя этот флажок, отключите его.
Byte Order Mark (BOM) — это три служебных байта, которые автоматически за-
писываются в начало документа и обозначают, что он сохранен в кодировке UTF.

Если кодировка указана в двух местах: в HTTP-заголовке и в самом докумен-


те, то браузер должен использовать кодировку, указанную в документе.
Если данных о кодировке нет, то браузер либо выбирает кодировку, установ-
ленную в нем по умолчанию, либо пытается определить ее автоматически, по
содержимому.

Кодирование символов
в сценарии JavaScript
Если в сценарии есть не ASCII-символы, то надо побеспокоиться о том,
чтобы текст сценария был в той же кодировке, что и HTML-страница, в кото-
рую он подключается.
Если сценарий динамически создает элементы, имеющие внешние ссылки, то
компоненты строки нужно закодировать методом escape глобального объек-
та JavaScript. Метод escape преобразует входную строку в шестнадцатерич-
ную кодировку. При этом все символы, не являющиеся символами латиницы,
заменяются на их шестнадцатеричные escape-коды %xx.
Глобальный объект создается исполняющей системой JavaScript перед нача-
лом исполнения сценария и располагает методами, указанными в табл. 8.4.
Таблица 8.4. Методы глобального объекта
Метод Описание
decodeURI Декодирует URI
decodeURIComponent Декодирует компонент URI
encodeURI Кодирует URI
encodeURIComponent Кодирует компонент URI

Преобразует строку в шестнадцатеричную кодировку


escape
Unicode
Преобразует шестнадцатеричную кодировку Unicode
unescape
в строку
86 Часть I. Технологии, составляющие AJAX

Для кодировки строк, содержащих URI, следует использовать методы


encodeURI и encodeURIComponent. Разберитесь в этом хорошенько, поэкспе-
риментировав с этими методами, например, в таком сценарии, как тот, что
представлен в листинге 8.1.
Листинг 8.1. Методы глобального объекта
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>encodeURI</title>
<script>
document.write(encodeURI("http://www.test.com?val=привет"))
</script>
</head>
<body> </body>
</html>

Русский язык в PHP


Для работы со строками в кодировке UTF-8 к модулю PHP необходимо подклю-
чать расширение для работы с многобайтными кодировками php_mbstring.dll, для
этого на этапе установки PHP надо отметить расширение multi-byte string.
Необходимо помнить, что стандартные функции для работы с регулярными
выражениями, для преобразования и сравнения строк в UTF-8 выполняются
функциями, имена которых начинаются с префикса mb. Например, преобра-
зование строки к верхнему регистру выполняется функцией mb_strtoupper.
Локализация в PHP производится по тем же правилам, что и вообще в UNIX,
т. е. существует понятие локали (от англ. locale), определяющей формат вы-
вода данных. Локализовать можно вывод текста, стандартных диагностиче-
ских сообщений, формируемых приложениями, формат вывода даты и вре-
мени, денежных показателей и т. п. В PHP определен ряд именованных
констант для задания категорий локализации:
LC_ALL — все из нижеперечисленного;
LC_COLLATE — для сравнения строк;
LC_CTYPE — для преобразований как, например, в функции strtoupper;
LC_MONETARY — для форматирования денежных данных функцией
localeconv;
Глава 8. Проблема русификации Web-приложений 87

LC_NUMERIC — для разделителя целой и дробной части числа;


LC_TIME — для форматирования даты и времени в функции strftime;
LC_MESSAGES — для системных диагностических сообщений.
Установить параметры локализации можно функцией setlocale, указав, на-
пример, так:
setlocale(LC_TIME, 'RUS');
echo strftime('%A', time());

И все бы хорошо, но работать это будет корректно, только пока ваш сервер
настроен на дефолтовую кодировку Windows-1251. А если вы настроили на
UTF-8, то с удивлением обнаруживаете, что функция strftime в приведен-
ном примере выдает русское название дня недели по-прежнему в кодировке
Windows-1251. Приходится применять функцию iconv:
setlocale(LC_ALL, 'RUS');
echo iconv('windows-1251', 'UTF-8', strftime('%A'));

Функция iconv служит для преобразования кодировки строки: в первом ее


параметре — входная кодировка, во втором — выходная, а в третьем — пре-
образуемая строка.
Вообще, внутреннее представление данных в PHP осуществляется в UTF-8.
На практике это означает, что функции PHP анализируют входные данные,
причем некоторые функции могут правильно определить входную кодиров-
ку, а некоторые нет.
Скажем, функции графической библиотеки gd2 рассматривают входной текст
как строку в кодировке UTF-8. Например, так ведет себя функция imagettftext,
которая пишет текст на динамически создаваемом рисунке, но результат бу-
дет удобочитаемым, только если передаваемый ей текст действительно напи-
сан в кодировке UTF-8. То есть автоматического преобразования входных
данных в строку с кодировкой UTF-8 не производится, строку в иной коди-
ровке требуется перекодировать с помощью функции iconv.
Функции для работы с XML-документами корректно распознают входную
кодировку парсируемого текста, автоматически преобразуют в UTF-8, вы-
полняют необходимые преобразования и генерируют результат в кодировке
UTF-8. Это относится к функциям SAX Parser, классу SimpleXML и DOM-
функциям. Если вы рассчитываете, что пользователь читает, скажем, новост-
ную ленту в Windows-1251, то опять придется обращаться к iconv.
При этом для добавления текстового узла в XML-документ с помощью
функций SimpleXML опять же надо побеспокоиться о том, чтобы добавляемая
строка была в UTF-8. Разумеется, настройка всех составляющих клиент-
88 Часть I. Технологии, составляющие AJAX

серверного взаимодействия в этом случае на UTF-8 снимает нужду в функ-


ции iconv.
Но есть и еще одна проблема с кодировками, возникающая при работе
с форматом JSON в AJAX. Функция json_encode, предназначенная для пре-
образования данных в JSON, выдает вместо русских букв коды, как это видно
на следующем примере (листинг 8.2).

Листинг 8.2. Работа функции json_encode с русскими строками


<?php
class Table {
public $material = "дерево";
}
$test = new Table;
echo json_encode($test);
?>

Этот простой пример выдаст вам следующее:


{"material":"\u0434\u0435\u0440\u0435\u0432\u043e"}

Результат едва ли можно признать удовлетворительным, т. е. мы получили


русские буквы, но представлены они в виде кодов. Вообще говоря, для по-
добных случаев приходится применять функции преобразования символов
вроде представленной в листинге 8.3.

Листинг 8.3. Функция преобразования кодов


<?php
function String_RusCharsDeCode($string)
{
$russian_codes = ar-
ray('\u0410','\u0411','\u0412','\u0413','\u0414','\u0415','\u0401',
'\u0416','\u0417','\u0418','\u0419','\u041a','\u041b','\u041c','\u041d',
'\u041e','\u041f','\u0420','\u0421','\u0422','\u0423','\u0424','\u0425',
'\u0426','\u0427','\u0428','\u0429','\u042a','\u042b','\u042c','\u042d',
'\u042e','\u042f','\u0430','\u0431','\u0432','\u0433','\u0434','\u0435',
'\u0451','\u0436','\u0437','\u0438','\u0439','\u043a','\u043b','\u043c',
'\u043d','\u043e','\u043f','\u0440','\u0441','\u0442','\u0443','\u0444',
'\u0445','\u0446','\u0447','\u0448','\u0449','\u044a','\u044b','\u044c',
'\u044d','\u044e','\u044f');
Глава 8. Проблема русификации Web-приложений 89

$russian_chars = array("А", "Б", "В", "Г", "Д", "Е", "Е", "Ж", "З",
"И", "Й", "К", "Л", "М", "Н", "О", "П", "Р", "С", "Т", "У", "Ф", "Х",
"Ц", "Ч", "Ш", "Щ", "Ъ", "Ы", "Ь", "Э", "Ю", "Я", "а", "б", "в", "г",
"д", "е", "е", "ж", "з", "и", "й", "к", "л", "м", "н", "о", "п", "р",
"с", "т", "у", "ф", "х", "ц", "ч", "ш", "щ", "ъ", "ы", "ь", "э", "ю",
"я");
return str_replace($russian_codes,$russian_chars,$string);
}
?>

Сохраним эти функцию в файле ruschars.php. Но в нашем случае данные


в формате JSON будет обрабатывать метод eval() глобального объекта
JavaScript, который благополучно преобразует коды в русские буквы.

Локализация MySQL
Начиная с версии MySQL 4.1, кодировку строк можно задать не только для
всего сервера в целом, но и отдельно для каждой базы, таблицы и даже каж-
дого столбца. Кроме того, при сравнении строк в MySQL необходимо ре-
шить, собираетесь ли вы различать регистр букв. Способ сравнения строк
в MySQL называется COLLATION, что переводится обычно словом "сравне-
ние" или "сопоставление". Сопоставление влияет именно на то, как приложе-
ние работает с совокупностью символов, оно влияет на порядок сортировки
и на сравнение символов.
Название каждого сопоставления начинается с имени соответствующей ко-
дировки (например, utf8_). Далее идет спецификация сопоставления (чаще
всего, идет простое сопоставление, general, не идентифицирующее буквы)
и указание на чувствительность к регистру (cs (case sensitive) — чувствитель-
но к регистру, ci (case insensitive) — не чувствительно).
Отдельно стоит отметить бинарные сопоставления (binary). Если строки со-
поставляются бинарным образом, то между ними не делается никаких сопос-
тавлений и преобразований.
После установки сервера MySQL в Windows необходимо настроить работу
с кодировками и сопоставлениями, иначе русские буквы в базе корректно
отображаться не будут.
Для этого в каталоге, где вы установили MySQL, надо найти конфигурацион-
ный файл my.ini. В нем нам понадобится серверная секция, которая помечена
так:
# SERVER SECTION
[mysqld]
90 Часть I. Технологии, составляющие AJAX

Найдите в этой секции директиву default-character-set, задающую коди-


ровку сервера по умолчанию, и укажите для нее следующее значение:
default-character-set=utf8

Кроме того, можно настроить взаимодействие с клиентом на кодировку UTF-8,


добавив директивы
init-connect="SET NAMES utf8"
skip-character-set-client-handshake

При создании баз можно явно указывать кодировку и способ сравнения:


CREATE DATABASE dbname DEFAULT CHARACTER SET cp1251 COLLATE cp1251_bin;

Наконец, утилита mysqldump по умолчанию сохраняет данные в файл в utf8


вне зависимости от того, в какой кодировке хранились ваши данные в базе.
Часть II
Создание
AJAX-приложений
Глава 9

Объект XMLHttpRequest

В технологии AJAX сценарий JavaScript выполняет следующие задачи:


создание HTTP-запроса;
отправка запроса и получение ответа от сервера. Отслеживание стадий
выполнения такого запроса;
обработка ответа, которая, в свою очередь, состоит из:
• разбора полученного документа и вычленения из него данных;
• создания фрагмента документа, включаемого в уже загруженную в брау-
зер и отражаемую в его окне Web-страницу.
Пришла пора заняться собственно запросом к серверу. Для создания запроса
сценарию потребуется соответствующий объект, содержащий все данные
запроса. По мере поступления ответа от сервера в этом объекте потребуется
хранить информацию об успешности или неудаче запроса, при успехе надо
будет хранить данные ответа, в каком бы формате они ни поступали.
Такой объект существует и поддерживается большинством браузеров, назы-
вается он XMLHttpRequest.
Черновой вариант спецификации объекта XMLHttpRequest создан рабочей
группой под руководством World Wide Web Consortium (W3C) и опублико-
ван 15 апреля 2008. Этот объект давно поддерживается многими браузерами
и позволяет формировать запрос клиента серверу по протоколу HTTP, отсле-
живать и обрабатывать ответ сервера. Объект поддерживает передачу данных
в текстовом формате, в первую очередь, в формате XML. Он может быть ис-
пользован для создания как HTTP-, так и HTTPS-запросов.
Методы объекта XMLHttpRequest представлены в табл. 9.1 и позволяют по-
нять последовательность действий при отправке такого запроса.
94 Часть II. Создание AJAX-приложений
Таблица 9.1. Методы класса XMLHttpRequest
Метод Описание
open(method, URL, async, Определяет метод, URL и другие пара-
userName, password) метры запроса
setRequestHeader(label, value) Добавляет HTTP-заголовок к запросу
send(content) Отправляет запрос на сервер
abort() Отменяет текущий запрос
getAllResponseHeaders() Возвращает полный список HTTP-
заголовков в виде строки
getResponseHeader(headerName) Возвращает значение указанного заго-
ловка

Сначала сценарий JavaScript должен создать сам объект запроса. При генера-
ции объекта не происходит никаких видимых изменений на странице, не от-
правляется еще и сам запрос. После создания объекта запроса его надо на-
полнить данными методом open. Метод open получает в качестве параметра
название HTTP-метода (GET или POST), который будет задействован, адрес
запрашиваемого ресурса, значение параметра async и данные для аутентифи-
кации клиента (логин и пароль), если они необходимы.
Параметр async определяет, в каком режиме будет работать запрос. Если
значение этого параметра true (по умолчанию так и есть), то будет использо-
ваться асинхронный запрос, т. е. выполнение сценария продолжится после
отправки запроса. Пользователь не заметит отправки запроса и сможет про-
должать работу со страницей, не дожидаясь ответа сервера. Если же значение
параметра async равно false, то сценарий будет ожидать ответа сервера,
чтобы продолжить работу. Ясно, что второй вариант может привести к непри-
ятным последствиям при возникновении проблем с сервером или каналами
связи с ним.
Для отправки подготовленного запроса используется метод send. Его единст-
венным параметром является параметр content, значением которого могут
быть данные для POST-запроса или пустая строка в случае использования ме-
тода GET.
Итак, мы видим, что методы объекта XMLHttpRequest позволяют создать
и отправить запрос, а затем прочитать заголовки ответа сервера. Посмотрим
теперь, где находятся данные из ответа сервера (табл. 9.2).
Глава 9. Объект XMLHttpRequest 95

Таблица 9.2. Свойства класса XMLHttpRequest


Свойство Описание
readyState Возвращает текущее состояние объекта
status Возвращает HTTP-статус ответа сервера в виде числа
(404 — "Not Found", 200 — "OK" и т. д.)
statusText Возвращает статус в виде строки ("Not Found", "OK"
и т. д.)
onreadystatechange Содержит обработчик события, которое происходит при
каждой смене состояния объекта
responseText Текст ответа на запрос
responseXML Ответ на запрос в виде дерева XML, который затем
может быть обработан сценарием JavaScript с исполь-
зованием DOM

Свойство readyState позволяет отслеживать прохождение запроса. Это


свойство может принимать одно из следующих значений:
0 — состояние после создания объекта XMLHttpRequest, но до вызова ме-
тода open, который инициализирует параметры запроса;
1 — запрос открыт, после вызова метода open, но до вызова метода send;
2 — отправка данных, после вызова метода send;
3 — получение данных после того, как браузер соединится с сервером, но
до завершения сервером ответа;
4 — данные загружены, после завершения запроса и полного получения
всех данных ответа с сервера.
Каждая смена состояния вызывает событие, которое может быть обработано.
Для этого необходимо назначить функцию-обработчик события, указывае-
мую в качестве значения свойства onreadystatechange.
Иначе говоря, это свойство определяет, какая функция будет вызываться ка-
ждый раз, когда изменяется свойство readyState. На английском языке
функции, вызываемые при наступлении события, называются "callback func-
tions", поэтому в русскоязычной литературе появился термин "функции об-
ратного вызова", что зачастую сбивает с толку читателя, не знакомого с ис-
ходной терминологией.
Браузеры по-разному отслеживают изменения свойства readyState, некото-
рые изменения проходят для них незаметно, но установка readyState в зна-
чение 4 сообщает, что данные получены, и пора их обрабатывать.
96 Часть II. Создание AJAX-приложений

Ответ в текстовом формате можно считать из свойства responseText.


Для того чтобы ответ сервера был воспринят клиентским сценарием как
XML-документ, он должен содержать заголовок Content-Type с корректным
MIME-типом text/xml или application/xml. Тогда этот ответ будет содер-
жаться в свойстве responseXML, и его следует обрабатывать в соответствии со
спецификацией XML и моделью DOM. В случае наличия ошибок в пришед-
шем ответе значением свойств responseText и esponseXML становится null.
Рассмотрим простой пример. Пользователь открывает страницу, представ-
ленную в листинге 9.1.
Листинг 9.1. Страница с обработкой события click
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>AJAX</title>
<meta http-equiv="Content-Language" content="ru">
<meta http-equiv="Content-Type" content="text/html; UTF-8">
<script type="text/javascript" src="request.js"></script>
</head>
<body onclick="http_zapros()">
Щелкните где-нибудь мышью, пожалуйста.
<br/>
<div id="otvet" />
</body>
</html>

Открыв в браузере эту страницу и щелкнув в произвольном месте экрана, вы


увидите, что вид страницы изменился. Он стал таким:
Щелкните где-нибудь мышью, пожалуйста.
Ответ сервера.
Но происходит это, только если вы обращаетесь к этой странице по протоко-
лу HTTP. Рассмотрим сценарий JavaScript, работа которого и привела к из-
менениям на нашей странице (листинг 9.2).
Листинг 9.2. Сценарий request.js
// Создаем объект XMLHttpRequest
var xmlHttp = createXmlHttpRequestObject();
// Функция создания объекта XMLHttpRequest
Глава 9. Объект XMLHttpRequest 97

function createXmlHttpRequestObject()
{
var xmlHttp;
try
{
// Firefox, Opera 8.0+, Safari
xmlHttp=new XMLHttpRequest();
}
catch (e)
{
// Internet Explorer
try
{
xmlHttp=new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e)
{
try
{
xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");
}
catch (e)
{
alert("Ваш браузер не поддерживает AJAX!");
return false;
}
}
}
return xmlHttp;
}
// Отправка асинхронного HTTP-запроса
function http_zapros()
{
if (xmlHttp)
{
// Попытка отправки запроса серверу
try
{
98 Часть II. Создание AJAX-приложений
// Запрос файла request.txt с сервера
xmlHttp.open("GET", "request.txt", true);
xmlHttp.onreadystatechange = obrabotka;
xmlHttp.send(null);
}
// Сообщение об ошибке в случае неудачи
catch (e)
{
alert("Не удается соединиться с сервером");
}
}
}
// Функция обработки ответа сервера
function obrabotka()
{
// Только в этом состоянии ответа обрабатываем пришедшие данные
if (xmlHttp.readyState == 4)
{
// Данные читаем, только если статус - "OK"
if (xmlHttp.status == 200)
{
try
{
// Чтение сообщения сервера
response = xmlHttp.responseText;
// Ищем место на странице, где будем писать ответ сервера
myDiv = document.getElementById("otvet");
// Отображение сообщения
myDiv.innerHTML += response;
}
catch(e)
{
// Сообщение об ошибке
alert("Ошибка при чтении ответа");
}
}
else
{
Глава 9. Объект XMLHttpRequest 99

// Вывод сообщения о статусе ответа


alert("Возникла проблема при получении данных с сервера:\n" +
xmlHttp.statusText);
}
}
}

Обратите внимание на то, что первая часть сценария request.js не связана


с реакцией на событие. Этот сценарий подключается к странице на этапе за-
грузки ее в браузер и сразу начинает выполняться. Таким образом, первое,
что делает сценарий, — это объявление переменной xmlHttp, которая полу-
чает значение, возвращаемое функцией createXmlHttpRequestObject. Задача
этой функции — инициализировать в браузере объект XMLHttpRequest, про-
сто создать его, но не наполнять конкретным адресом запроса и другими па-
раметрами.
Делать это мы будем очень осторожно: дело в том, что не все браузеры под-
держивают этот объект. В браузерах Firefox, Opera 8.0+ и Safari объект асин-
хронного запроса можно создать, просто обратившись к соответствующему
прототипу XMLHttpRequest. А вот браузер Internet Explorer версии 6.0 и более
ранних версий не поддерживает объект XMLHttpRequest. Это вынуждает нас
обращаться к библиотеке ActiveXObject, что мы и делаем. В разных версиях
Internet Explorer работают различные версии этой библиотеки, что тоже прихо-
дится учитывать, но это осложнение мы рассмотрим в следующих примерах.
Зная, что любое действие может вызвать ошибку в браузере пользователя, мы
помещаем наши попытки создать объект внутрь блока try...catch. При бла-
гоприятном исходе функция возвращает объект xmlHttp, а при неудаче выво-
дит на экран диагностическое сообщение в окне alert.
Но вот объект создан, он находится в состоянии с readyState=0, всю исход-
ную страницу загрузили в браузер, и пользователь начинает щелкать мышью.
При щелчке вызывается функция http_zapros, которая для начала проверяет,
удалось ли создать объект запроса. Если удалось, то начинается формирова-
ние и отправка запроса на сервер.
Пользуясь методом open, мы задаем необходимые параметры запроса, т. е.
указываем, что запрос отправляется методом GET, к ресурсу на сервере
request.txt запрос будет выполняться асинхронно. В отношении типа файла,
к которому мы отправляем запрос, нет никаких ограничений: он может быть
простым текстом, HTML-страницей, PHP-скриптом — чем угодно. Важно
другое: обычно невозможно отправить запрос к документу, расположенному
не на том сервере, с которого была получена текущая страница. Таковы
100 Часть II. Создание AJAX-приложений

настройки безопасности в браузерах, они запрещают сценарию JavaScript об-


ращаться к другому серверу. Настройки несколько отличаются в разных
браузерах, но общее правило вам лучше запомнить с самого начала.
Повторим еще раз, чтобы читатель не забывал в дальнейшем: сценарий Java-
Script и страница, которую он запрашивает, должны быть на одном и том же
сервере!
Следующее, что надо сделать, — это задать значение свойства
onreadystatechange. Оно должно содержать указатель на функцию, которая
будет вызываться всякий раз, когда меняется состояние запроса. Вот это
и есть callback-функция, назовем ее obrabotka, а определим несколько позже.
Теперь надо отправить запрос, для этого вызываем метод send. Раз запрос
посылается методом GET, то send вызывается с пустым параметром. Посколь-
ку ошибки могут поджидать в любом месте, то и все только что описанные
действия мы помещаем в блок try и подготавливаем сообщение на случай
неудачи с отправкой запроса. На этом работа функции http_zapros заканчи-
вается.
Функция obrabotka, как мы уже сказали, вызывается при любом изменении
состояния запроса, но что-то делать ей надо только после того, как клиент-
ский браузер закончил прием ответа сервера, что соответствует состоянию
readyState=4. Добавлять же данные в страницу нам потребуется только
в случае, если статус ответа сервера равен 200, т. е. xmlHttp.status=200.
Ответ сервера в данном случае представляет собой простой текст (таково со-
держимое файла request.txt), поэтому его можно прочитать из свойства
responseText объекта xmlHttp. Затем надо найти место на странице, где бу-
дет выведено содержимое файла request.txt. Методом getElementById выби-
раем элемент с заданным идентификатором и меняем значение его свойства
innerHTML, добиваясь нужного результата. Ну и, конечно, не забываем о под-
стерегающих неприятностях, обрабатывая потенциальные ошибки в блоке
try/catch.
В этой главе мы познакомились с тем, как создать, отправить и обработать
простой асинхронный запрос. Впереди нас ждет генерация ответа сервера
в формате XML и JSON, обработка таких ответов на клиентской стороне
и получение данных для ответа сервера из базы данных MySQL.
Глава 10

Использование XML и создание


периодических запросов
Начиная с этого момента, мы будем использовать усовершенствованную
функцию создания объекта XMLHttpRequest, предусматривающую наличие
у клиента старых версий браузера Internet Explorer. Будем держать эту функ-
цию в файле requestobject.js, который представлен в листинге 10.1.
Листинг 10.1. Функция создания объекта XMLHttpRequest
var xmlHttp = createXmlHttpRequestObject();
function createXmlHttpRequestObject()
{
var xmlHttp;
try
{
xmlHttp = new XMLHttpRequest();
}
catch(e)
{
// Создаем объект в браузере IE6 или более старых версиях
var XmlHttpVersions = new Array("MSXML2.XMLHTTP.6.0",
"MSXML2.XMLHTTP.5.0",
"MSXML2.XMLHTTP.4.0",
"MSXML2.XMLHTTP.3.0",
"MSXML2.XMLHTTP",
"Microsoft.XMLHTTP");
// Пробуем создать до тех пор, пока не получится
for (var i=0; i<XmlHttpVersions.length && !xmlHttp; i++)
{
102 Часть II. Создание AJAX-приложений
try
{
xmlHttp = new ActiveXObject(XmlHttpVersions[i]);
}
catch (e) {}
}
}
if (!xmlHttp)
alert("Ошибка при попытке создания объекта XMLHttpRequest");
else
return xmlHttp;
}

Массив XmlHttpVersions состоит из названий различных версий библиотеки


MSXML, которая используется для создания объекта XMLHttpRequest в брау-
зере Internet Explorer. Перебираем версии, начиная с самой свежей, до тех
пор, пока не удастся создать объект, если его не удалось создать в самом на-
чале оператором new.
Теперь, когда мы запаслись удобной функцией, начнем обращаться, исполь-
зуя асинхронный запрос, к сценарию PHP. Этот сценарий будет генерировать
ответ в виде XML-документа, а клиентский JavaScript-сценарий обработает
ответ и добавит на страницу часть этого ответа. Посмотрите листинг 10.2, где
представлена страница, предлагающая пользователю список автомобилей
(например, он хочет взять один из них напрокат и выбирает цвет). Пользова-
тель щелкает по выбранному автомобилю мышью, и внизу появляется квад-
ратик, закрашенный цветом, в который выкрашен этот автомобиль.
Сам список автомобилей нетрудно сгенерировать из серверных данных, но
нас сейчас интересует другой аспект работы со страницей, поэтому список
сделан коротким и простым.
Листинг 10.2. Страница со списком автомобилей
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>AJAX</title>
<meta http-equiv="Content-Language" content="ru">
<meta http-equiv="Content-Type" content="text/html; UTF-8">
Глава 10. Использование XML и создание периодических запросов 103

<script type="text/javascript" src="requestobject.js"></script>


<script type="text/javascript" src="generateXML.js"></script>
</head>
<body >
Выберите автомобиль, чтобы узнать его цвет:
<ul>
<?php $avto = array("Opel", "Ford");
for ($i=0; $i<2; $i++) { ?>
<li> <a href="javascript:void(0);" onclick="http_zapros('<?php echo
$avto[$i]; ?>');"> <?php echo $avto[$i]; ?> </a> </li>
<?php } ?>
</ul>
<div id="otvet" />
</body>
</html>

Сценарий 10.2.php создает список автомобилей. Их названия хранятся в мас-


сиве $avto, а затем поодиночке выводятся в виде элементов списка. Для того
чтобы пользователь понимал, куда следует щелкать, названия автомобилей
представлены в виде ссылок.
Операция void(0) приводит к тому, что никаких стандартных действий по
обработке щелчка не происходит, но событие click приводит к вызову
функции http_zapros, получающей в виде параметра идентификатор элемен-
та, на котором произошло событие. Для того чтобы лучше понять, как все это
работает, замените событие click на mouseover. Если вы пользуетесь браузе-
ром Mozilla Firefox, то к этому моменту в вашем браузере уже должен быть
Firebug (посмотрите приложение 2, описывающее применение этого удобно-
го отладчика), так что не забывайте нажимать клавишу <F12>, чтобы под-
робнее посмотреть, что происходит при работе вашей программы. Прекрасен
Firebug и в том случае, когда надо понять, где возникла ошибка.
Функция http_zapros определена в сценарии requestXML.js, который дан
в листинге 10.3. Для отправки запроса серверу этой функции понадобится
информация о том, по какому элементу щелкнули в браузере. В нашем случае
сгодилось бы и значение идентификатора, но мы сделаем иначе: прочитаем
текстовое содержимое элемента и отправим его на сервер, чтобы серверный
сценарий по названию автомобиля переправил клиенту все данные о нем, за-
вернув эти данные в структуру XML. Запрос к сценарию generateXML.php
отправим методом GET, а название автомобиля передадим как значение пара-
метра name.
104 Часть II. Создание AJAX-приложений

Листинг 10.3. Сценарий generateXML.js


function http_zapros(el)
{
if (xmlHttp)
{
try
{
// Читаем название выбранного автомобиля
var params = "name=" + el;
// Отправляем запрос к сценарию generateXML.php
xmlHttp.open("GET", "generateXML.php?" + params, true);
xmlHttp.onreadystatechange = requestControl;
xmlHttp.send(null);

}
catch (e)
{
alert("Невозможно соединиться с сервером:\n" + e.toString());
}
}
}

// Функция, вызываемая при изменении состояния запроса


function requestControl()
{
// Если readyState равно 4, то мы готовы обрабатывать ответ сервера
if (xmlHttp.readyState == 4)
{
// Продолжаем, только если статус HTTP равен "OK"
if (xmlHttp.status == 200)
{
try
{
// Обрабатываем ответ сервера
obrabotka();
}
catch(e)
Глава 10. Использование XML и создание периодических запросов 105

{
alert("Ошибка при обработке ответа сервера: " + e.toString());
}
}
else
{
// Показываем статус ответа сервера
alert("Проблема с получением данных от сервера:\n" +
xmlHttp.statusText);
}
}
}

// Обработка ответа сервера


function obrabotka()
{
// Ожидаем, что ответ пришел в виде XML
var xmlResponse = xmlHttp.responseXML;
// Ловим возможные ошибки в IE и Opera
if (!xmlResponse || !xmlResponse.documentElement)
throw("Ответ не содержит XML-данных:\n" + xmlHttp.responseText);
// Получаем корневой элемент ответа
xmlRoot = xmlResponse.documentElement;
ar = xmlRoot.getElementsByTagName("color");
// Выбираем текстовое содержимое первого элемента, вложенного в
// третий элемент из числа наследников корневого
responselcolor = xmlRoot.childNodes[2].firstChild.data;
myDiv = document.getElementById("otvet");
myDiv.innerHTML =
"<div style='width:50px;height:50px;background-color:" +
responselcolor + " ' />";
}

Обработка изменения состояния запроса осуществляется в функции


requestControl. Поскольку XML-структуру придется разбирать, а процесс
этот в общем случае довольно длинный, то вынесем его в отдельную функ-
цию obrabotka, вызываемую, когда readyState стал равен 4, причем статус
ответа — 200. При этом, как всегда, позаботимся о том, чтобы возможные
ошибки были корректно обработаны.
106 Часть II. Создание AJAX-приложений

Для того чтобы понять, что, собственно, надо обрабатывать из ответа


сервера, посмотрим на то, как этот ответ формируется в сценарии
generateXML.php (листинг 10.4).

Листинг 10.4. Сценарий generateXML.php, создающий ответ сервера


<?php
$param = mb_strtoupper($_GET['name']);
if($param != "") {
if ($param == "FORD") $kraska ="#FFFF00";
if ($param == "OPEL") $kraska ="#00FF00";
$doc = new DOMDocument("1.0","utf-8");
$root = $doc->createElementNS('http://myford.ru', 'car');
$doc->appendChild($root);

$model = $root->appendChild(new DOMElement("model"));


$model->appendChild(new DOMText("Focus"));

$date = $root->appendChild(new DOMElement("date"));


$date->appendChild(new DOMText("2008"));

$color = $root->appendChild(new DOMElement("color"));


$color->appendChild(new DOMText($kraska));

header('Content-Type: text/xml');
echo $doc->saveXML();
}
?>

Начнем мы с того, что в серверном сценарии прочитаем переданный пара-


метр name. Для удобства сравнения строк их обычно преобразуют к верхнему
регистру, и вот тут-то нельзя забывать, что все строковые операции прихо-
дится выполнять над строками в UTF-8, а потому используем функции, имена
которых начинаются с приставки mb.
Во избежание выполнения лишних действий сначала проверяем, не пустая ли
строка была передана серверному сценарию.
В данном примере, не мудрствуя лукаво, просто генерируем XML-ответ из
готовых текстов. Но, конечно, следующим шагом должен стать запрос этих
данных из базы или с какого-то иного сервера, ведь никаких ограничений
Глава 10. Использование XML и создание периодических запросов 107

по части безопасности для серверных сценариев не существует. Создаем


небольшой XML-документ, предваряем его заголовком Content-Type:
text/xml и отправляем клиенту. Обратите внимание, что корневой элемент
созданного документа — car, а в клиентском сценарии мы захотим прочи-
тать текстовое содержимое элемента color, чтобы использовать его для ри-
сования на нашей странице цветного квадратика. Посмотрите на рис. 10.1,
какую информацию о работе этого сценария выводит Firebug.

Рис. 10.1. Web-страница с заголовками ответа сервера в окне Firebug

Вернемся теперь к сценарию requestXML.js. Раз ответ пришел в виде XML,


то читать его следует из свойства responseXML. Обращаясь к этому свойству,
мы получим ответ в виде дерева DOM, что диктует способ дальнейшего об-
ращения с ответом сервера. Сначала предстоит найти корневой элемент до-
кумента, прочитав свойство documentElement объекта xmlResponse, содер-
жащего ответ сервера.
108 Часть II. Создание AJAX-приложений

Затем, если нам нужны все данные из ответа, надо перебирать узлы получен-
ного дерева, но мы сделаем проще: найдем узел, содержащий цвет автомоби-
ля. Сделаем это, запросив список элементов, вложенных в корневой элемент.
Свойство childNodes даст желаемый результат, вернув нам массив таких уз-
лов. Из этого массива выберем третий элемент (считаем с 0), найдем первый
вложенный в него элемент с помощью свойства firstChild, это будет тек-
стовый узел, и запросим его свойство data для получения требуемого значе-
ния. Вот теперь переменная responselcolor будет содержать цвет выбранно-
го автомобиля, и этим цветом мы закрасим маленький квадрат, добавив его
в страницу.
Мы получили нужный результат, но, как только требуется более основа-
тельный анализ полученного XML-документа, сценарий обработки разрас-
тается, да и сам XML-формат при большом количестве передаваемой ин-
формации становится неудобным: слишком много в нем избыточности,
поэтому неудивительно, что, несмотря на то, что сама аббревиатура AJAX
включает в себя XML, в настоящее время большинство разработчиков
предпочитает передавать данные в формате JSON, что мы и продемонстри-
руем в следующих главах.

Создание периодических запросов


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

Листинг 10.5. Страница с полем ввода номера телефона


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>AJAX </title><meta http-equiv="Content-Language" content="ru">
<meta http-equiv="Content-Type" content="text/html; UTF-8">
<script type="text/javascript" src="requestobject.js"></script>
<script type="text/javascript" src="settime.js"></script>
</head>
<body onload='http_zapros()'>
Глава 10. Использование XML и создание периодических запросов 109

Наберите номер своего телефона:


<input type="text" id="myNumber" />
<div id="myDiv" />
</body>
</html>

Генерация объекта XMLHttpRequest вынесена в подключаемый файл


requestobject.js, который мы обсуждали ранее. Поэтому мы прямо перейдем
к рассмотрению сценария JavaScript в файле settime.js (листинг 10.6), в кото-
ром определена функция http_zapros. Эта функция будет вызвана сразу по-
сле загрузки страницы в браузер.

Листинг 10.6. Сценарий settime.js


function http_zapros()
{
if (xmlHttp.readyState == 4 || xmlHttp.readyState == 0)
{
// Запрашиваем значение поля формы
name = encodeURIComponent(document.getElementById("myNumber").value);
// Выполняем запрос к серверу
xmlHttp.open("GET", "settime.php?name=" + name, true);
// Определяем функции обработки события
xmlHttp.onreadystatechange = obrabotka;
// Отправляем запрос к серверу
xmlHttp.send(null);
}
else
// В случае занятости повторяем запрос через 1 секунду
setTimeout('http_zapros()', 1000);
}
//
function obrabotka()
{
// Начинаем обработку при завершении запроса
if (xmlHttp.readyState == 4)
{
// Обрабатываем ответ, если его статус равен 200
if (xmlHttp.status == 200)
110 Часть II. Создание AJAX-приложений
{
// Читаем текстовый ответ сервера
xmlResponse = xmlHttp.responseText;
document.getElementById("myDiv").innerHTML =
'<i>' + xmlResponse + '</i>';
// Повторяем вызов функции через 1 секунду
setTimeout('http_zapros()', 1000);
}
// Если статус ответа отличен от 200, то выводим сообщение об ошибке
else
{
alert("Проблема доступа к серверу: " + xmlHttp.statusText);
}
}
}

Функция http_zapros начинает свою работу с проверки состояния запроса,


а затем считывает данные из поля формы с идентификатором Mynumber. Этот
идентификатор отправится на сервер в качестве параметра запроса к сцена-
рию settime.php. В случае, если запрос к моменту обращения оказался не готов,
попытка вызвать функцию http_zapros будет предпринята снова через 1 сек.
Ответ сервера формируется в сценарии settime.php, представленном в лис-
тинге 10.7.
Листинг 10.7. Сценарий settime.php
<?php
$name = $_GET['name'];
if (preg_match("/(\d){7}/", $name))
echo 'Вы набрали ' . $name;
else
echo 'Наберите 7 цифр!';
?>

Из него становится ясно, что пришедшие на сервер данные проверяются на


наличие в них 7 цифр, и в случае успеха клиенту отправляется текст, содер-
жащий эти цифры. В противном случае пользователь увидит сообщение
о необходимости ввести семь цифр. Полученная страница представлена
на рис. 10.2.
Глава 10. Использование XML и создание периодических запросов 111

Рис. 10.2. Страница, периодически проверяющая состояние поля ввода формы

Надо отметить, что разработка и отладка AJAX-приложений для браузера


Internet Explorer имеет несколько осложняющих ситуацию особенностей. Во-
первых, Internet Explorer кэширует GET-запросы, следует использовать либо
запросы методом POST, либо отправлять GET-запросы каждый раз с новым
значением параметра. Альтернативным решением является запрет кэширова-
ния, который осуществляется отправкой специальных заголовков HTTP, как
показано в следующей главе. Тем не менее, полностью решить проблему та-
кого кэширования оказывается непростой задачей.
Вторым аспектом работы с Internet Explorer оказываются ошибки, возни-
кающие при попытке создать объекты JavaScript, одноименные с HTML-
элементами страницы. Эта недокументированная особенность заставляет ре-
комендовать отказаться от создания объектов JavaScript, имена которых сов-
падают с идентификаторами или именами тегов.
Глава 11

Запрос данных
с сервера MySQL
В реальной жизни большие объемы информации хранят в базах данных,
и настало время формировать ответ сервера, обращаясь к серверу MySQL.
В этой главе мы создадим базу данных и будем хранить в ней данные
о нескольких автомобилях. Предоставим возможность пользователю снова
запросить цвет автомобиля, теперь выбор данных производится по имени
владельца. Имя отправляется на сервер MySQL. Web-страница, предлагаю-
щая пользователю узнать цвет автомобиля, дана в листинге 11.1.
Листинг 11.1. Страница запроса данных с сервера MySQL
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
<head>
<title>Используем MySQL</title>
<meta http-equiv="Content-Type" content="text/html; UTF-8">
<link href="12.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="requestobject.js"></script>
<script type="text/javascript" src="requestMySQL.js"></script>
</head>
<body >
<div class="dialog">
<span>Цвет автомобиля у моих друзей:</span>
<select id="сar" onchange="http_zapros()">
<option value=""/><span>Выбери имя</span>
<option value="Михаил"/><span>Михаил</span>
<option value="Анна"/><span>Анна</span>
Глава 11. Запрос данных с сервера MySQL 113

<option value="Петр"/><span>Петр</span>
<option value="Наталия"/><span>Наталия</span>
</select><div />
Ответ сервера:
<div id="myDiv" class="myDiv" />
</div>
</body>
</html>

Итак, нам потребуется база данных, назовем ее ajax и создадим в ней таблицу
cars. Сценарий создания таблицы представлен в листинге 11.2. При создании
базы укажите, что вы хотите использовать кодировку UTF-8. В таблице cars
мы будем хранить сведения о нескольких автомобилях, имени владельцев,
марке автомобиля, модели, годе выпуска и цвете. Внесем в таблицу несколь-
ко строк данных.
Листинг 11.2. Сценарий ajax.sql для создания таблицы в базе данных ajax
CREATE TABLE cars
(
car_id INT UNSIGNED NOT NULL AUTO_INCREMENT primary key,
owner VARCHAR(30) NOT NULL,
make VARCHAR(30) NOT NULL,
model VARCHAR(30) NOT NULL,
kogda year(4) not null,
color VARCHAR(30) NOT NULL
);

INSERT INTO cars (owner, make, model, kogda, color) VALUES


('Михаил','opel','astra', 2008, '#C0C0C0');
INSERT INTO cars (owner, make, model, kogda, color) VALUES
('Анна','opel','meriva', 2007, '#FAEBD7');
INSERT INTO cars (owner, make, model, kogda, color) VALUES
('Петр','ford','focus', 2007, '#800000');
INSERT INTO cars (owner, make, model, kogda, color) VALUES
('Наталия','subaru','impreza', 2007, '#008000');

grant all on ajax.* to 'myname@localhost' identified by 'secret';

Наш сценарий будет обращаться к серверу MySQL от имени пользователя


myname с паролем secret. Поэтому не забудем создать такого пользователя
114 Часть II. Создание AJAX-приложений

сервера MySQL командой grant. Параметры для установления соединения


с сервером запишем в конфигурационный файл config.php (листинг 11.3).

Листинг 11.3. Конфигурационный файл config.php


<?php
// Константы для соединения с сервером
define('DB_HOST', 'localhost');
define('DB_USER', 'myname');
define('DB_PASSWORD', 'secret');
define('DB_DATABASE', 'ajax');
?>

Для работы с базой данных мы хотим использовать класс, в нем определим


все действия, которые потребуется выполнять. Посмотрите листинг 11.4
и файл car_class.php, чтобы разобраться во всем. Сначала подключаем кон-
фигурационный файл для соединения с сервером MySQL, а затем определяем
конструктор класса car. Работа конструктора состоит в установлении соеди-
нения с сервером MySQL, создавая экземпляр класса mysqli, определенного
в одноименном расширении PHP. Свойство mysqli класса car будет содер-
жать указатель на установленное соединение.
Для отправки запросов к MySQL нам потребуется метод getcolor, который
в качестве параметра принимает имя владельца автомобиля. Мы хотим опре-
делить цвет автомобиля, которым владеет этот человек. Запрос в базу от-
правляется методом query. Результат запроса будет представлять собой объ-
ект класса mysqliResult и храниться во временной таблице, на которую
указывает переменная $result.

Листинг 11.4. Класс car в файле car_class.php


<?php
require_once ('config.php');
// Класс для работы с базой данных
class car
{ // Хранит идентификатор соединения с базой
protected $mysqli;
function __construct(){
$this->mysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD,
DB_DATABASE);
}
Глава 11. Запрос данных с сервера MySQL 115

function getcolor($name){ // Выбор данных об автомобиле


$query = "SELECT color FROM cars where owner = '$name'";

// Выполнение запроса
if ($result = $this->mysqli->query($query)){
// Выбор данных из ответа сервера MySQL
$arr = $result->fetch_array(MYSQLI_ASSOC);

// Удаляем результирующую выборку


$result->close();

// Вывод ответа клиенту


return $arr["color"];
}
else return "данных нет";
}
// Деструктор закрывает соединение с базой
function __destruct()
{
$this->mysqli->close();
}
}
?>

Метод fetch_array извлекает из выборки ассоциативный массив, но нам-то


нужен один-единственный его элемент, поэтому мы просто извлекаем один
элемент этого массива и отправляем его клиенту.
Запрос к базе данных направляем на небольшую проверку разумности ре-
зультатов, и, если получены подходящие данные, метод getcolor возвращает
цвет автомобиля. В конце сценария пишем деструктор, который разорвет со-
единение с MySQL по окончании работы сценария. Ну, хорошо, класс мы
создали. Напишем теперь PHP-сценарий, к которому будет обращаться кли-
ентский JavaScript. Сценарий этот должен создавать объект класса car и за-
прашивать данные из базы (листинг 11.5 и файл на компакт-диске).
Листинг 11.5. Сценарий requestMySQL.php
<?php
require_once('car_class.php');
// Запрет кэширования
116 Часть II. Создание AJAX-приложений
header('Expires: Wed, 01 Dec 1980 00:30:00 GMT'); // time in the past
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
// Читаем параметр запроса
$name = $_GET['name'];
if ($name != '')
{
// Создаем объект класса cars
$avto = new car();
// Отправляем запрос в базу
$color = $avto->getcolor($name);
// Отправляем ответ клиенту
if ($color!="данных нет"){
echo $color;
}
}
else
{
echo 'Ошибка на сервере';
}
?>

Первое, что мы делаем в сценарии, — это подключаем сценарий класса car


и запрещаем кэширование результатов в браузере клиента. Затем читаем
передаваемый в запросе параметр: он должен содержать имя владельца авто-
мобиля. Затем создаем объект класса car и отправляем запрос с именем вла-
дельца в базу данных. Полученные данные отправляем в клиентский сцена-
рий, не забыв предусмотреть возможность появления ошибок.
Наконец, надо рассмотреть работу сценария requestMySQL.js, который пред-
ставлен в листинге 11.6.
Листинг 11.6. Сценарий requestMySQL.js
function http_zapros()
{
var serverAddress = "requestMySQL.php?name=" +
encodeURIComponent(document.getElementById("сar").value);
if (xmlHttp)
{
Глава 11. Запрос данных с сервера MySQL 117

try
{
xmlHttp.open("GET", serverAddress, true);
xmlHttp.onreadystatechange = obrabotka;
xmlHttp.send(null);
}
catch(e)
{
alert("Невозможно :\n" + e.toString());
}
}
}
function obrabotka ()
{
if (xmlHttp.readyState == 4)
{
if (xmlHttp.status == 200)
{
try
{
var response = xmlHttp.responseText;
myDivel = document.getElementById("myDiv");
if (response!="данных нет"){
myDivel.innerHTML =
"<div style='width:50px;height:50px;background-color:"
+ response + " ' />";
}
}
catch(e)
{
alert("Ошибка при обработке данных сервера:\n" + e.toString());
}
}
else
{
alert(xmlHttp.statusText);
}
}
}
118 Часть II. Создание AJAX-приложений

Рис. 11.1. Web-страница запроса данных с сервера MySQL

Посмотрите на рис. 11.1, где дан вид полученной страницы, и оцените, на-
сколько информативно сообщение Firebug об асинхронном запросе.
При взгляде на этот сценарий, читатель сразу понимает, что нового в нем
очень мало, т. к. в нем снова отправляется асинхронный запрос, ответ сервера
представлен в виде текста, что приводит к простой программе обработки по-
лученных данных. Но как только речь заходит об обработке более объемных
данных, сразу становится очевидно, что применение XML-формата данных
в ответе сервера приводит к резкому усложнению программ обработки,
и взоры программистов обращаются к формату JSON, что сделаем и мы на
следующем примере.
Передача данных в формате JSON
Изменим немного предыдущий сценарий, запросив из той же базы данных
всю информацию об автомобиле. Будем теперь получать данные, задав номер
автомобиля. Данные должны отображаться в виде блока с текстом на цвет-
ном фоне. Web-страница будет теперь выглядеть, как в листинге 11.7.
Листинг 11.7. Запрос данных JSON
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
Глава 11. Запрос данных с сервера MySQL 119

<title>AJAX</title>
<meta http-equiv="Content-Language" content="ru">
<meta http-equiv="Content-Type" content="text/html; UTF-8">
<script type="text/javascript" src="requestobject.js"></script>
<script type="text/javascript" src="generatejson.js"></script>
</head>
<body >
Выберите номер автомобиля и узнайте его характеристики:
<ul>
<?php $avto = array(1, 2, 3, 4);
for ($i=0; $i<4;$i++) {
echo '<li><a href="javascript:void(0);"
onclick="http_zapros(\''.$avto[$i].'\');"
id="'.$avto[$i].'">'.$avto[$i].'</a></li>';
} ?>
</ul>
<div id="otvet" />
</body>
</html>

Начнем разбор действий с серверного сценария, точнее, с класса, который


представлен в листинге 11.8.
Листинг 11.8. Сценарий adv_car_class.php: создание класса adv_car
<?php
require_once ('car_class.php');
class adv_car extends car{
// Вызываем явно конструктор базового класса
function __construct($id){
parent::__construct();
$this->car_id=$id;
$this->get_array($id);
}
function get_array ($iden){
$request = "SELECT * FROM cars where car_id = $iden";
$result = $this->mysqli->query($request);
$massiv = $result->fetch_array(MYSQLI_ASSOC);

foreach($massiv as $key=>$value){
120 Часть II. Создание AJAX-приложений
$this->$key = $value;
}
$result->close();
}
}
?>

Сценарий adv_car_class.php создания класса демонстрирует нам, что класс


adv_car расширяет класс car. Вот тут-то становится ясно, зачем свойство
mysqli, указывающее на соединение с сервером, определялось как защищен-
ное. Сделай мы его частным, и уже не удастся применить его в классе-
наследнике. Конструктор класса adv_car явно вызывает конструктор базово-
го класса, а затем выполняет собственные задачи, вызывая метод get_array,
запрашивающий для нас с сервера MySQL все необходимые данные. Затем
в цикле foreach создается массив свойств генерируемого объекта.
Итак, объект создан. Посмотрим теперь, где он используется (листинг 11.9).
Листинг 11.9. Сценарий generatejson.php
<?php
require_once('adv_car_class.php');
// Запрет кэширования
header('Expires: Wed, 01 Dec 1980 00:30:00 GMT'); // Время в прошлом
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
// Читаем параметр запроса
$id = $_GET['id'];
if ($id != '')
{
// Создаем объект класса car
$avto = new adv_car($id);
// Отправляем ответ клиенту
echo json_encode($avto);
}
else
{
echo 'Ошибка на сервере';
}
?>
Глава 11. Запрос данных с сервера MySQL 121

Комментировать в нем, в общем-то, и нечего. Для понимания его работы дос-


таточно вспомнить, что данные JSON формируются из PHP-объекта функ-
цией json_encode.
Теперь самое время посмотреть, как эти данные обрабатываются на клиент-
ской стороне в сценарии generatejson.js (листинг 11.10).
Листинг 11.10. Обработка на стороне клиента
function http_zapros(el)
{
if (xmlHttp)
{
try
{
// Читаем текст элемента
var params = "id=" + el;
xmlHttp.open("GET", "generatejson.php?" + params, true);
xmlHttp.onreadystatechange = requestControl;
xmlHttp.send(null);
}
catch (e)
{
alert("Невозможно соединиться с сервером:\n" + e.toString());
}
}
}
// Функция, вызываемая при изменении состояния запроса
function requestControl()
{
// Если readyState равно 4, то мы готовы обрабатывать ответ сервера
if (xmlHttp.readyState == 4)
{
// Продолжаем, только если статус HTTP равен "OK"
if (xmlHttp.status == 200)
{
try
{
// Обрабатываем ответ сервера
obrabotka();
122 Часть II. Создание AJAX-приложений
}
catch(e)
{
alert("Ошибка при обработке ответа сервера: " + e.toString());
}
}
else
{
// Показываем статус ответа сервера
alert("Проблема с получением данных от сервера:\n" +
xmlHttp.statusText);
}
}
}
// Обработка ответа сервераfunction obrabotka()
{
// Ожидаем, что ответ пришел в формате JSON
var xmlResponse = xmlHttp.responseText;
var data = eval("(" + xmlResponse + ")");
myDiv = document.getElementById("otvet");
info="";
info+="<div style='width:200px;height:200px;background-color:" +
data.color + "'>";
info+="Владелец -" + data.owner;
info+="<br />Марка -" + data.make;
info+="<br />Модель -" + data.model;
info+="</div>";
myDiv.innerHTML = info;
}

Поясним работу функции obrabotka. Переменная data получает от функции


eval объект JavaScript с данными, пришедшими с сервера. Зная, какие свой-
ства содержал серверный объект, мы читаем одноименные свойства объекта
JavaScript и выводим их на странице. Посмотрите на рис. 11.2, какой ответ
получает клиентский сценарий.
В этой главе мы рассмотрели разработку простых Web-приложений, реали-
зующих асинхронные запросы. Даже при небольшом объеме кода становится
очевидным, что отладка приложения для корректной работы в различных
браузерах весьма трудоемка. Объем же кода для обработки ответа сервера
Глава 11. Запрос данных с сервера MySQL 123

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

Рис. 11.2. Страница с открытой консолью Firebug


Часть III
Библиотеки
для работы с AJAX
Глава 12

Обзор библиотек
для создания AJAX-приложений
До этого момента мы обсуждали применение AJAX, исходя из того, что на
клиентской стороне работает сценарий JavaScript, а на серверной — PHP.
Но в настоящее время на серверной стороне могут использоваться также
Java, .NET, Ruby on Rails, Python, ColdFusion, Perl и др.
Что касается клиентского сценария, то, полагаем, читатель уже убедился, что
создание AJAX-приложений на чистом JavaScript — это разработка очень длин-
ного кода с долгой отладкой его в различных браузерах. К счастью,
в настоящее время в распоряжении разработчика есть большой набор библиотек,
позволяющих ускорить разработку Web-приложений. Часто такие библиотеки
называют фреймворками (англ. Framework), используя термин, обозначающий
более широкое и весьма расплывчатое понятие. Мы все-таки будем использовать
английский термин. Framework может включать вспомогательные программы,
библиотеки кода, язык сценариев и другое программное обеспечение, облегчаю-
щее разработку и объединение разных компонентов большого программного
проекта. В отличие от библиотеки, которая объединяет в себе набор близкой
функциональности, Framework содержит в себе большое число разных по тема-
тике библиотек. Все же термин "Framework" тяжеловат для русского слуха,
и в дальнейшем мы будем чаще использовать термин "библиотека".
Основная работа при разработке AJAX-приложений ложится на клиентскую
сторону, причем в силу различной поддержки браузеров стандарта DOM,
объем программирования может оказаться весьма значительным. Для уско-
рения процесса разработки существуют два подхода.
Серверные библиотеки, позволяющие генерировать код JavaScript из ин-
терфейса, созданного на языке серверного программирования. Можно
привести два примера:
• Google Web Toolkit (GWT) позволяет создавать интерфейс на языке
программирования Java, а GWT компилирует исходный код в тщатель-
но оптимизированный JavaScript;
128 Часть III. Библиотеки для работы с AJAX

• XAJAX — библиотека PHP, позволяющая писать требуемые функции


на PHP, а затем функции библиотеки генерируют JavaScript-код, вы-
полняя созданные программистом функции.
В данной книге мы не будем подробно рассматривать этот класс биб-
лиотек.
JavaScript-библиотеки, создающие кроссбраузерный код JavaScript, вы-
полняющий всю рутинную работу по созданию, отправке и обработке
AJAX-запросов и многое другое.
В этой и последующих главах мы рассмотрим наиболее интересные биб-
лиотеки второй из перечисленных групп библиотек. Эти библиотеки в той
или иной степени расширяют стандартную объектную модель JavaScript,
позволяя оперировать понятиями класса, наследования и полиморфизма.
В некоторых из них реализованы общедоступные и частные свойства и ме-
тоды классов.
С точки зрения компонентов, обычно в библиотеке есть все или часть пере-
численных ниже средств ускорения разработки Web-приложений:
поддержка AJAX-запросов и обработка результатов;
запрос и обработка данных в формате JSON;
средства автодополнения форм путем запроса данных с помощью AJAX;
усовершенствованная поддержка событий;
средства генерации HTML;
создание иерархических деревьев;
генерация тем (themes, skins);
создание функциональности, реализующей эффект нажатия кнопки Назад
в браузере;
простые визуальные эффекты, например, изменение размеров, прозрачно-
сти или видимости фрагмента страницы;
анимация и связанные с ней более сложные визуальные эффекты;
drag & drop;
виджеты для ускорения ввода данных и проверка форм;
создание редактируемых таблиц данных (grids) и др.
В каждой оконной системе существует свой набор элементов, с интерфейсом
для работы с ними. Элементы интерфейса — это примитивы графического
интерфейса пользователя, имеющие стандартный внешний вид и выполняю-
щие стандартные действия. Такие элементы известны также под именем
Глава 12. Обзор библиотек для создания AJAX-приложений 129

виджеты (от англ. widget) и элементы управления. Обычно виджетами назы-


вают элементы следующих типов:
кнопка (button);
список (list box);
выпадающий список (combo box);
флажок (check box);
радиокнопка или переключатель (radio button);
поле редактирования (textbox, edit field);
значок (icon);
панель инструментов (toolbar);
панель (строка) состояния (status bar);
всплывающая подсказка (tooltip, hint);
полоса прокрутки (scrollbar);
вкладка (tab);
элемент для отображения табличных данных (grid view);
меню (menu);
окно (window);
панель (panel);
диалоговое окно (dialog box);
модальное окно (modal window).
Все популярные JavaScript-библиотеки сопровождаются подробной докумен-
тацией и демонстрационными примерами. Обычно такие библиотеки распро-
страняются под двойной лицензией с возможностью как свободного, так
и коммерческого использования. Перечислим некоторые библиотеки, завое-
вавшие популярность на момент написания данной книги.
Dojo (доджо) — свободная модульная JavaScript-библиотека. Разработка
библиотеки была начата Алексом Расселом в 2004 году. Библиотека нахо-
дится под двойной лицензией: BSD License и Academic Free License. Dojo
используется в Zend Framework, начиная с версии 1.6.0. Сайт:
http://dojotoolkit.org/.
ExtJS — JavaScript Framework для разработки Web-приложений и поль-
зовательских интерфейсов, изначально задуманный как расширенная вер-
сия Yahoo! UI Library. Использует адаптеры для доступа к библиотекам
YUI, jQuery или Prototype/script.aculo.us. Распространяется по условиям
130 Часть III. Библиотеки для работы с AJAX

трех лицензий: Commercial License, Open Source License и OEM/Reseller


License. Сайт: http://extjs.com.
jQuery — JavaScript Framework, фокусирующийся на взаимодействии
JavaScript и HTML. Сайт: http://Jquery.com.
MooTools — это свободный JavaScript Framework для разработки кросс-
браузерных Web-приложений и Web-сервисов. Сайт: http://Mootools.net.
The Yahoo! UI Library (YUI) — библиотека с открытым кодом. Сайт:
http://developer.yahoo.com/yui/.
Prototype — JavaScript Framework. Сайт: prototypejs.org.
Script.aculo.us — JavaScript-библиотека для разработки пользовательского
интерфейса Web-приложений, построенная на Framework Prototype.
Обычно используется программистами вместе с Ruby on Rails, однако
также доступна в виде отдельной библиотеки и присутствует в составе
некоторых каркасов для разработки сайта. Сайт: http://script.aculo.us.
Глава 13

Библиотека Prototype

Prototype — это библиотека, созданная на JavaScript и предназначенная для


упрощения генерации динамических Web-приложений. Prototype была созда-
на Сэмом Стефенсоном в 2005 г. в качестве проекта с открытым кодом.
Свежую версию Prototype можно скачать с сайта http://www.prototypejs.org,
ее версию 1.6 читатель найдет на диске, приложенном к данной книге.
Если вы посмотрите на оглавление документации по Prototype, то увидите,
что эта библиотека состоит из двух неравных частей: утилиты и классы, что
сразу дает представление о назначении Prototype: утилиты служат для упро-
щения обычных операций JavaScript, а классы расширяют стандартную объ-
ектную модель языка, позволяя создавать объекты, располагающие большим
числом методов.
Расширение это выполнено таким образом, что программирование на JavaScript
с помощью Prototype становится похожим на работу с объектами по класси-
ческой схеме: можно оперировать понятием класса, что невозможно в клас-
сическом JavaScript, класса-наследника и т. п. В связи с этим Prototype
вызвала ряд критических замечаний, суть которых сводится к тому, что пре-
вращение JavaScript в слабое подобие C++ убивает мощь и гибкость, зало-
женные в JavaScript исходно. Но каковы бы ни были достоинства и недостат-
ки Prototype, следует признать, что она стала первой JavaScript-библиотекой,
получившей действительно широкое распространение среди разработчи-
ков, и послужила основой для развития других библиотек, например,
script.aculo.us, речь о которой пойдет в следующей главе. Prototype — это
библиотека, содержащая, в основном, низкоуровневые средства для Web-
разработки. То есть она не включает в себя наборы визуальных эффектов
и готовые решения для создания пользовательских интерфейсов, но обеспе-
чивает более удобную работу с HTML-элементами документа, снабжает ме-
тодами обработки списков элементов, управления стилевыми классами до-
кумента, ускоряет работу с формами и AJAX-запросами и т. п.
132 Часть III. Библиотеки для работы с AJAX

Раз речь идет о программе на JavaScript, то и подключать Prototype надо как


обычные сценарии JavaScript. Копируем Prototype в каталог scripts и включа-
ем в HTML-документ:
<script src="scripts/prototype.js" type="text/javascript"></script>

После этого мы можем вызывать функции, определенные в Prototype, в тек-


сте нашего сценария.

Полезные методы в Prototype


Рассмотрим методы Prototype, собранные в группу "Utility methods" и упро-
щающие программирование на JavaScript (табл. 13.1).

Таблица 13.1. Полезные методы в Prototype


Название Возвращаемое Описание
значение
$(id | element) HTMLElement Если аргументом служит строка (значе-
ние идентификатора), то возвращает
элемент с указанным значением иденти-
фикатора. Может принимать несколько
аргументов
$$(cssRule...) HTMLElement Принимает список CSS-селекторов, воз-
вращает массив элементов, соответст-
вующих селекторам, в том порядке,
в каком они указаны в документе
$A(iterable) actualArray Принимает любую пронумерованную
коллекцию и возвращает массив. Явля-
ется псевдонимом метода Array.from
$F(element) value Возвращает значение поля ввода формы.
Является удобным псевдонимом метода
Form.Element.getValue
$H([obj]) EnumerableHash Метод получения хэша. Возвращает но-
вый объект со свойствами объектов Hash
и Enumerable
$R(start, end[, ObjectRange Создает новый объект ObjectRange
exclusive =
false])
$w(String) Array Разбивает строку на массив, используя
пробел в качестве разделителя
Try.these firstOKResult Принимает в качестве параметра список
(Function...) функций и возвращает результат выпол-
нения первой, не вызвавшей ошибку
Глава 13. Библиотека Prototype 133

Метод $() — это краткий синоним известного метода


document.getElementById(). Все просто — посмотрите листинг 13.1.
Листинг 13.1. Метод $()
<html>
<head>
<script src="prototype.js"></script>
<script>
function show_element(){
fragment = $("firstDiv");
alert(fragment.innerHTML);
}
</script>
</head>
<body onclick="show_element();"><div id="firstDiv">Текст</div></body>
</html>

Для применения метода $$() надо знать, как именно обозначаются парамет-
ры этого метода. Приведем несколько коротких примеров:
$$('div') — указываем имя тега, метод возвращает массив всех элемен-
тов, созданных тегом div;
$$('#contents') — указываем значение идентификатора, и, несмотря на
то, что в документе может быть только один элемент с таким идентифика-
тором, метод $$() все равно возвращает массив, состоящий, правда, из
одного элемента;
$$('.faux') — выбираем все элементы, относящиеся к стилевому классу
faux;
$$('li.faux') — выбираем все элементы списка li, относящиеся к сти-
левому классу faux.
Метод f() — первый из методов Prototype, на которых начинаешь чувство-
вать, что жизнь становится легче и приятнее. Посмотрите пример, приведен-
ный в листинге 13.2, и вы поймете, что это действительно так. Здесь метод
f() возвращает строку, содержащую значение поля ввода формы, длину по-
лученной строки мы читаем из ее свойства length.
134 Часть III. Библиотеки для работы с AJAX

Листинг 13.2. Метод f() в Prototype

<html>
<head>
<title>Prototype</title>
<script src="scripts/prototype.js" language="JavaScript"
type="text/javascript" ></script>
<script language="Javascript">
function f()
{ alert($F('zip').length); }
</script>
</head>
<body onLoad="f()">
<form>
<input type="text" name="zip" id="zip" value="slovo" />
</form>
</body>
</html>

Часть остальных методов, приведенных в табл. 13.1, служит для того, чтобы
преобразовать HTML-элемент или список элементов в объекты классов, оп-
ределенных в Prototype, позволяя таким образом применять к ним ряд удоб-
ных методов.
При определении некоторого клиентского метода мы можем с помощью ме-
тода Try.these указать, что надо выполнить первую из функций, перечис-
ленных в качестве параметра этого метода, и не вызывающую ошибки.
Таким образом, метод Try.these позволяет, например, при создании объекта
запроса не заботиться о том, какой браузер используется на клиентском ком-
пьютере.
getTransport: function() {
return Try.these(
function() { return new XMLHttpRequest() },
function() { return new ActiveXObject('Msxml2.XMLHTTP') },
function() { return new ActiveXObject('Microsoft.XMLHTTP') }
) || false;
}
Глава 13. Библиотека Prototype 135

Класс Element
Класс Element предоставляет пользователю ряд методов, которые можно
сгруппировать по их ролям.
Управление стилевыми свойствами элемента. Эти методы могут читать
и модифицировать отдельные свойства CSS, приписывать элемент к какому-
либо стилевому классу, анализировать стилевые таблицы документа и из-
влекать элементы на основании их принадлежности к тому или иному
стилевому классу.
Методы, создающие, модифицирующие и удаляющие HTML-элементы.
Эти методы оперируют непосредственно с понятиями DOM, включая та-
кие, как родительские элементы и элементы-наследники, предыдущие
и последующие элементы (previous and following siblings) и т. д.
Методы, получившие в официальной документации титул расширяющих
DOM. Эти методы превращают объекты JavaScript, в котором есть поня-
тие объекта и его прототипа, но нет классов, в объекты, которыми можно
манипулировать в соответствии с классовой объектной моделью.
Следующая группа методов облегчает работу с событиями в JavaScript,
делая ее более элегантной, чем, впрочем, могут похвастаться и многие
другие методы Prototype.
И, наконец, набор приятных мелочей, например, позволяющих прокручи-
вать окно браузера так, как окажется удобным.
В табл. 13.2 описаны некоторые методы объекта Element, они представлены
не полностью, но в объеме, позволяющем понять функциональность и назна-
чение обсуждаемого класса.
Таблица 13.2. Методы объекта Element
Метод Описание
Управление стилями CSS
toggle(elem1 [, elem2 [, Изменяет видимость данных объектов
elem3 [...]]])
show(elem1 [, elem2 [, Показывает элементы, устанавливая их свойст-
elem3 [...]]]) во display в значение '' (т. е. пустая строка)
hide(elem1 [, elem2 [, Прячет элементы, устанавливая их стилевое
elem3 [...]]]) свойство display в значение 'none'
addClassName(element, Добавляет элементу стилевой класс
className)
136 Часть III. Библиотеки для работы с AJAX
Таблица 13.2 (окончание)
Метод Описание
setStyle(element, styles) Устанавливает стилевое свойство для указанно-
-> HTMLElement го элемента (см. листинг 13.8)
removeClassName(element, Удаляет указание стилевого класса элемента
className)
hasClassName(element, Возвращает true, если элемент относится
className) к указанному классу
getHeight(element) Возвращает свойство offsetHeight элемента
toggleClassName(element, Меняет CSS-класс элемента и возвращает сам
className) -> HTMLElement элемент
getDimensions(element) -> Вычисляет ширину и высоту элемента (width
{height: Number, width: и height) и возвращает их в виде литерального
Number} объекта
hasClassName(element, Проверяет, относится ли элемент к указанному
className) -> Boolean классу CSS
DOM- функции
remove(element) Удаляет элемент из документа
replace(element[, html]) -> Заменяет элемент на содержимое аргумента
HTMLElement html и возвращает удаленный элемент
update(element[, Заменяет содержимое элемента на значение
newContent]) аргумента newContent и возвращает элемент
Расширение DOM
extend(element) Дает возможность применять все методы клас-
са Element к указанному HTML-элементу
addMethods([methods]) Принимает список методов и делает их мето-
дами того объекта, к которому применяется сам
addMethods
cleanWhitespace(element) Убирает все пробельные текстовые узлы из
списка детей данного элемента
Управление событиями
observe(element, eventName, Привязывает обработчик события к элементу
handler[, useCapture = и возвращает элемент
false]) -> HTMLElement
stopObserving(element, Удаляет обработчик события и возвращает
eventName, handler) -> элемент
HTMLElement
Управление окном браузера
scrollTo(element) -> Прокручивает окно так, что элемент оказывает-
HTMLElement ся на верху области видимости окна
Глава 13. Библиотека Prototype 137

Приведем пример использования метода hide() (листинг 13.3).


Листинг 13.3. Метод hide()
<html>
<head>
<title>Prototype</title>
<script src="scripts/prototype.js" language="JavaScript"
type="text/javascript"></script>
<script language="Javascript">
function hide_el()
{ $('smth2').hide(); }
</script>
</head>
<body onclick="hide_el()">
<div id="smth">Блок 1</div>
<div id="smth2"> <b>Щелкни по этому элементу, и он исчезнет.</b>
Блок 2</div>
<div id="smth3">Блок 3</div>
</body>
</html>

Класс Array
Метод $$(), как мы уже упоминали, возвращает массив DOM-элементов
в том порядке, в каком они встречаются в документе. А раз это массив, то
и обращаться с ним можно как с объектом Array из Prototype. В табл. 13.3
описаны основные методы этого объекта.
Эти методы представляют собой расширение набора функций JavaScript об-
работки массивов: сортировка, удаление повторяющихся элементов, преоб-
разование массива в другие форматы данных и т. п.
Таблица 13.3. Методы объекта Array
Метод Описание
clone Возвращает копию массива, исходный массив не изменяется
compact Удаляет из массива пустые элементы
each Перебирает все элементы массива
138 Часть III. Библиотеки для работы с AJAX
Таблица 13.3 (окончание)
Метод Описание
first Выбирает первый элемент массива
last Выбирает последний элемент массива
size Возвращает размер массива
toJSON Возвращает элементы массива в формате toJSON

Вот теперь можно на небольшом примере продемонстрировать краткость


кода, который получается при использовании утилит и методов Prototype.
Начнем с того, что создадим стилевые классы в файле styles.css (лис-
тинг 13.4), указав различные классы для заголовков и простых ячеек таб-
лицы.
Листинг 13.4. Файл styles.css
.Table1
{ border: DarkGreen 1px solid;
background-color: LightGreen;
width : 300px;
}
.TableHead1
{ font-family: Verdana, Arial;
font-weight: bold;
font-size: 10pt;
}
.TableContent1
{
font-family: Verdana, Arial;
font-size: 10pt;
}
.Table2
{ border: DarkBlue 1px solid;
background-color: LightBlue;
width : 300px;
}
.TableHead2
{ font-family: Verdana, Arial;
Глава 13. Библиотека Prototype 139

font-weight: bold;
font-size: 10pt;
}
.TableContent2
{ font-family: Verdana, Arial;
font-size: 10pt;
}

Создадим теперь страницу, подключив в нее стилевой файл и библиотеку


Prototype (листинг 13.5). На странице у нас будут три таблицы и три кнопки.
Щелчок по кнопке вызывает соответствующую функцию.
Функция setStyle1 начинает свои действия с того, что методом $$() выби-
рает таблицы со стилевым классом Table1, создавая список объектов. Затем
методом first выбираем первый объект из полученного массива и методом
toggleClassName меняем его стилевой класс.

Листинг 13.5. Утилиты и методы Prototype


<html>
<head><title>Prototype</title>
<script src="scripts/prototype.js" language="JavaScript"
type="text/javascript" > </script>
<link href="styles.css" type="text/css" rel="stylesheet"/>
<script language="Javascript">
function setStyle1()
{
var spisok = $$('table.Table1');
spisok.first().toggleClassName('Table2');
}
function setStyle2()
{
var spisok = $$('th');
spisok.last().remove();
}
function setStyle3()
{
var spisok = $$('th');
for(i=0; i<spisok.length;i++){
spisok[i].toggleClassName('Table2');}
140 Часть III. Библиотеки для работы с AJAX
}
}
</script>
</head>
<body>
<table class="Table1">
<tr><th>Заголовок первой таблицы</th></tr>
<tr><td>Ячейка первой таблицы</td></tr>
<tr><td>Ячейка первой таблицы</td></tr>
</table>
<table class="Table2">
<tr><th>Заголовок второй таблицы</th></tr>
<tr><td>Ячейка второй таблицы</td></tr>
<tr><td>Ячейка второй таблицы</td></tr>
</table>
<table class="Table1">
<tr><th>Заголовок третьей таблицы</th></tr>
<tr><td>Ячейка третьей таблицы</td></tr>
<tr><td>Ячейка третьей таблицы</td></tr>
</table> <br />
<input type="button" value="Изменить CSS-класс первой таблицы"
onclick="setStyle1();" /> <br />
<input type="button" value="Удалить последний из заголовков таблиц"
onclick="setStyle2();" /> <br />
<input type="button"
value="Изменить стили заголовков" onclick="setStyle3();" />
</body>
</html>

Функция setStyle2 из списка заголовков таблиц выбирает последний и уда-


ляет его из дерева документа. Функция setStyle3 отбирает все заголовки и,
перебирая их в цикле, меняет их стили. Как легко заметить, работа с метода-
ми библиотеки Prototype легка и проста по синтаксису.
Напоследок еще один пример, демонстрирующий применение метода A(),
создающего из коллекции элементов массив, к которому можно применить
метод each() (листинг 13.6). Сам же метод each(), перебирая элементы мас-
сива в цикле, применяет к каждому элементу анонимную функцию, которая
тут же и определена.
Глава 13. Библиотека Prototype 141

Листинг 13.6. Применение метода each()


<html>
<head>
<title>Класс Array</title>
<script src="scripts/prototype.js"></script>
<link href="styles.css" type="text/css" rel="stylesheet"/>
<script>
function showOptions(){
var someNodeList = $('lstEmployees').getElementsByTagName('option');
var nodes = $A(someNodeList);
nodes.each(function(node){
alert(node.nodeName + ': ' + node.innerHTML);
};
}
</script>
</head> <body>
<select id="lstEmployees" size="3" >
<option value="5">Алексей</option>
<option value="8">Анна</option>
<option value="1">Марк</option>
</select><br />
<input type="button" value="перебрать опции списка"
onclick="showOptions();" >
</body>
</html>

AJAX в Prototype
Для создания AJAX-запросов с помощью Prototype можно использовать объ-
екты классов, наследующих базовому классу, определенному в Prototype —
это класс Ajax.Base. Приведем фрагмент его кода, чтобы читателю стало
ясно, какие опции запросов установлены с самого начала:
Ajax.Base = function() {};
Ajax.Base.prototype = {
setOptions: function(options) {
this.options = {
method: 'post',
asynchronous: true,
142 Часть III. Библиотеки для работы с AJAX
contentType: 'application/x-www-form-urlencoded',
encoding: 'UTF-8',
parameters: ''
}
}
}

Итак, начиная с этого момента, никакого произвола с выбором кодировки:


в Prototype, как и во всех остальных рассматриваемых в данной книге биб-
лиотеках, можно использовать только UTF-8.
Но реальная работа по отправке запроса и обработке его результатов выпол-
няется не объектом класса Ajax.Base, а объектами иных классов. Посмотрим,
как это происходит.
Класс Ajax.Request
Класс Ajax.Request наследует классу Ajax.Base. Объект класса
Ajax.Request обычно используется в том случае, когда сервер возвращает
данные в формате XML, что, естественно, требует дальнейшей обработки.
Синтаксис тут прост:
new Ajax.Request(url[, options])

Конструктор Ajax.Request создает один экземпляр объекта и принимает два


параметра:
url — адрес запрашиваемого ресурса, выбранный URL, подвергается про-
верке браузера на безопасность. Во многих случаях браузер не будет со-
вершать запрос, если URL не принадлежит тому же хосту, что и текущая
страница;
options — опции запроса, частично это те, что унаследованы от базового
класса, кроме того, в опциях можно задать callback-функции, которые вы-
зываются при изменении состояния запроса.
Одни и те же типы callback-функций используются во всех классах, реали-
зующих AJAX-запросы и наследующих классу Ajax.Base. Приведем их
в табл. 13.4.
Таблица 13.4. Callback-функции AJAX-запросов
Callback-функция Описание
onComplete Выполняется в конце жизненного цикла запроса
onException Выполняется при возникновении ошибки объекта
XMLHttpRequest
Глава 13. Библиотека Prototype 143

Таблица 13.4 (окончание)


Callback-функция Описание
onFailure Функция, которая будет вызвана, если AJAX-запрос завер-
шится с ошибкой. Выполняется перед onComplete
onSuccess Функция, которая будет вызвана, если AJAX-запрос завер-
шится успешно. Выполняется перед onComplete
onXYZ XYZ — статус HTTP-ответа сервера. Предотвращает выпол-
нение onSuccess/onFailure. Выполняется перед
onComplete

Рассмотрим пример использования объекта Ajax.Request:


var req=new Ajax.Request( 'myData.xml',
{ method: 'get',
parameters: { name:'Анна', likes:'шоколад' },
onLoaded: function(){ alert('Загружено!'); },
onComplete: function(){alert('Готово!' + req.transport.responseText);}
}
);

Первый параметр объекта — адрес запроса, запрос будет выполнен, если за-
данный здесь адрес указывает на тот же хост, откуда была получена текущая
страница. Второй параметр вызова Ajax.Request представляет собой ано-
нимный объект. Это значит, что мы передаем объект, который имеет свойст-
во method, содержащее строку 'get', свойство parameters, содержащее
строку параметров HTTP-запроса и свойства onLoaded и onComplete, содер-
жащее ссылки на соответствующие функции.
Поскольку в Prototype по умолчанию используется метод POST, то метод
GET приходится указывать явно. Обработчики onLoaded и onComplete
представляют собой callback-функции, которые выполняются, когда
свойство readyState объекта xmlHttpRequest изменяется. Переменная
req.transport.responseText в функции onComplete представляет собой
свойство responseText объекта xmlHttpRequest, точнее, объекта, оберты-
вающего объект xmlHttpRequest. Объект этот называется Ajax.Response
и заслуживает отдельного рассмотрения.

Класс Ajax.Response
Объект этого класса передается в качестве первого аргумента (если таковые
вообще передаются) всем callback-функциям AJAX-запросов. Как только что
было упомянуто, класс Ajax.Response обертывает объект XMLHttpRequest,
обеспечивая работу с ним некоторыми дополнительными возможностями.
144 Часть III. Библиотеки для работы с AJAX

Свойства класса Ajax.Response описаны в табл. 13.5.


Таблица 13.5. Свойства объекта Ajax.Response
Свойство Описание
status Код HTTP-ответа сервера
statusText Текстовое сообщение о статусе HTTP-ответа сервера
readyState Текущее состояние AJAX-запроса. Значения такие же, как у объ-
екта xmlHttpRequest
responseText Текст ответа
responseXML XML-ответ сервера, если значение заголовка Content-type —
application/xml, иначе — null
responseJSON Ответ в формате JSON, если значение заголовка Content-
type — application/json, иначе — null
request Сам объект запроса (экземпляр класса Ajax.Request или клас-
са Ajax.Updater)
transport Сам объект xmlHttpRequest

Класс Ajax.Updater
Как сказано в документации, класс Ajax.Updater является конкретизацией
класса Ajax.Request, и все, что справедливо для Ajax.Request, справедливо
и для Ajax.Updater. Синтаксис создания объекта этого класса таков:
new Ajax.Updater(container, url[, options])

Здесь container — тот элемент, внутрь которого будет помещен ответ серве-
ра. Ajax.Updater помещает текстовый ответ сервера в выбранную ветвь
DOM и используется, когда запрошенный серверный ресурс возвращает фраг-
мент HTML, который вы хотите напрямую вставить в определенный элемент
на странице. Для этого класса используются две специфические опции:
insertion — функция, которая будет вызвана, чтобы вставить полу-
ченный текст внутрь элемента. Она будет вызвана с двумя аргументами:
элементом, который надо обновить, и текстом ответа;
evalScript — определяет, будут ли выполнены блоки <script>, получен-
ные в составе ответа сервера.
Пример применения Ajax.Updater:
var myAjax = new Ajax.Updater( {success: 'placeholder'}, url,
{method:'get', parameters:' ', onFailure: reportError} );
Глава 13. Библиотека Prototype 145

Элемент со значением id="placeholder" должен обновляться только в слу-


чае удачного запроса. Для этого используем свойство success, которое будет
использовано, если все сработает успешно. Опция onFailure указывает на
функцию, которая будет обрабатывать ошибочные состояния.

Класс Ajax.PeriodicalUpdater
Объект этого класса создается периодически и использует объект
Ajax.Updater для обновления элементов на странице или для любых других
действий, которые может выполнять Ajax.Updater. Здесь нам понадобятся
еще две специфические опции:
frequency — период между обновлениями в секундах. По умолчанию
2 секунды;
decay — скорость уменьшения частоты запросов. Дело в том, что при
инициализации нового запроса сценарий проверяет, отличался ли послед-
ний ответ сервера от предыдущего. Если ответы оказались одинаковыми,
то новый запрос отправляется через более длительное время, равное зна-
чению опции frequency, умноженной на значение decay. То есть если
decay=2, то при получении одинаковых ответов каждый новый запрос бу-
дет отправляться через промежуток времени в 2 раза длиннее, чем преды-
дущий.
П РИМЕЧАНИЕ
На просторах Рунета (http://habrahabr.ru/blogs/Ajax/29156/) нашлось такое
важное уточнение: для того чтобы периодический запрос работал корректно
в Internet Explorer, следует использовать метод POST:
new Ajax.PeriodicalUpdater('items', '/items', { method: 'post',
frequency: 3, decay: 2 });

Использование AJAX-запросов в Prototype


Рассмотрим, наконец, полноценные работающие примеры применения
AJAX-запросов с помощью объектов библиотеки Prototype. Запросим у кли-
ента снова его номер телефона (листинг 13.7).
Листинг 13.7. Применение объекта класса Ajax.Updater
<html>
<head>
<title>Ajax.Updater </title>
<script src="scripts/prototype.js" language="JavaScript"
146 Часть III. Библиотеки для работы с AJAX
type="text/javascript"> </script>
<script type="text/javascript" language="JavaScript">
function check_function() {
if($F('tel').length == 7) {
var url = 'check.php';
var params = 'tel=' + $F('tel');
var request = new Ajax.Updater(
{success: 'Result'}, url,
{method: 'get',parameters: params, onFailure: reportError}
);
}
}
function reportError(request) {
$('Result').innerHTML = "Ошибка при соединении с сервером";
}
</script>
</head>
<body>
<form>
Укажите номер телефона (7 цифр): <br />
<input type="text" name="tel" id="tel" onkeyup="check_function();" />
<div id="Result"></div>
</form>
</body>
</html>

Подготовим форму с полем, идентифицируемым значением tel, для ответа


сервера создадим блок с идентификатором Result. Каждый раз, когда поль-
зователь пишет что-либо в поле формы, а значит, и щелкает по клавиатуре,
вызывается функция check_function, которая для начала проверяет, не равна
ли семи длина поля tel, если же равна, то создается объект запроса класса
Ajax.Updater, отправляющий запрос к сценарию check.php, методом GET.
Серверный сценарий check.php ясен и краток:
<?php echo "Вы указали ".$_GET['tel']; ?>

В случае неудачного запроса будем вызывать функцию reportError, прини-


мающую объект запроса в качестве параметра. При удаче ответ сервера запи-
сывается внутрь блока, идентификатор которого указан в опции success.
В следующем примере AJAX-запрос возвращает данные в формате JSON
(листинг 13.8).
Глава 13. Библиотека Prototype 147

Листинг 13.8. Запрос данных в формате JSON


<html>
<head>
<title>AJAX Приложение </title>
<style type="text/css">
.mydiv {
background-color:#cccccc;
width:100;
height:50;
}
</style>
<script src="scripts/prototype.js" language="JavaScript"
type="text/javascript"></script>
<script type="text/javascript" language="JavaScript">
function ask() {
var url = "json.php";
var Ajax =
new Ajax.Request(
url,
{method: "get",
onSuccess: function(transport){
var data = eval("(" + transport.responseText + ")");
$("update_me").setStyle({backgroundColor:data.colorcode});
}
}
);
}
</script>
</head>
<body>
<h2 onclick="ask();">Щелкни здесь!</h2>
<div id="update_me" class="mydiv"></div>
</body>
</html>

Попросим пользователя щелкнуть по тексту внутри заголовка h2, для которо-


го определена обработка события click. Щелчок приведет к созданию
AJAX-запроса, а результаты запроса будут обработаны и отображены в блоке
с идентификатором update_me.
148 Часть III. Библиотеки для работы с AJAX

Функция ask, вызываемая при щелчке, создает объект Ajax.Request. При


конфигурировании этого объекта укажем, что запрос методом GET отправля-
ется к серверному сценарию json.php. Мы определяем, что при успешном за-
вершении запроса должна выполняться анонимная функция (описанная в оп-
ции onSuccess), которая в качестве параметра получает объект запроса.
В начале работы этой функции вызывается eval, обрабатывающая текстовый
ответ сервера, хранящийся в свойстве transport.responseText. Обратите
внимание на синтаксис параметра, передаваемый этой функции. Как мы уже
упоминали в главе 4, для того чтобы функция eval корректно обработала
данные, надо поместить их в дополнительные скобки. Функция вернет объ-
ект JavaScript, который мы и запишем в переменную data. Затем метод $()
находит элемент с идентификатором update_me. К возвращаемому этим ме-
тодом элементу применяем метод setStyle, который дает новое значение
цвету фона (свойство backgroundColor). Само присваиваемое значение бе-
рется из свойства colorcode объекта data.
Для того чтобы понять, откуда взялось такое свойство, надо посмотреть на
серверный сценарий json.php, который очень похож на тот, что рассматри-
вался в главе 4. Приведем его здесь (листинг 13.9).
Листинг 13.9. Сценарий json.php
<?php
class car{
public $make = "Ford";
public $model = "Focus";
public $colorcode = "#ff0000";
public $color = "красный";
public $date = array("year"=>2008, "month"=>"may");
}
$test = new car;
echo json_encode($test);
?>

Как видите, свойство colorcode вполне подходит для того, чтобы его значе-
ние стало цветом квадратика на нашей странице.
Подводя итоги этой главы, можно сказать, что Prototype позволяет облегчить
жизнь Web-программиста, хотя и не содержит поражающие воображение
средства создания пользовательских интерфейсов. Несмотря на некоторые
недостатки официальной документации, использовать эту библиотеку
несложно.
Глава 14

Библиотека script.aculo.us

Script.aculo.us представляет собой библиотеку JavaScript с открытым кодом


для ускорения разработки пользовательских интерфейсов для Web-сайтов.
Она включает в себя механизм реализации визуальных эффектов, библиотеку
средств drag & drop, ряд элементов управления (controls), основанных на
AJAX, в том числе автодополнение, слайдеры (sliders), редактирование тек-
ста по месту (in-place editing) и ряд других средств.
Вы можете скачать script.aculo.us со страницы http://script.aculo.us/
downloads.
Поскольку script.aculo.us использует библиотеку Prototype, последняя пона-
добится вам также. И конечно, script.aculo.us использует кодировку UTF-8.
Весь комплект библиотек, который потребуется нам в этой главе, состоит из
следующих файлов:
prototype.js;
scriptaculous.js;
builder.js;
effects.js;
dragdrop.js;
slider.js;
controls.js.
Поместите эти файлы, например, в каталог scripts и подключайте к вашей
странице, добавив к ней следующие теги:
<script src="scripts/prototype.js" type="text/javascript"> </script>
<script src="scripts/scriptaculous.js" type="text/javascript"> </script>

Сценарий scriptaculous.js автоматически подгружает все остальные фай-


лы этой библиотеки, необходимые для создания визуальных эффектов,
150 Часть III. Библиотеки для работы с AJAX

слайдеров и т. п. Если вы не хотите, чтобы подгружались все файлы, то мо-


жете ограничить список загружаемых файлов, указав их через запятую сле-
дующим образом:
<script src="scriptaculous.js?load=effects,dragdrop"
type="text/javascript"></script>

Так можно указать файлы библиотек builder, effects, dragdrop, controls и slid-
er. Обратите внимание, что для обеспечения их правильной работы сценарии
нужно подключать в указанном порядке.

Эффекты
Библиотека визуальных эффектов (effects.js) содержит средства анимации
для Web-страниц. Анимация выполняется строго в соответствии с указанны-
ми временными параметрами, не зависящими от скорости отображения
эффекта в браузере клиента. Эффекты работают в Firefox, Internet Explorer,
Safari, iPhone и других браузерах.
Библиотека содержит средства для создания базовых эффектов (Core Effects),
комбинационных эффектов (Combination Effects), представляющих собой
комбинации базовых эффектов и эффекты очередей (Queues Effects), послед-
ние представляют собой средства для построения очередности выполнения
эффектов, создавая анимацию.
Имеется семь базовых эффектов, составляющих основу библиотеки
script.aculo.us:
Effect.Opacity — изменение прозрачности;
Effect.Scale — изменение размеров;
Effect.Morph — изменение свойств CSS для элемента;
Effect.Move — движение элемента;
Effect.Highlight — подсветка элемента;
Effect.Parallel — объединение нескольких эффектов;
Effect.Tween — установка параметров.
Базовый синтаксис создания эффекта таков:
new Effect.EffectName(element, required-params, [options]);

Здесь element может быть либо значением идентификатора элемента, либо


элементом DOM. Параметры required-params зависят от конкретного эф-
фекта и не являются обязательными. Опции options используются для на-
стройки параметров конкретного эффекта.
Глава 14. Библиотека script.aculo.us 151

Например, задание параметров для создания эффекта прозрачности может


выглядеть так:
new Effect.Opacity('my_element', {
duration: 2.0,
transition: Effect.Transitions.linear,
from: 1.0,
to: 0.5
});

Общие параметры базовых эффектов представлены в табл. 14.1


Таблица 14.1. Параметры базовых эффектов script.aculo.us
Опция Описание
duration Длительность эффекта в секундах. По умолчанию 1.0
fps Сколько кадров в секунду будет показано. По умолчанию 25.
Не может превышать 100
from Задает начальную точку перехода. Возможные значения: от 0.0
до 1.0. По умолчанию 0.0
to Задает конечную точку перехода. Возможные значения: от 0.0
до 1.0. По умолчанию 1.0
delay Задает паузу в секундах перед началом эффекта. По умолчанию
0.0
transition Задает функцию, которая меняет временной режим анимации.
Поддерживаются такие режимы анимации, как линейный, синусои-
дальный, пульсация, обратный порядок и пр.
sync Устанавливает, будет отображение анимации выполняться авто-
матически или вручную с помощью специальных функций
queue Задает опции порядка выполнения эффекта

Кроме того, дополнительные параметры могут задавать callback-функции,


вызываемые в ходе работы эффекта. Этим функциям надо передавать ссылку
на объект, к которому применяют эффект. Укажем только существующие типы
функций такого рода: beforeStart, beforeUpdate, afterUpdate, afterFinish.
Экземпляры объекта Effect располагают рядом полезных свойств и методов,
таких как:
effect.element — элемент, к которому применяют эффект;
effect.options — опции выполняемого эффекта;
152 Часть III. Библиотеки для работы с AJAX

effect.currentFrame — номер последнего показанного кадра;


effect.startOn, effect.finishOn — время в миллисекундах, когда на-
чался эффект и когда он завершится;
effect.effects[] — массив эффектов при их параллельном выполнении;
effect.cancel() — остановить эффект;
effect.inspect() — получить отладочную информацию.
Посмотрите на примере, приведенном в листинге 14.1, как просто создать
эффект изменения видимости элемента с помощью вызова методов объекта
Effect.

Листинг 14.1. Эффект Effect.toggle в script.aculo.us


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Эффект toggle</title>
<script src="scripts/prototype.js" type="text/javascript">
</script>
<script src="scripts/scriptaculous.js?load=effects"
type="text/javascript">
</script>
</head>
<body>
<a href="javascript:void Effect.toggle('d1', 'blind');">
Показать/Спрятать (blind)</a>
<div id="d1" style="background-color:green; height:50px; width: 100px">
Первый раздел.</div>
<br />
<a href="#" onclick="Effect.toggle('d2','slide',{ duration: 1.0 });
return false;">Свернуть/Развернуть (slide)</a>
<div id="d2" style="display:none;">
<div style="background-color:#ff8080;width:100px;border:2px solid
red;padding:10px;">
Второй раздел.</div></div>
<br />
<a href="#" onclick="Effect.toggle('d3','appear'); return
false;">Показать/Спрятать (appear)</a>
Глава 14. Библиотека script.aculo.us 153

<div id="d3" style="display:none;">


<div style="background-color:#ff8080;width:100px;border:2px solid
red;padding:10px;">
Третий раздел.</div></div>
</body>
</html>

Легко понять, что для создания эффекта достаточно вызвать метод toggle
объекта Effect, указать ему идентификатор элемента, к которому надо при-
менить эффект, и тип эффекта, задав значение опции blind, slide или
appear. Первый прямоугольник при щелчке по нему сворачивается или раз-
ворачивается при каждом щелчке по очереди. Второй прямоугольник разво-
рачивается или сворачивается наподобие свитка, а последний появляется или
исчезает, постепенно набирая или теряя яркость. Поэкспериментируйте
в этом примере с временными параметрами, заданными в опции duration.

Перетаскивание и сортировка
(Draggable & Sortable)
Продемонстрируем теперь сразу несколько эффектов (изменение прозрачно-
сти, подсвечивание элемента) в совокупности с сортировкой и перетаскива-
нием элементов мышью.
Effect.Opacity меняет прозрачность элемента. Например, для того чтобы
элемент за полсекунды стал прозрачнее на 30%, надо задать параметры эф-
фекта так:
new Effect.Opacity('id_of_element', { from: 1.0, to: 0.7, duration: 0.5
});

Метод Effect.Highlight меняет фоновый цвет элемента на короткое время,


создавая эффект вспышки. Он используется для привлечения внимания к
элементам, которые претерпевают какие-либо изменения. Ему можно задать
следующие опции:
startcolor — цвет фона на время вспышки. По умолчанию #ffff99
(светло-желтый);
endcolor — цвет последнего кадра вспышки. По умолчанию #ffffff (бе-
лый);
restorecolor — цвет фона после вспышки. По умолчанию — цвет фона
до вспышки.
154 Часть III. Библиотеки для работы с AJAX

Теперь разберемся с перетаскиванием и сортировкой. Для того чтобы приме-


нить эффект перетаскивания к элементу, надо создать новый экземпляр клас-
са Draggable, используя следующий синтаксис:
new Draggable('id_of_element', [options]);

Основные опции класса Draggable даны в табл. 14.2.


Таблица 14.2. Опции класса Draggable
Опция Описание
zindex Z-индекс перетаскиваемого элемента
constraint Если задано horizontal или vertical, то перетаскивание
осуществляется только по горизонтали или по вертикали
starteffect Задает эффект, применяемый к перетаскиваемому элементу
endeffect Определяет эффект, применяемый при окончании перетаскива-
ния. По умолчанию Effect.Opacity
revert Задает действие по окончании перетаскивания. Если указать
значение true, то элемент после окончания перетаскивания
вернется на исходное место. По умолчанию false

Расширить функциональность Draggable можно, применив методы класса


Sortable. Класс Sortable дает способ организовать сортировку несколь-
ких перетаскиваемых элементов, вложенных внутрь одного контейнерного
элемента. Синтаксис создания сортируемых элементов таков:
Sortable.create('id_of_container',[options]);

Метод Sortable.create можно применять только к контейнерному элементу,


содержащему блочные элементы, за исключением TABLE, THEAD, TBODY и TR.
Таково техническое ограничение в современных браузерах. Для получения
детальной информации и, возможно, обновлений, снимающих такие ограни-
чения, обращайтесь к документации по этой библиотеке в Интернете.
Некоторые опции метода Sortable.create приведены в табл. 14.3.
Таблица 14.3. Опции метода Sortable.create
Опция Описание
tag Тег, который будет сортироваться. По умолчанию li
only Позволяет ограничить сортируемые элементы теми, что относятся
к заданному классу CSS. По умолчанию не указывается, т. е. огра-
ничений нет
Глава 14. Библиотека script.aculo.us 155

Таблица 14.3 (окончание)


Опция Описание
overlap vertical или horizontal. По умолчанию vertical
constraint Ограничение движения элементов. По умолчанию vertical
format Формат задания имени идентификатора. По умолчанию:
/^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/

Callback-функции, которые могут быть связаны с этим методом, могут быть


следующих типов:
onChange — вызывается при перетаскивании;
onUpdate — вызывается при окончании перетаскивания.
Посмотрим, наконец, как все это работает в листинге 14.2. Для начала созда-
дим функцию, которая будет вызываться в начале каждого перемещения
элемента. Ссылка на нее будет храниться в переменной myStartEffect. Эта
функция сначала определяет текущую прозрачность элемента, потом в тече-
ние 0,2 сек меняет ее и подсвечивает перетаскиваемый элемент светло-
желтым фоном.
Листинг 14.2. Drag & Drop с применением script.aculo.us
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title></title>
<script type="text/javascript" src="scripts/prototype.js"></script>
<script src="scripts/scriptaculous.js?load=effects,dragdrop"
type="text/javascript"></script>
<script type="text/javascript">
var myStartEffect = function(element) {
element._opacity = element.getOpacity(element);
new Effect.Opacity(element, {duration:0.2, from:element._opacity,
to:0.7});
new Effect.Highlight(element, {});
}
</script>
156 Часть III. Библиотеки для работы с AJAX
<style type="text/css">
h1 {font-size: 1.2em; text-align:center;}
li {
margin: 5px auto;
padding: 2px;
width: 200px;
text-align:center;
list-style-type:none;
border: 2px solid #779;
background-color: #dde
}
div {
margin: 5px auto;
padding: 2px;
width: 300px;
text-align:center;
border: 2px solid #797;
background-color: #ded
}
</style>
</head>
<body>
<h1>Сортировка без задержки</h1>
<ul id="sort1">
<li id="s1_1">Первый</li>
<li id="s1_2">Второй</li>
<li id="s1_3">Третий</li>
</ul>
<h1>Сортировка с паузой 500 мс</h1>
<ul id="sort2">
<li id="s2_1">Первый</li>
<li id="s2_2">Второй</li>
<li id="s2_3">Третий</li>
</ul>
<h1>Перетаскивание без задержки</h1>
<div id="drag1">
Тащим этот элемент
</div>
<h1>Перетаскивание с задержкой 1000 мс</h1>
Глава 14. Библиотека script.aculo.us 157

<div id="drag2">
Тащим этот элемент
</div>
<script type="text/javascript">
Sortable.create('sort1', {starteffect: myStartEffect});
Sortable.create('sort2', {starteffect:myStartEffect, delay:500});
new Draggable('drag1', {starteffect:myStartEffect});
new Draggable('drag2', {starteffect:myStartEffect, delay:1000});
</script>
</body>
</html>

В теле документа есть два контейнера с идентификаторами sort1 и sort2.


Элементы, вложенные в эти контейнеры, можно будет перетаскивать, но после
окончания перетаскивания они вернутся на свое место, причем установится
исходный порядок их расположения на странице. Элементы с идентификато-
рами drag1 и drag2 можно перетаскивать по экрану и бросать в любом месте,
обратно они не вернутся. Задание параметра delay приводит к тому, что надо
немного подождать после выбора элемента для перетаскивания, пока не начнется
выполнение эффекта. Эксперименты с работающим примером дадут вам всю
необходимую информацию для практического использования этих эффектов.

AJAX в script.aculo.us
Автодополнение
Класс Ajax.Autocompleter позволяет создать автодополнение, т. е. выводит
подсказку в виде выпадающего списка, как только пользователь начинает
набирать текст в поле ввода. Текст подсказки формируется как дополнение
к символам, введенным пользователем, образуя словарные слова. Данные для
подсказки запрашиваются с сервера AJAX-запросом. Класс Ajax.Autocompleter
расширяет класс Ajax.Request библиотеки Prototype, наследуя его свойства.
Для создания объекта автодополнения надо задать идентификатор поля
id_of_text_field, откуда будут считываться символы, вводимые пользова-
телем, идентификатор id_of_div_to_populate того блока, где будут отобра-
жаться данные с сервера, адрес запрашиваемого ресурса на сервере url и,
необязательно, дополнительные опции. Синтаксис создания автодополнения
таков:
new Ajax.Autocompleter(id_of_text_field, id_of_div_to_populate,
url, options);
158 Часть III. Библиотеки для работы с AJAX

Опции создания объекта даны в табл. 14.4.


Таблица 14.4. Опции автодополнения в Ajax.Autocomplete
Опция Значение Описание
по умолчанию
paramName Имя элемента Имя параметра, передаваемого на сервер. Это
имя задается в элементе input, в который поль-
зователь заносит текст для автодополнения
frequency 0.4 Промежуток в секундах между запросами AJAX
minChars 1 Минимальное число символов, которые надо вве-
сти для того, чтобы сгенерировать запрос AJAX
indicator null Id элемента, который отображается в процессе
запроса AJAX. По окончании запроса элемент бу-
дет скрыт

Рассмотрим работу автодополнения на примере (листинг 14.3).


Листинг 14.3. Автодополнение в script.aculo.us
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Ajax автодополнение</title>
<script src="scripts/prototype.js" type="text/javascript"></script>
<script src="scripts/scriptaculous.js?load=effects,controls"
type="text/javascript"></script>
</head>
<body style="padding:30px;">
<style type="text/css">
div.autocomplete {
position:absolute;
width:200px;
background-color:white;
border:1px solid #888;
margin:0px;
padding:0px;
Глава 14. Библиотека script.aculo.us 159

}
div.autocomplete ul {
list-style-type:none;
margin:0px;
padding:0px;
}
div.autocomplete ul li.selected { background-color: #ffb;}
div.autocomplete ul li {
list-style-type:none;
display:block;
margin:0;
padding:2px;
cursor:pointer;
background:none;
}
</style>
<input type="text" id="city" name="city" style="width:200px" value=""
/>
<span id="indicator"
style="height:11px; display:none;"> <img src="1.gif"
width="43" height="11" align="absmiddle" alt="Загрузка..." /></span>
<div id="variants" class="autocomplete"></div>
<script type="text/javascript">
new Ajax.Autocompleter("city", "variants", "cities.php",
{paramName: "city", minChars: 1, indicator: 'indicator'});
</script>
</div>
</body></html>

Страница начинается с поля ввода с идентификатором city. Затем указываем


рисунок, который послужит индикатором загрузки данных с сервера, в каче-
стве такого индикатора обычно используется анимированный GIF, в нашем
случае это файл 1.gif. Индикатор помещаем внутри элемента span с иденти-
фикатором indicator.
Блок div с идентификатором variants отводим под отображение вариантов
подсказки, пришедших с сервера.
Вернемся теперь по коду чуть повыше и посмотрим на таблицу стилей. Зада-
ние стилей CSS для блока div и списка ul является важным фрагментом
программы. Сервер должен прислать неупорядоченный список подсказок,
160 Часть III. Библиотеки для работы с AJAX

которые отобразятся с применением указанных стилей. Для появления под-


сказки пользователь должен набрать хотя бы одну русскую букву (это опре-
деляется параметром minChars и набором слов в конкретно нашем серверном
сценарии).
Слова подсказки отобразятся в блоке с идентификатором variants в виде вы-
падающего списка в стиле li.selected. Имя стилевого класса autocomplete
не является зарезервированным словом, но задать стили надо, иначе подсказ-
ка будет иметь вид простого неупорядоченного списка, а не выпадающего
списка, как выглядит выпадающий список, созданный элементом select.
Сценарий cities.php, выполняющий работу на сервере, дан в листинге 14.4.

Листинг 14.4. Сценарий cities.php, формирующий автодополнение


<?php
$cities = array('Астрахань', 'Барнаул', 'Брянск', 'Волгоград', 'Гатчина',
'Дармштадт', 'Екатеринбург', 'Железноводск', 'Загреб', 'Иваново',
'Курск', 'Москва', 'Луга', 'Новгород', 'Омск', 'Пермь', 'Симферополь',
'Санкт-Петербург', 'Тула', 'Уфа', 'Харьков', 'Ярославль');
$return = array();
$str = mb_strtolower($_POST['city']);
function str_srch($item, $key) {
global $return,$str;
if(mb_strtolower(mb_substr($item,0,mb_strlen($str))) == $str)
$return[] = $item;
}
array_walk($cities,'str_srch');
if (count($return)>0) echo '<ul><li>'. implode('</li><li>', $return).
'</li></ul>';
else echo '<span></span>';
?>

Сценарий cities.php содержит список городов в массиве $cities. Затем сце-


нарий читает присланную клиентом букву из элемента $_POST['city'] и вы-
бирает все подходящие элементы списка. Обратите внимание на то, что рус-
скоязычные строки в кодировке UTF-8 надо сравнивать, применяя функции,
имена которых начинаются на mb (multi-byte).
Работа автодополнения в браузере будет выглядеть так, как показано
на рис. 14.1.
Глава 14. Библиотека script.aculo.us 161

Рис. 14.1. Автодополнение с помощью Ajax.Autocompleter

Класс Ajax.InPlaceEditor
Класс Ajax.InPlaceEditor позволяет редактировать текст и сохранять его на
сервере. Текст может загружаться в область редактирования из серверного
файла или создаваться пользователем с нуля.
Синтаксис создания объекта:
new Ajax.InPlaceEditor(element, url, {options});

Конструктор класса принимает три параметра: HTML-элемент element, в ко-


тором выполняется редактирование, адрес ресурса url, куда отправляется
отредактированный текст, при этом сервер может проверять введенные дан-
ные и обрабатывать полученный от пользователя текст. Третьим параметром
служит набор опций. Серверный сценарий получает данные, отправленные
методом POST. Опции конструктора приведены в табл. 14.5.
Таблица 14.5. Опции объекта Ajax.InPlaceEditor
Название По умолчанию Описание
okControl button Тип кнопки подтверждения
ввода отредактированного
текста (button, link, false)
cancelControl link Тип кнопки отмены при редак-
тировании (button, link,
false)
cancelText cancel Текст в поле отмены редакти-
рования
savingText Saving... Текст, отображаемый при за-
грузке текста на сервер
162 Часть III. Библиотеки для работы с AJAX
Таблица 14.5 (окончание)
Название По умолчанию Описание
clickToEditText Click to edit Текст, показываемый, пока
указатель мыши находится на
редактируемом тексте
rows 1 Количество строк в поле ввода
cols none Количество символов в строке
в поле ввода
highlightcolor Ajax.InPlaceEditor.def Цвет фона элемента
aultHighlightColor
callback function(form) Функция, выполняемая перед
{Form.serialize(form)} отправкой текста на сервер
clickToEditText Click to edit Текст, отображаемый во
всплывающей подсказке при
наведении курсора на редак-
тируемую область
onComplete function(transport, Код, выполняемый при удач-
element) {new Effect. ном обновлении текста на
Highlight(element, сервере
{startcolor:
this.options.highlight
color});}
onFailure function(transport) Код, выполняемый при
{ alert("Error неудаче
communicating with
the server: " +
transport.responseText.
stripTags());}
savingClassName inplaceeditor-saving Класс CSS, добавляемый
к элементу при отображении
сообщения о сохранении
текста

Создадим страницу с возможностью редактирования in-place, используя


класс Ajax.InPlaceEditor. Поле для редактирования у нас будет содержать
15 строк по 40 символов в строке. Отредактированные данные будут переда-
ваться в сценарий demoajaxreturn.php. Пока идет запись на сервер, будет ото-
бражаться уже знакомый по предыдущим примерам анимированный GIF
загрузки. По умолчанию в Ajax.InPlaceEditor данные передаются в пара-
метре value, но мы зададим свое имя для переменной — myparam.
Посмотрите листинг 14.5, чтобы понять, как создается такое поле редакти-
рования.
Глава 14. Библиотека script.aculo.us 163

Листинг 14.5. Ajax.InPlaceEditor


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Ajax.InPlaceEditor </title>
<script src="scripts/prototype.js" type="text/javascript"></script>
<script src="scripts/scriptaculous.js?load=effects,controls"
type="text/javascript"></script>
<style type="text/css" media="screen">
.inplaceeditor-saving { background: url('1.gif') bottom right no-
repeat; }
</style>
</head>
<body style="padding:30px;">
<div class="inplaceeditor-saving"></div>
<p id="editme">Щелкай здесь, чтобы начать редактирование! </p>
<script type="text/javascript">
new Ajax.InPlaceEditor('editme', 'demoajaxreturn.php',{rows:15,
cols:40, cancelControl:"button", savingText: "Сохраняем...", callback:
function(form, value) { return 'myparam=' + encodeURIComponent(value)}});
</script>
</body>
</html>

Индикатор загрузки текста на сервер помещается в блоке с идентификатором


inplaceeditor-saving. Само поле редактирования помечено идентификато-
ром editme. Затем создаем объект Ajax.InPlaceEditor, приводя для его кон-
фигурирования указанные только что опции. Кроме того, мы определяем
callback-функцию, которая сработает при завершении редактирования и от-
правит на сервер переменную myparam с текстом, отредактированным поль-
зователем. Серверный сценарий в нашем примере предельно прост и предна-
значен исключительно для проверки того, что данные действительно пришли
на сервер в параметре myparam:
<?php echo "Вы отправили на сервер: <br />".$_POST['myparam']; ?>

Если вы тестируете этот пример на своем домашнем компьютере, то вряд ли за-


метите процесс загрузки. Тогда добавьте в начало серверного сценария функцию
sleep(2), которая сделает паузу длиной две секунды перед отправкой текста
клиенту, а вы сможете увидеть работу анимированного рисунка заставки.
Часть IV
Библиотека ExtJS
Глава 15

Структура и идеология
библиотеки
Вокруг уже шуршали пески и шумели
водопады милой моему сердцу Внут-
ренней Монголии.
В. Пелевин

ExtJS — это библиотека, написанная на JavaScript и расширяющая возмож-


ности этого языка. В 2006 г. Джек Слокум (Jack Slocum) начал разрабатывать
расширения для библиотеки Yahoo! User Interface (YUI). К концу года эти
расширения превратились в популярную библиотеку, которая была названа
ExtJS. ExtJS использует двойную модель лицензирования. Она доступна как
под открытой LGPL, так и под коммерческой лицензией.
В настоящее время можно посоветовать очень мало мест, где можно узнать
про ExtJS. Но вот главные сайты:
http://extjs.com/ — основной сайт библиотеки ExtJS, с которого можно
скачать свежую версию библиотеки, документацию и примеры;
http://techwork.ru/extjs-book/, http://extjs.ru/ — русскоязычные ресурсы,
посвященные ExtJS.
На компакт-диске, приложенном к книге, читатель найдет версию ExtJS-2.2.
В каталоге extjs-2.2 находятся следующие файлы:
ext-all.js — сжатая версия всех компонентов библиотеки;
ext-all-debug.js — все компоненты библиотеки без комментариев (код дос-
тупен для просмотра и отладки);
ext-core.js — сжатая версия ядра библиотеки;
ext-core-debug.js — основные компоненты без комментариев (код досту-
пен для просмотра и отладки).
В том же каталоге extjs-2.2 лежит англоязычная документация, вольное пере-
ложение которой и составляет теоретическую часть данной главы. Кроме то-
го, там много прекрасных примеров, часть которых мы тоже разберем здесь.
168 Часть IV. Библиотека ExtJS

Сразу хочется отметить, что русскоязычная терминология, необходимая


в этой главе, далека от состояния стабильности. Большинство разработчиков
пользуется выражениями, являющимися кальками английских терминов.
Авторы настоящей книги далеки от желания перевести любой термин на рус-
ский язык, не считаясь с тем, что в следующей же статье или книге читателю
попадется другой перевод, только отдаленно напоминающий исходное анг-
лийское слово.
На компакт-диске находится полная документация на английском языке
extdocs-2.2.air, работать с которой можно после установки ее в систему с по-
мощью программы AdobeAirInstaller. После установки окно с документацией
откроется в виде, представленном на рис. 15.1.

Рис. 15.1. Документация по ExtJS

Библиотека ExtJS представляет собой наиболее последовательное расшире-


ние языка JavaScript, формирующее объектную модель, привычную для про-
граммистов, которые оперируют классами, наследованием и инкапсуляцией.
Ранее в главе 3 мы рассмотрели эти аспекты работы с JavaScript, и освоивший
ее читатель достаточно подготовлен к встрече с ExtJS. С другой стороны,
в ExtJS широко используется и обработка событий, составляющая гвоздь
почти любой программы на JavaScript.
Глава 15. Структура и идеология библиотеки 169

Соглашения об именах
При создании библиотеки ExtJS были выработаны следующие соглашения об
именах объектов:
имена классов начинаются с прописных букв (GridPanel, Observable
и т. д.);
имена событий состоят из строчных букв (click, dblclick и т. д.);
константы полностью указываются в верхнем регистре (DAY, HOUR и т. д.);
все прочие идентификаторы имеют смешанный регистр (ext, doSomething,
myValue и т. д.).

Конфигурирование ExtJS
и первый пример применения
В начале каждой страницы, применяющей ExtJS, надо подключать файлы
этой библиотеки. Это выглядит так, как показано в листинге 15.1.
Листинг 15.1. Подключение библиотеки ExtJS
<html>
<head>
<title>Введение в Ext</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="ext-2.2/adapter/ext/ext-base.js">
</script>
<script type="text/javascript" src="ext-2.2/ext-all-debug.js"></script>
<script type="text/javascript" src="ExtStart.js"></script>
<link rel="stylesheet" type="text/css"
href="ext-2.2/resources/css/ext-all.css">
<link rel="stylesheet" type="text/css" href="ExtStart.css">
</head>
<body>
<h1>Введение в Ext</h1>
<div id="content">
<p>Это стартовая страница.</p>
</div>
</body>
</html>
170 Часть IV. Библиотека ExtJS
П РИМЕЧАНИЕ
Обратите внимание, что, как и в других библиотеках JavaScript, в ExtJS исполь-
зуется исключительно кодировка UTF-8.
В файле ExtStart.js у нас будет следующий код (листинг 15.2).
Листинг 15.2. Сценарий ExtStart.js
Ext.onReady(function() {
alert("Поздравляю! Ваша Ext сконфигурирована правильно!");
});

Открыв в браузере эту первую HTML-страницу, вы должны увидеть то, что


представлено на рис. 15.2.

Рис. 15.2. Вид стартовой страницы в браузере

Итак, наш первый сценарий на ExtJS начинается с обращения к объекту Ext.


Объект этот является экземпляром синглетона Ext. Синглетон Ext обеспечи-
вает нам доступ к функциональности этого класса, задавая при этом общее
пространство имен. Метод Ext.onReady вызывается на том этапе загрузки
документа в браузер, когда объектная модель документа уже построена и го-
това к работе, но, возможно, не все картинки и другие фрагменты страницы
уже получены браузером.
Если модальное окно с поздравлением не появилось на вашем экране, прове-
ряйте, правильно ли указаны пути до библиотек.
Глава 15. Структура и идеология библиотеки 171

Далее в этой главе будет много теории. При первом чтении ее можно пропустить,
но после разбора примеров следует возвращаться к ней и перечитывать различ-
ные фрагменты, что приведет к более глубокому пониманию логики ExtJS.
Объект Ext.Element
Вся работа в JavaScript начинается с выбора элемента HTML-страницы.
Мы запрашиваем его, вызывая привычный метод getElementById:
var myDiv = document.getElementById('myDiv');

Возвращаемый объект является узлом DOM и предоставляет не слишком


много удобных методов для работы с ним. Приходится писать еще длинный
код, позволяющий выполнять нужные действия.
Объект Ext.Element библиотеки ExtJS содержит метод, позволяющий полу-
чать доступ к элементам и выполнять действия над ними. Метод Ext.get (со-
кращенное обозначение метода Ext.Element.get) возвращает объект с ука-
занным идентификатором. Вот как выглядит вызов этого метода:
Ext.onReady(function() {
var myDiv = Ext.get('myDiv');
});

Отметим некоторые примечательные черты объекта Element:


объект Element обертывает многие привычные нам методы и свойства
DOM, создавая удобный кроссбраузерный интерфейс;
метод Element.get создает внутренний кэш, так что многократные обра-
щения к одному и тому же объекту выполняются очень быстро;
многие действия, которые обычно приходится совершать над элементом
DOM, встроены в кроссбраузерные методы объекта Element, например,
удаление или добавление стилевых классов, позиционирование, перетас-
кивание, удаление или добавление обработчиков событий и др.
Все это позволяет сократить объем создаваемого кода. Вот, например, что
можно сделать с элементом, запрошенным методом get, с помощью различ-
ных методов ExtJS:
myDiv.highlight() — фон элемента станет желтым, а потом поблекнет;
myDiv.addClass('red') — добавляем к элементу пользовательский класс
CSS (например, определенный в присоединенном к документу стилевом
файле);
myDiv.center() — помещаем элемент в центр видимой области;
myDiv.setOpacity(.25) — делаем элемент частично прозрачным.
172 Часть IV. Библиотека ExtJS

Firebug — запаситесь выжигателем жучков


FireBug — это расширение для браузера Firefox, являющееся отладчи-
ком JavaScript. Firebug показывает в консоли функцию, вызвавшую ошибку,
параметры запросов к серверу, дерево DOM документа и много другой отла-
дочной информации. Если у вас до сих пор не установлен этот удобный ин-
струмент, то перейдите на сайт http://getfirebug.com/, скачайте и настройте
у себя Firebug. Посмотрите приложение 2, чтобы узнать о некоторых воз-
можностях Firebug.

Контекст
Мы будем применять термин "контекст" для обозначения области действия
или области видимости свойства (переменной) или метода (функции). Тер-
мин этот соответствует английскому "scope".
Для демонстрации контекста выполните следующий код, делать это удобнее
всего с помощью Firebug. Перейдите в Firebug на вкладку Script и напечатай-
те слово "window" в строке "New watch expression..." в правой части Firebug,
затем нажмите клавишу <Enter>. Создадим два объекта (o1 и o2). Их свойст-
ва и методы имеют одинаковые имена, но разные значения.
var o1 = {testvar:22, fun:function() { alert('o1: ' + this.testvar); }};
var o2 = {testvar:33, fun:function() { alert('o2: ' + this.testvar); }};

Запускать код на выполнение в Firebug можно щелчком по кнопке Run.


Вызвать метод fun(), просто обратившись к нему, нельзя, он не является ме-
тодом глобального объекта, и попытка приведет к сообщению об ошибке
в любом из указанных здесь способов:
fun();
window.fun();
this.fun();

Еще бы! Объект window не имеет метода fun. Следующий же код вполне кор-
ректен, ведь теперь метод вызывается в контексте того объекта, где он дейст-
вительно объявлен:
o1.fun();
o2.fun();

А сейчас самое интересное. Пусть наши объекты имеют методы fun1 и fun2.
var o1 = {testvar:22, fun1:function() { alert('o1: ' + this.testvar); }};
var o2 = {testvar:33, fun2:function() { alert('o2: ' + this.testvar); }};
Глава 15. Структура и идеология библиотеки 173

Вызовем метод fun1 объекта o1, но так, чтобы он выполнялся для того значе-
ния свойства testvar, которое определено в объекте o2:
o1.fun1.call(o2); // Будет выведено o1: 33

Выполнив указанные действия, вы должны увидеть в окне браузера картину,


представленную на рис. 15.3. Вот это и называется выполнить функцию
o1.fun1 в контексте объекта o2. Таким образом, от того, в каком контексте
выполняется функция, будет зависеть значение свойства объекта.

Рис. 15.3. Запуск методов с помощью Firebug

Задание контекста в ExtJS


Контекст можно рассматривать как особый параметр каждой функции. Java-
Scriptзадает его при каждом вызове. Функция может быть определена как
элемент некоторого объекта. В этом случае функция запускается в контексте
этого объекта. Если функция не является элементом объекта, то контекстом
174 Часть IV. Библиотека ExtJS

служит контекст глобального объекта (в браузере глобальным объектом яв-


ляется объект window).
Выполним с помощью Firebug следующий код на JavaScript (листинг 15.3).

Листинг 15.3. Вложенные функции


var testvar = 'window property';
var o3 = {
testvar:'3',
testvar2:'3**',
fun:function(){
alert('o3: '+this.testvar); // пишет '3'
var inner = function(){
alert('o3-inner: '+this.testvar); // выводит 'window property'
alert('o3-inner: '+this.testvar2); // выводит 'undefined'
};
inner();
}
};
o3.fun();

Обратите внимание на то, что функция inner выполняется не в том контек-


сте, что функция fun. Библиотека ExtJS дает нам возможность задать по сво-
ему усмотрению контекст, в котором будут выполняться функции.
Переменные, созданные внутри блоков (таких как, например, цикл for или
оператор if), продолжают существовать до конца функции. Например, за-
пустим такую функцию:
function foo() {
var i=123;
for (var i=0; i<3; i++) { }
alert(i);
}
foo();

Будет выведено число 3. Это происходит потому, что переменная i опреде-


лена внутри функции, но не локально внутри блока for. Поэтому i, которая
используется внутри цикла, — это та же переменная i, что была определена
вне цикла.
Глава 15. Структура и идеология библиотеки 175

Адаптеры и пространство имен


Одна из задач, которую ставили себе разработчики библиотеки ExtJS, была
возможность сосуществования с другими JavaScript-библиотеками. Для этого
были добавлены методы, помогающие разработчику создавать, наследовать
и сопровождать код различных классов.
ExtJS использует специальные адаптеры для доступа к функциям библиотек
YUI, jQuery, Protoype, Scriptaculous.
В дистрибутиве ExtJS включены следующие адаптеры:
ext-base — встроенный адаптер самой библиотеки для выполнения низко-
уровневых операций;
jQuery — библиотека jQuery версии 1.2.3 и ext-jquery-adapter, сам адаптер;
Prototype — библиотека Prototype, адаптер к ней, Scriptaculous. Базовые
визуальные эффекты реализуются с помощью расширения Effects для
Scriptaculous;
YUI — адаптер, использующий компоненты Yahoo! User Interface.
Для применения встроенного адаптера в код страницы надо добавить строку
<script type="text/javascript" src="ext-2.2/adapter/ext/ext-base.js">
</script>

Для того чтобы создаваемый программистом код не конфликтовал с другими


библиотеками, надо использовать пространство имен, которое в ExtJS явля-
ется объектом, содержащим список определений других объектов. В ExtJS
существует метод Ext.namespace, который позволяет определить все про-
странства имен за один вызов.
Метод Ext.namespace создает пространство имен, которое будет использова-
но для задания области действия переменных и классов. Например:
Ext.namespace('Company', 'Company.data');
Company.Widget = function() { ... }
Company.data.CustomStore = function(config) { ... }

Механизм наследования в ExtJS


Класс Ext включает в себя метод extend, который позволяет реализовать ме-
ханизм наследования в библиотеке ExtJS. Он дает возможность менять или
расширять функциональность любого класса, не внося изменение непосред-
ственно в код класса, что обычно и трактуется как создание производного
класса, иначе говоря, реализует механизм наследования.
176 Часть IV. Библиотека ExtJS

Для того чтобы создать новый класс, наследующий существующему классу,


надо сначала объявить конструктор нового класса, а затем вызвать метод
extend, чтобы определить свойства нового класса, которые будут общими
для всех экземпляров этого нового класса.
JavaScript не имеет механизма автоматического вызова конструктора базово-
го класса, так что приходится вызывать его явно (листинг 15.4). При этом
первый аргумент всегда должен быть this, чтобы конструктор выполнялся
в контексте вызывающей его функции.
Листинг 15.4. Создание класса-наследника в ExtJS
MyNewClass = function(arg1, arg2, etc) {
// Явный вызов конструктора базового класса
MyNewClass.superclass.constructor.call(this, arg1, arg2, etc);
};

Ext.extend(MyNewClass, SomeBaseClass, {
theDocument: Ext.get(document),
myNewFn1: function() {
// Необходимые действия
},
myNewFn2: function() {
// Еще какие-то нужные действия
}
});

Вызов метода базового класса


Если в классе-наследнике был перегружен некоторый метод someFunction, а по-
том захотелось вызвать метод someFunction базового класса, то надо сделать так:
MyClass = Ext.extend(Ext.SomeClass, {
someFunction : function(arg1, arg2){
// Какой-то фрагмент кода функции.
// Вызов базового класса
MyClass.superclass.someFunction.call(this, arg1, arg2);
// Еще один фрагмент кода
}
);

Для корректного задания контекста не забывайте указывать this в качестве


первого параметра функции call.
Глава 15. Структура и идеология библиотеки 177

Обработка событий в ExtJS


Событие — это некоторое сообщение, генерируемое программой, кото-
рую называют источником события. Событие адресовано другой программе,
которую называют по-английски "event listener", что можно перевести как
"слушатель события". Этот дословный перевод имеет, по крайней мере,
то достоинство, что из него становится понятна логика действий программы.
Но хочется все-таки говорить по-русски, поэтому будем называть эту про-
грамму обработчиком. Хм... С русским как-то не очень получается. Ладно.
События генерируются в результате реакции на изменение в источнике собы-
тия. Последний не зависит от обработчиков и генерирует событие даже в том
случае, когда никто не готов его обрабатывать.
Обработчик же готов предпринять некоторые действия в том случае, если его
оповестят о событии.
В ExtJS есть два типа событий: события DOM и события JavaScript или про-
граммные события.

События DOM
Браузеры применяют механизм обработки событий при отображении
XHTML-страниц и создают события, когда пользователь выполняет какие-
либо действия над элементами DOM.
Привычный способ обработки выглядит так:
<div id="mydiv" onclick="alert('Щелкнули!')">Щелкни здесь!</div>

Объект Ext.Element позволяет объединить элемент DOM вместе с его собы-


тиями с помощью метода on, и обработка того же самого, что и в предыду-
щем примере, события выглядит так:
Ext.get('mydiv').on('click', function() {alert('Щелкнули!');});

Можно сказать, что события DOM передаются из объектной модели обра-


ботчику при помощи объекта Ext.Element.

События JavaScript
Элементы DOM — не единственные источники событий. Вполне естественно
распространить логику генерации и обработки событий на любой объект
JavaScript. Многие объекты ExtJS могут извещать обработчиков о наступле-
нии события.
178 Часть IV. Библиотека ExtJS

Если у вас есть объект какого-либо класса из библиотеки ExtJS, например


панель, и надо предпринять какие-то действия при изменении размера пане-
ли, то можно задать обработчик события таким образом:
// Создаем панель
var myPanel = new Ext.Panel({...});
// Задаем обработчик изменения размера
myPanel.on('resize', function(panel, w, h) {
alert('теперь размеры панели ' + w + 'x' + h);
});

Теперь любые изменения размера панели приведут к вызову функции обра-


ботки события. Абстрактный класс Ext.util.Observable предоставляет ин-
терфейс для работы с событиями. Для того чтобы объект был возможным
источником события, он должен наследовать классу Observable. Объект
Panel, а также другие объекты, например, Grid, Form или Tree, являются на-
следниками класса Observable, так что они становятся источниками событий
уже с момента своего создания.
События, вызываемые созданным вами объектом, вызываются именно клас-
сом-предком созданного объекта.
Одно и то же событие можно привязать к нескольким элементам сразу.
В следующем примере событие вызывается при щелчке по любому парагра-
фу документа:
Ext.onReady(function() {
Ext.select('p').on('click', function() {
alert("You clicked a paragraph");
});
});

При этом используется анонимная функция-обработчик события. Зададим


теперь обработчик с помощью именованной функции, которую впоследствии
можно будет снова вызвать, указав то же самое имя:
Ext.onReady(function() {
var paragraphClicked = function() {
alert("You clicked a paragraph");
}
Ext.select('p').on('click', paragraphClicked);
});

В функцию-обработчик события можно передать сам объект события,


точнее, его кроссбраузерную реализацию в ExtJS — объект Event. Передается
Глава 15. Структура и идеология библиотеки 179

он в качестве параметра, а в обработчике можно прочитать характеристики


события, выбрав соответствующие свойства объекта Event.
Например, узел DOM, на котором произошло событие, можно опреде-
лить так:
Ext.onReady(function() {
var paragraphClicked = function(e) {
Ext.get(e.target).highlight();
}
Ext.select('p').on('click', paragraphClicked);
});

Свойство target возвращает DOM-узел, поэтому сначала мы должны полу-


чить соответствующий объект Element и уже на нем производить действия.

Пользовательские события
Весьма часто требуется добавить пользовательское событие, которое не
встроено в Framework ExtJS. Рассмотрим, например, два класса: Employee
(Сотрудники) и OrgChart (Структура фирмы). Хочется устроить так, чтобы
можно было простым перетаскиванием элементов менять принадлежность
сотрудника к тому или иному подразделению, меняя при этом и должность
этого сотрудника.
В следующем примере мы создадим и такие обработчики событий, которые
будут рассылать письма, оповещающие сотрудников о новых назначениях.
Вот как это делается (листинг 15.5).

Листинг 15.5. Пользовательские события


OrgChart = Ext.extend(Ext.Panel, {
initComponent:function() {
// Вызываем компонент родительского класса
OrgChart.superclass.initComponent.apply(this, arguments);

// Добавляем пользовательские события


this.addEvents('assigned', 'dismissed');
}
,assign:function(employee, position) {
// Делаем все, что необходимо, для назначения сотрудника
// на должность.
// Вызываем событие назначения.
180 Часть IV. Библиотека ExtJS
this.fireEvent('assigned', this, employee, position);
}
,dismiss:function(empoyee, position) {
// Делаем все для снятия сотрудника с должности.
// Вызываем событие увольнения.
this.fireEvent('dismissed', this, employee, position);
}
});

В функции initComponent мы сообщаем классу Observable, что собираемся


генерировать новые события, так что ему пора подготовить все необходимое
для этого.
П РИМЕЧАНИЕ
Если же мы используем объект Panel, то будет задействована цепочка наследова-
ния Observable → Component → BoxComponent → Container → Panel.
Теперь в функциях назначения на должность assign и снятия с должности
dismiss мы вызываем наши события после выполнения всех необходимых
действий по назначению или снятию, которые выполнялись с заданными
значениями параметров empoyee и position.
Когда мы вызываем функцию fireEvent, Observable смотрит, есть ли обра-
ботчики для данного события, и вызывает их с аргументами, которые мы пе-
редали при вызове функции fireEvent. Если же обработчиков нет, то ника-
кие действия не происходят.
Подытожим:
событие — это сообщение, посылаемое источником события обработчику
для того, чтобы оповестить о наступлении события;
источник события — это объект, вызывающий событие;
обработчик события — это функция, вызываемая при генерации события;
для того чтобы обрабатывать события, надо определить функцию-
обработчик;
для создания источника события надо использовать методы addEvents
и ireEvent класса Observable.

Xtypes
Xtype — это псевдоним класса, только и всего. Есть, например, у нас класс
Ext.ux.MyGrid. Это обычное имя класса, которе мы используем, создавая
объект.
Глава 15. Структура и идеология библиотеки 181

Но в дополнение к имени класса вы можете указать так:


Ext.reg('mygrid', Ext.ux.MyGrid);

Значение xtype (псевдонима) в этой записи — mygrid. Такой строчкой мы


задаем новый xtype и связываем его с классом Ext.ux.MyGrid. Отлично.
А зачем это надо?
Представьте себе, что у нас большое приложение. Работая с ним, поль-
зователь щелкает по кнопке, в результате чего создается и отображается
таблица.
Для того чтобы все это работало, надо создать объект и хранить его в памяти
на клиентской машине. А если таких объектов сотни?
Если же в вашем распоряжении есть механизм псевдонимов xtype, то доста-
точно просто включить в код ссылку такого вида:
{xtype:'mygrid", border:false, width:600, height:400, ...}

Это называется ленивым созданием объекта (от англ. lazy instantiation).


Когда пользователь щелкает по кнопке, Ext обнаруживает, что затребован-
ный объект до сих пор не создан, но рецепт его создания имеется, так что ме-
неджер компонентов выполнит такой код:
create : function(config, defaultType){
return new types[config.xtype || defaultType](config);
}

Иными словами:
return new Ext.ux.MyGrid(config);

То есть наша таблица создается и отображается, но только та, что запро-


шена.

Классы ExtJS
Опишем некоторые важные классы ExtJS, к ним надо будет обращаться либо
явно, либо по цепочке наследования.

Класс Component
Класс Component предоставляет унифицированную модель для создания
компонентов страницы, их отображения, управления ими и, наконец, унич-
тожения.
182 Часть IV. Библиотека ExtJS

Жизненный цикл компонента


Многие этапы такого цикла протекают незаметно для разработчика. Но все
же авторы библиотеки считают, что понимание жизненного цикла важно для
разработчиков, так что мы кратко его рассмотрим.
1. Инициализация компонента.
При создании объекта класс Component получает все конфигурационные
параметры создаваемого объекта. Компонент регистрируется менеджером
компонентов ComponentMgr. Затем вызывается метод initComponent, кото-
рый выполняет всю основную работу: вызывается класс, объект которого
создается, прослеживается вся цепочка наследования этого класса, вы-
страивается логика работы создаваемого компонента.
2. После этого подгружаются плагины, если они нужны. Затем если компо-
нент подразумевает наличие состояний, то они устанавливаются. Если не-
обходимо, то созданный компонент встраивается в объектную модель до-
кумента.
3. Рендеринг.
Термин "рендеринг" в разговоре об ExtJS мы будем использовать часто,
подразумевая при этом встраивание созданного компонента в структуру
страницы, отображение на этой странице. На этапе рендеринга создается
контейнер для формируемого компонента (при необходимости). Приме-
няются методы класса и стили, заданные пользователем.
4. Уничтожение компонента.
Удаляются все обработчики событий, связанные с удаляемым элементом,
сам элемент удаляется из DOM. Уничтожается вся цепочка наследования
методов для удаляемого компонента. Удаляется регистрация компонента
у менеджера ComponentMgr.
Xtypes класса Component
Каждый компонент имеет свой псевдоним (xtype), вот некоторые из них:
box — псевдоним для Ext.BoxComponent;
button — Ext.Button;
component — Ext.Component;
container — Ext.Container;
grid — Ext.grid.GridPanel;
panel — Ext.Panel;
slider — Ext.Slider;
Глава 15. Структура и идеология библиотеки 183

viewport — Ext.Viewport;
window — Ext.Window;
form — Ext.FormPanel;
label — Ext.form.Label;
radio — Ext.form.Radio;
textfield — Ext.form.TextField.
Любой компонент может быть создан неявно указанием его псевдонима. При
этом выделение памяти для этого компонента и вообще его инициализация
откладываются до момента непосредственного обращения пользователя
к этому объекту.
// Явное создание объекта:
var panel = new Ext.Panel({
...
items: [
new Ext.Button({
text: 'OK'
})
]
};
// Неявное указание на объект с помощью псевдонима:
var panel = new Ext.Panel({
...
items: [{
xtype: 'button',
text: 'OK'
}]
};

В первой части кода кнопка создается в момент инициализации панели. Во


втором случае кнопка не будет создана до отображения панели в окне браузера.

Класс BoxComponent
Этот важный класс расширяет функциональность класса Component и обеспе-
чивает кроссбраузерное внедрение каждого компонента, который должен
быть отображен и может использоваться в компоновке страницы. Класс
BoxComponent управляет установкой размеров и позиционированием компо-
нентов корректно для всех браузеров. Все классы контейнеров расширяют
класс BoxComponent.
184 Часть IV. Библиотека ExtJS

Класс Container
Класс Container обеспечивает логику компоновки (layout) и представления
(rendering) для определения размеров и вложения компонентов, предоставляя
также механизм добавления компонентов в контейнер. Данный класс никогда
не используется сам по себе, но служит базой для всех визуальных ком-
понентов.

Класс Panel
Большинство задач по компоновке страницы решается с помощью этого
класса. Панель может быть абсолютно невидимым контейнером, используе-
мым для компоновки. Панель также снабжает блоки, из которых строится
страница, панелями инструментов (toolbars), полосами прокрутки, кнопками,
верхними и нижними колонтитулами (headers, footers).
Следующие потомки класса Panel являются главными интерфейсными
элементами окна или виджетами (от англ. widgets) в Ext 2.0:
GridPanel;
TabPanel;
TreePanel;
FormPanel.

Окно (Window)
Окно — это специализированная панель, размеры которой можно изменять,
сворачивать, разворачивать, а само окно можно перетаскивать.
Окно просмотра (Viewport)
Класс Viewport встраивает компонент в тело документа и подстраивает свои
размеры под окно просмотра браузера. Помните, что Viewport может встраи-
ваться только к тегу body, и, следовательно, на странице можно использовать
только один экземпляр класса Viewport.

Компоновка (layout)
В Ext 2.0 создана целая система управления компоновкой элементов. Имеется
10 отдельных менеджеров компоновки, задающих практически все возмож-
ные способы компоновки содержимого страницы.
Схемы компоновки не вызываются явно new. Вместо этого они создаются
и используются классами контейнеров. Сами контейнеры ничего не знают
Глава 15. Структура и идеология библиотеки 185

о компоновке, они просто передают задачи построения макета страницы тому


классу layout, который указан при конфигурировании контейнера. Каждый
раз, когда вы создаете контейнер, вы описываете стиль компоновки и пара-
метры компоновки в свойстве layoutConfig. Например:
var panel = new Panel({
title: 'My Accordion',
layout: 'accordion', // Стиль компоновки для этой панели
layoutConfig: {
animate: true // Параметры конфигурации компоновки
}
// Прочие опции класса Panel
});

Каждый класс layout поддерживает свои опции конфигурации. Вы можете


познакомиться с вариантами компоновки в табл. 15.1.
Таблица 15.1. Варианты компоновки в ExtJS
Изображение Опция Описание
конфигурации
ContainerLayout Это базовый класс для всех менеджеров
компоновки. Такой макет используется для
всех контейнеров, где компоновка не зада-
на явно. Эта схема компоновки не имеет
визуального представления,
ContainerLayout просто включает компо-
ненты в нужном месте, выводя их и изме-
няя при этом их размеры до необходимых
для отображения в нужном месте
CardLayout Здесь контейнер содержит несколько эле-
ментов, но только один из них надо отобра-
жать в каждый конкретный момент
AbsoluteLayout Это простой макет, позволяющий точно
позиционировать элементы, задавая
координаты X и Y
ColumnLayout Этот макет позволяет создавать структуру
в виде нескольких столбцов, где ширина
столбца задается в процентах или пикселах,
а высота определяется размером содержи-
мого
AccordionLayout Этот макет содержит группу панелей,
которые расположены одна над другой
и могут быть развернуты или свернуты для
отображения содержимого
186 Часть IV. Библиотека ExtJS
Таблица 15.1 (окончание)
Изображение Опция Описание
конфигурации
FitLayout Этот макет позволяет отобразить один ком-
понент в соответствии с размерами контей-
нера
AnchorLayout Это макет для элементов, которые нужно
привязывать к разным сторонам контей-
нера
FormLayout Это макет для отображения форм. Объекты
FormPanel должны использовать
layout:'form'

BorderLayout Этот макет наиболее часто используется


в бизнес-приложениях, позволяя вклады-
вать сворачиваемые панели, разделяя ви-
димую область на части
с помощью рамок
TableLayout Это макет для стандартной таблицы HTML
с поддержкой параметров colspan
и rowspan
Глава 16

Поиск элементов:
класс DomQuery
Обычно работа с ExtJS начинается с отбора элементов в HTML-документе,
затем для этих элементов назначают обработчики событий, меняют стили
и другие свойства отобранных элементов. Рассмотрим подробно методы та-
кого отбора.
Обычным способом отбора в JavaScript является отбор по значению иденти-
фикатора элемента. Но часто неудобно или невозможно выбирать узлы по их
идентификатору. Допустим, вам более удобно отбирать узлы по значению
какого-то иного их атрибута или имени стилевого класса. Для решения по-
добных задач ExtJS включает мощный и удобный класс DomQuery.
Класс DomQuery работает HTML- и XML-документами и позволяет опериро-
вать фильтрами узлов, содержащими CSS-селекторы пути до элемента и ис-
пользующими синтаксис XPath.
Метод Ext.DomQuery.select(), который мы будем сокращенно обозначать
как Ext.query(), принимает два параметра: первый — строка селектора, вто-
рой — идентификатор элемента, в котором осуществляется поиск.
Выбор узлов DOM
Класс DomQuery можно использовать и сам по себе, но чаще его применяют
к элементам посредством метода Element.select, который вызывает
DomQuery для поиска элементов. Например, применим метод highlight ко
всем параграфам нашего документа:
Ext.select('p').highlight();
Этот пример демонстрирует удобный аспект работы с методом
Element.select — он возвращает объект класса CompositeElement, обеспе-
чивающий доступ ко всем вложенным элементам без необходимости переби-
рать все элементы в цикле.
Посмотрим, как производится отбор элементов с помощью функции query.
188 Часть IV. Библиотека ExtJS

Селекторы элементов
Ext.query("span") — функция вернет массив элементов span;
Ext.query("span" "foo") — элементы span с идентификатором foo;
Ext.query("#foo") — элементы с идентификатором foo;
Ext.query(".foo") — отобрать элементы класса foo;
Ext.query("*") — все элементы документа;
Ext.query("div span") — отобрать все элементы span внутри элемен-
тов div.

Селекторы атрибутов
Эти селекторы позволяют отобрать элементы по значениям их атрибутов:
Ext.query("*[class]") — все элементы с атрибутом class;
Ext.query("*[class = bar]") — элементы класса bar;
Ext.query("*[class! = bar]") — все элементы кроме тех, что из класса
bar;
Ext.query("*[class^ = b]") — элементы класса, имя которого начинает-
ся с b, т. е. при отборе можно использовать регулярные выражения.

Отбор элементов CSS Value selectors


Эти селекторы отбирают элементы по значению атрибута style. Формат
такой:
element{attribute operator value}

Примеры:
Ext.query("*{color = red}") — текстовые элементы красного цвета;
Ext.query("*{color = red} *{color = pink}") — все розовые элемен-
ты, являющиеся наследниками красных;
Ext.query("*{color! = red}") — отобрать все элементы кроме красных;
Ext.query("*{color^ = yel}") — элементы, название цвета которых на-
чинается с "yel", т. е. снова можно применять регулярные выражения;
Ext.query("*{color* = ow}") — все элементы, в значении атрибута
color которых есть подстрока "ow".
Глава 16. Поиск элементов: класс DomQuery 189

Продемонстрируем, как можно выбирать элементы, используя термины объ-


ектной модели документа:
Ext.query("span:first-child") — выбираем первый из вложенных элемен-
тов span;
Ext.query("a:last-child") — ищем элемент a, являющийся последним
из элементов-ссылок своего уровня;
Ext.query("span:nth-child(2)") — элемент span, являющийся вторым
ребенком элемента span;
Ext.query("tr:nth-child(odd)") — нечетные элементы tr;
Ext.query("li:nth-child(even)") — четные элементы li;
Ext.query("a:only-child") — отбираем те элементы a, которые являют-
ся единственными детьми своих родителей;
Ext.query("input:checked") — отбираем элементы input, выбранные
пользователем;
Ext.query("tr:first") — первый из тегов tr;
Ext.query("td:nth(2)") — второй тег td;
Ext.query("div:has(a)") — отбираем те div, которые имеют вложенные
теги a;
Ext.query("td:next(td)") — каждый элемент td, за которым снова идет
td;
Ext.query("label:prev(input)") — каждый элемент label, перед кото-
рым стоит input.
Посмотрите пример в листинге 16.1. Мы будем подсвечивать желтым цветом
элементы, отбираемые по указанным правилам:
Ext.query('li:first') — отобрать первый элемент списка (тег <li>);
Ext.query('li:even') — отобрать все нечетные элементы списка (нуме-
рация в JavaScript начинается с 0, а не с 1);
Ext.query('li').splice(0,3) — показать три первых элемента списка.
Счет начинаем с 0, а не с 1;
Ext.query('li:not(.goofy)') — отобрать элементы 1, 2 и 4, поскольку
они не относятся к классу goofy;
Ext.query('p a[href* = #]') — все ссылки внутри параграфа, у которых
атрибут href содержит символ #;
Ext.query('code, li.goofy') — все элементы code и элементы списка
класса goofy;
190 Часть IV. Библиотека ExtJS

Ext.query('ul .goofy > strong') — все элементы strong, вложенные


в элементы класса goofy, но только те, что вложены в упорядоченный
список;
Ext.query('li + li > a[@href$ = pdf]') — все ссылки, заканчиваю-
щиеся на буквы "pdf", при этом ссылки должны быть вложены в такие
элементы li, перед которыми идут другие элементы списка;
Ext.query("span{display = none}") — все скрытые элементы span;
Ext.query("li:nth(4)") — 4-й элемент списка.

Листинг 16.1. Отбор элементов


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv = "Content-Type"
content = "text/html; charset = utf-8">
<title>DomQuery</title>
<style>
.yellow{background-color:yellow;}
</style>
<script type = "text/javascript"
src = "ext-2.2/adapter/ext/ext-base.js">
</script>
<script type = "text/javascript" src = "ext-2.2/ext-all.js"></script>
<script>
var domquery = function() {
return {
init: function() {
Ext.select("div.dom-traversal-toggles").on("click",
function(e, el){
var id = el.id;
id = id.replace("dt-link", "");
if (id ! = ""){
var toggler = function(e){
var highlight = function(els){
for (var x = 0 ; x < els.length; x ++){
var el = Ext.get(els[x]);
Глава 16. Поиск элементов: класс DomQuery 191

if (el) {
if el.hasClass("yellow")) {
el.removeClass("yellow");
}else{
el.addClass("yellow");
}
}
}
return els;
};
var highlightHidden = function(els){
var elmts = highlight(els);
for (var x = 0 ; x < elmts.length; x ++){
var el = Ext.get(elmts[x]);
if (el) {
if (el.hasClass("yellow")){
el.fadeIn();
}else{
el.fadeOut();
el.removeClass("yellow");
}
}
}
}
switch(e){
case "1": highlight(Ext.query('li:first', "extdt"));
break;
case "2": highlight(Ext.query('li:even', "extdt"));
break;
case "3": highlight(Ext.query('li', "extdt").splice(0,3));
break;
case "4": highlight(Ext.query('li:not(.goofy)', "extdt"));
break;
case "5": highlight(Ext.query('p a[href* = #]', "extdt"));
break;
case "6": highlight(Ext.query('code, li.goofy', "extdt"));
break;
case "7": highlight(Ext.query('ul .goofy > strong', "extdt"));
break;
192 Часть IV. Библиотека ExtJS
case "8": highlight(Ext.query('li+li>a[href$ = pdf]', "extdt"));
break;
case "9": if(Ext.query("span{display = none}","extdt").length >0)
highlightHidden(Ext.query("span{display = none}", "extdt"));
}else {
highlightHidden( Ext.query("span{display}", "extdt"));
}
break;
case "10": highlight( Ext.query("li:nth(4)", "extdt") );
break;
}
}(id);
}
if (e = = "x"){
}
}
);
}
}
}();
Ext.onReady(
function(){
domquery.init();
}
);
</script>
</head>
<body id = "body">
<!— Фрагмент, в котором ищем элементы - - >
<div style = "border: 1px solid rgb(0, 0, 0); padding: 1em; width:
400px;"
id = "extdt">
<p class = "goofy"> Это <em>параграф</em> с <strong>текстом</strong>
класса class = "goofy." В нем есть
<a title = "http://www.englishrules.com" class = "external text"
href = "http://www.englishrules.com">внешняя ссылка</a>
, какой-то <code>код</code> и
<a title = "" href = "#dt-link3_same-page_link">ссылка на #dt-link3 </a>.
</p>
<ul>
<li>Это первый элемент списка со ссылкой на
Глава 16. Поиск элементов: класс DomQuery 193

<a title = "Silly.pdf" class = "new"


href = "/action/edit/Silly.pdf"> silly.pdf</a>.
</li>
<li class = "goofy"> <em>Это второй <strong> элемент</strong> списка</em>
класса class = "<strong>goofy</strong>".
</li>
<li >Это третий элемент списка. <span style = "display:none;">
Сюрприз!</span>
</li>
<li> <strong>Это четвертый элемент списка</strong> с совсем уже глупой
ссылкой на <a title = "Silly.pdf silly.pdf" class = "new" href =
"/action/edit/Silly.pdf_silly.pdf">silly.pdf_silly.pdf</a>.
</li>
</ul>
</div>
<!— Конец фрагмента, в котором мы ищем элементы - - >
<!— Кнопки для подсветки выбранных элементов - - >
<div class = "dom-traversal-toggles">
<ul>
<li><input type = "submit" value = "Переключить" id = "dt-link1" />
<code>Ext.query('li:first')</code> </li>
Выглядеть в браузере это должно
<li><input type = "submit" value = "Показать" id = "dt-link2" />
<code>Ext.query('li:even')</code> </li>
<li><input type = "submit" value = "Показать" id = "dt-link3"/>
<code>Ext.query('li').splice(0,3)</code> </li>
<li><input type = "submit" value = "Показать" id = "dt-link4"/>
<code>Ext.query('li:not(.goofy)')</code> </li>
<li><input type = "submit" value = "Показать" id = "dt-link5"/>
<code>Ext.query('p a[href* = #]')</code> </li>
<li><input type = "submit" value = "Показать" id = "dt-link6"/>
<code>Ext.query('code, li.goofy')</code> </li>
<li><input type = "submit" value = "Показать" id = "dt-link7"/>
<code>Ext.query('ul .goofy > strong')</code> </li>
<li><input type = "submit" value = "Показать" id = "dt-link8"/>
<code>Ext.query('li + li > a[@href$ = pdf]')</code> </li>
<li><input type = "submit" value = "Показать" id = "dt-link9"/>
<code>Ext.query("span{display = none}")</code> .</li>
<li><input type = "submit" value = "Показать" id = "dt-link10"/>
<code>Ext.query("li:nth(4)")</code> </li>
</ul>
</div>
</body>
</html>
194 Часть IV. Библиотека ExtJS

Рис. 16.1. Применение метода query

Выглядеть у вас в браузере это должно так, как показано на рис. 16.1.
Код получился довольно длинным, объясним его здесь, но при первом чте-
нии это объяснение можно пропустить, вернувшись впоследствии, когда раз-
беретесь с некоторыми методами, описываемыми в следующих главах.
Начнем разбор с конца сценария. В самом конце вызывается функция
Ext.onReady, срабатывающая после загрузки DOM. Она вызывает функцию
domquery.init().
Эта функция и выполняет всю основную работу. Она привязывает событие
click к элементам раздела div класса dom-traversal-toggles. Каждый
Глава 16. Поиск элементов: класс DomQuery 195

щелчок по кнопке приводит к тому, что выбираются некоторые элементы,


выбранные элементы подсвечиваются желтым цветом, а повторный щелчок
по той же кнопке удаляет подсветку.
При щелчке запускается функция, определяющая элемент, по которому
щелкнули, затем метод replace удаляет из идентификатора элемента-
источника события все, кроме последней цифры. Эта цифра записывается
в переменную и используется для определения того, какие именно элементы
надо подсвечивать функцией highlight. Сам процесс отбора можно увидеть
в конструкции switch, в ней также вызывается и сама функция highlight,
определенная в программе несколько раньше.
Функции highlight в качестве параметров передается массив отобранных
элементов, относительно которых методом hasClass проверяется, к какому
стилевому классу они относятся. Затем класс либо удаляется функцией
removeClass, либо наоборот, добавляется функцией addClass, в зависимости
от исхода проверки.
Кроме того, для 9-й кнопки нам потребуется функция highlightHidden, ко-
торая умеет не только подкрашивать элемент, но и применять к нему методы
fadeIn или fadeOut для отображения или скрытия элемента, причем исчезает
элемент при этом постепенно.
Глава 17

Панели и компоновка элементов

Простая панель
Создадим на странице панель, которую можно будет сворачивать и развора-
чивать щелчком по встроенной в нее особой кнопке. Панель должна появить-
ся в самом верху документа, поэтому составляющие ее элементы должны
быть среди первых наследников элемента body. Сделаем так: добавим в нача-
ло документа новый пустой div, а к нему прикрепим панель. Вид простой
панели в браузере представлен на рис. 17.1.
Код создания простой панели дан в листинге 17.1.

Рис. 17.1. Простая панель


Глава 17. Панели и компоновка элементов 197

Листинг 17.1. Сворачиваемая панель


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>ExtJS</title>
<link rel="stylesheet" type="text/css"
href="ext-2.2/resources/css/ext-all.css" />
<script type="text/javascript" src="ext-2.2/adapter/ext/ext-base.js">
</script>
<script type="text/javascript" src="ext-2.2/ext-all.js"> </script>
<script type="text/javascript" >
Ext.onReady(function(){
Ext.get(document.body).update('<div id="test"></div>');
new Ext.Panel({
renderTo: 'test',
width: '200px',
title: 'Панель',
html: 'Содержимое панели',
collapsible: true
});
});
</script>
</head>
<body><div id="div1"></div></body>
</html>

Используем здесь статический метод get, возвращающий элемент по ука-


занному идентификатору или имени элемента. Будем оперировать мето-
дами класса Element: метод update изменяет свойство innerHTML только
что выбранного нами элемента, добавляя в него HTML-фрагмент
'<div id="test"></div>'.
Затем создаем объект Panel (панель). Панель — это основной контейнер для
контента страницы. Панель может содержать верхние и нижние линейки ин-
струментов (toolbars), собственные заголовки, может отображать содержимое
в пределах различных секций. Пользователь может сворачивать и разворачи-
вать эти секции. Панели просто разместить в любом контейнере.
198 Часть IV. Библиотека ExtJS

В нашем примере использованы некоторые конфигурационные параметры


объекта Panel:
html — HTML-фрагмент, который будет служить контентом панели;
renderTo — идентификатор узла, к которому будет присоединена панель;
title — текст заголовка панели;
width — ширина панели в пикселах;
collapsible — значение true для этого параметра позволяет сворачивать
и разворачивать панель с помощью кнопки управления, находящейся в за-
головке панели.

Вложенные панели
Вложим теперь одну панель в другую (листинг 17.2).
Листинг 17.2. Вложенные панели
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>extjs</title>
<link rel="stylesheet" type="text/css"
href="ext-2.2/resources/css/ext-all.css" />
<script type="text/javascript" src="ext-2.2/adapter/ext/ext-base.js">
</script>
<script type="text/javascript" src="ext-2.2/ext-all.js"> </script>
<script type="text/javascript">
Ext.onReady(function(){
panel1 = new Ext.Panel({
title: 'Внешняя панель',
collapsible: true,
renderTo: Ext.getBody(),
html: 'Содержимое внешней панели',
items: [
new Ext.Panel({
title: 'Внутренняя панель',
collapsible: true,
Глава 17. Панели и компоновка элементов 199

html: 'Содержимое внутренней панели'


})
]
});
});
</script>
</head>
<body></body>
</html>

Конфигурационный параметр items содержит информацию об объектах,


вкладываемых в панель. Выглядит это в браузере так, как представлено
на рис. 17.2.

Рис. 17.2. Вложенные панели

Компоновка панелей: создание аккордеона


Web-страница в ExtJS создается в результате компоновки объектов и ото-
бражения созданного макета. Поэтому все примеры в этой главе начинаются
с создания панелей, т. е. объектов, но сейчас пришла пора научиться их ком-
поновать с использованием макетов или компоновок (layout) и отображать
с помощью специального объекта Viewport. Виды компоновок приводились
в главе 15, там же говорилось, что контейнеры обращаются к ним, когда не-
обходимо скомпоновать страницу. Рассмотрим весь этот процесс на примере
200 Часть IV. Библиотека ExtJS

одного из видов компоновки — аккордеона (accordion), как представлено


в листинге 17.3.

Листинг 17.3. Аккордеон


<html>
<head>
<title>Accordion Layout</title>
<link rel="stylesheet" type="text/css"
href="ext-2.2/resources/css/ext-all.css"/>
<script type="text/javascript"
src="ext-2.2/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="ext-2.2/ext-all.js"></script>
<style type="text/css">
html, body {
font: normal 12px verdana;
margin: 0;
padding: 0;
border: 0 none;
overflow: hidden;
height: 100%;
}
.empty .x-panel-body {
padding-top: 20px;
text-align: center;
font-style: italic;
color: gray;
font-size: 11px;
}
</style>
<script type="text/javascript">
Ext.onReady(function() {
var item1 = new Ext.Panel({
title: 'Аккордеон. Элемент 1',
html: 'Какое-нибудь содержание',
cls: 'empty' // Указание применяемого стилевого класса
});
var item2 = new Ext.Panel({
title: 'Аккордеон. Элемент 2',
Глава 17. Панели и компоновка элементов 201

html: 'Какое-нибудь содержание',


cls: 'empty'
});
var item3 = new Ext.Panel({
title: 'Аккордеон. Элемент 3',
html: 'Какое-нибудь содержание',
cls: 'empty'
});
var item4 = new Ext.Panel({
title: 'Аккордеон. Элемент 4',
html: 'Какое-нибудь содержание',
cls: 'empty'
});
var item5 = new Ext.Panel({
title: 'Аккордеон. Элемент 5',
html: 'Какое-нибудь содержание',
cls: 'empty'
});
var accordion = new Ext.Panel({
region: 'west',
margins: '5 0 5 5',
split: true,
width: 210,
layout: 'accordion', // Задание компоновки
items: [item1, item2, item3, item4, item5]
});
var viewport = new Ext.Viewport({
layout: 'border',
items:[
accordion, {
region: 'center',
margins: '5 5 5 0',
cls: 'empty',
bodyStyle: 'background:#f1f1f1',
html: '<br/><br/>Содержимое центральной панели'
}]
});
});
202 Часть IV. Библиотека ExtJS
</script>
</head>
<body></body>
</html>

Создав пять простых панелей, мы объединяем их вместе в объекте


accordion, указываем с помощью свойства region, что их всех надо пози-
ционировать относительно левого края страницы, и задаем параметр layout
для того, чтобы применить соответствующий вариант компоновки. Попро-
буйте применить другие значения, например, anchor или column. Посмотри-
те, как изменится компоновка панелей. С меньшим успехом, но также можно
применить значения absolute, fit, absolute и table.
На рис. 17.3 представлен вид страницы, скомпонованной в виде аккордеона.

Рис. 17.3. Аккордеон

Последнее действие в этом примере — это создание объекта Viewport. Класс


Ext.Viewport описывает специализированные контейнеры, позволяющие
отображать содержимое в браузере. Объект класса Viewport внедряет себя
в тело документа, автоматически меняя свои размеры под размеры окна брау-
зера, иначе говоря, он создает окно для демонстрации содержимого в рамках
окна браузера. На странице можно создать только один экземпляр класса
Viewport.
Сам Viewport не обеспечивает прокрутки содержимого, так что в панелях-
наследниках требуется указать необходимые параметры для реализации про-
крутки (scrolling).
Глава 17. Панели и компоновка элементов 203

При создании объекта Viewport надо задать значение параметра layout


и список отображаемых компонентов. Мы задали значение border для пара-
метра layout, что означает использование компоновки BorderLayout. Ото-
бражать будем аккордеон и центральную панель, конфигурацию которой
тут же и указываем. Обратите внимание на последнее обстоятельство: не
обязательно заранее создавать объект для отображения, можно сделать это
и на лету.

Панель с несколькими вкладками


Усовершенствуем нашу страницу, создав на ней несколько панелей-вкладок,
на которых можно размещать содержимое различных файлов. Для начала
напишем HTML-каркас (листинг 17.4).
Листинг 17.4. Страница с несколькими панелями-вкладками
<html>
<head>
<title>TabPanel Tutorial</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css"
href="ext-2.2/resources/css/ext-all.css" />
<script type="text/javascript"
src="ext-2.2/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="ext-2.2/ext-all.js"></script>
<script type="text/javascript" src="tab_actions.js"></script>
<style>
#actions li {
margin:.3em;
}
#actions li a {
color:#666688;
text-decoration:none;
}
</style>
</head>
<body>
<ul id="actions" class="x-hidden">
<li><a id="use" href="#">Перейти на существующую вкладку</a></li>
204 Часть IV. Библиотека ExtJS
<li><a id="create" href="#">Создать новую вкладку</a></li>
</ul>
<div id="tabs"></div>
</body>
</html>

В приведенном коде есть два важных элемента. Во-первых, это список


с идентификатором actions, который послужит перечнем выполняемых дей-
ствий. Действия инициируются щелчком по пункту синего меню слева стра-
ницы (рис. 17.4). Во-вторых, раздел с идентификатором tabs станет контей-
нером для первой вкладки. В этой вкладке у нас будет отображаться файл
1.html.
Теперь нам потребуется создать панели, отобразить в них всю требуемую
информацию и написать функции, создающие новые вкладки и подгружаю-
щие в них файлы. Функции должны вызываться щелчком мыши по пунктам
меню. Вот это меню и должно быть содержимым одной из вкладок.
Щелкая по пункту Использовать существующую вкладку, вы обновите
содержимое первой вкладки, т. е. заново в нее загрузится тот же файл, что
и в самом начале загружался, но в заголовке ведется счет обновлений.
При каждом щелчке по пункту Создать новую вкладку появляется вкладка
с отображаемым файлом.

Рис. 17.4. Вид панели с вкладками


Глава 17. Панели и компоновка элементов 205

Сценарий tab_actions.js в листинге 17.5 выполняет всю работу по созданию


вкладок и размещению документов в них.
Листинг 17.5. Сценарий tab_actions.js
Ext.onReady(function(){
// Меню возможных действий пользователя
var tabActions = new Ext.Panel({
frame: true, // Отображать со скругленными рамочками
title: 'Действия',
// Разрешаем сворачивание панели по щелчку кнопкой в заголовке панели
collapsible: true,
// Указываем идентификатор элемента, который превращаем в панель
contentEl: 'actions',
// Разрешаем сворачивание щелчком по любому месту заголовка панели
titleCollapse: true
});
// Родительская панель для меню
var actionPanel = new Ext.Panel({
id: 'action-panel',
region: 'west',
split: true,
collapsible: true,
collapseMode: 'mini',
width: 200,
minWidth: 150,
border: false,
baseCls: 'x-plain',
items: [tabActions]
});

// Главная панель для вкладок


var tabPanel = new Ext.TabPanel({
region: 'center',
/* TabPanel использует Ext.layout.CardLayout для управления
вкладками. Здесь мы указываем, что вкладка первый раз
отобразится только после того, как к ней обратятся. */
deferredRender: false,
autoScroll: true,
206 Часть IV. Библиотека ExtJS
margins: '0 4 4 0',
activeTab: 0,
items:[{
id: 'tab1',
contentEl: 'tabs',
title: 'Главная',
closable: false,
autoScroll: true
}]
});

// Конфигурируем Viewport
viewport = new Ext.Viewport({
layout: 'border',
items: [actionPanel,tabPanel]});

// Добавляем вкладку в центральную панель


function addTab(tabTitle, targetUrl){
tabPanel.add({ // Добавляем компонент в контейнер
title: tabTitle,
iconCls: 'tabs',
// Получаем указанный URL с помощью AJAX-запроса
autoLoad: {url: targetUrl, callback: this.initSearch, scope: this},
closable: true
}).show(); // Показываем компонент
}
// Обновляем содержимое вкладки, при его отсутствии - создаем
function updateTab(tabId,title, url) {
var tab = tabPanel.getItem(tabId); // Отбираем по идентификатору
if (tab){
tab.getUpdater().update(url); // Запрашиваем указанный url
tab.setTitle(title);
}else{
tab = addTab(title,url);
}
tabPanel.setActiveTab(tab);
}
// Связываем идентификаторы и функции
var count = 0;
Глава 17. Панели и компоновка элементов 207

var actions = {
'create': function(){
addTab("Новая вкладка",'1.html');
},
'use': function(){
updateTab('tab1','Заменялось' + count + 'раз','sample' +
(count%2) + '.html');
count++;
}
};

function doAction(e, t){


/* Останавливаем распространение события и предотвращаем
его стандартную обработку */
e.stopEvent();
actions[t.id]();
}
// После задания Viewport привязываем событие mousedown
actionPanel.body.on('mousedown', doAction, null, {delegate:'a'});
});

Весь наш код обернут в метод Ext.onReady, так что он не будет выполняться
до того, как в браузер будут загружены все элементы HTML-документа.
Первое, что мы делаем, — создаем объект tabActions, представляющий со-
бой панель для меню. Оно будет отображаться слева в окне на синем фоне.
Для этого потребуется конвертировать список <ul> с идентификатором
actions в панель tabActions. Значением конфигурационного параметра
contentEl как раз и должен быть идентификатор подходящего для этой цели
списка.
Параметр frame со значением true позволяет указать, что панель должна
обрамляться скругленными рамочками.
Затем создадим родительскую панель actionPanel для меню; значение пара-
метра items должно указывать на имя панели tabActions, подключаемой
внутрь родительской панели. Панель actionPanel будет позиционирована на
странице с помощью специального менеджера, так что нам потребуется ука-
зать значение параметра region при конфигурировании. Параметр baseCls
указывает на то, какой стилевой класс будет использован для панели (по
умолчанию это 'x-panel').
Третий компонент, который надо создать, — главная панель TabPanel с вклад-
ками. Она должна располагаться в середине страницы, что соответствует
208 Часть IV. Библиотека ExtJS

значению 'center' параметра region. Кроме того, мы передаем список объ-


ектов, которые следует отображать во вкладках.
Наконец, отображаем все эти панели с помощью Viewport. После предыду-
щего примера трудности для читателя этого представлять не должно.
Теперь, когда все элементы созданы, мы можем добавить методы для обнов-
ления панели TabPanel. Создадим три файла, содержимое которых будет
отображаться во вкладках: 1.html, sample0.html и sample1.html.
Неважно, какое именно будет у них содержимое, но оно должно позволить
отличить загрузку одного файла от другого.
Функция addTab добавляет новую вкладку в центральную панель, вызывая
метод add. На этой вкладке должно отображаться содержимое файла 1.html.
При вызове данной функции придется передавать имя отображаемого файла.
Функция updateTab принимает параметр tabId, который позволяет прове-
рить, существует ли уже нужная вкладка. Если существует, то вызывается
метод Updater, который обновляет содержимое панели. В противном случае
вызывается метод addTab для создания вкладки.
В конце скрипта мы определяем обработчик события mousedown, вызываю-
щий функцию doAction при наступлении события. Событие должно возник-
нуть в пределах панели actionPanel, в которую помещено меню.
Функция doAction предотвращает выполнение действий по умолчанию для
этого события методом stopEvent и вызывает функцию actions.
Но в функции on есть еще опция delegate:'a', заставляющая передавать
в функцию actions идентификаторы элементов списка, из которых мы сде-
лали меню и по которым происходит щелчок. В зависимости от идентифика-
тора, который может быть либо create, либо use, вызывается либо addTab,
либо updateTab.
Глава 18

Формы
Создание элемента формы
Для создания любого элемента формы в ExtJS существует соответствующий
объект. Создадим, например, кнопку (листинг 18.1).
Листинг 18.1. Кнопка
<html>
<head>
<title>Введение в Ext</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css"
href="ext-2.2/resources/css/ext-all.css"/>
<script type="text/javascript"
src="ext-2.2/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="ext-2.2/ext-all.js"></script>
<link rel="stylesheet" type="text/css"
href="ext-2.2/resources/css/ext-all.css">
<script type="text/javascript">
button = new Ext.Button({
text: 'Кнопка',
renderTo: Ext.getBody(),
handler: function() {
Ext.MessageBox.alert("На меня нажали!", "Сообщение"); }
})
</script>
</head>
<body> </body>
</html>
210 Часть IV. Библиотека ExtJS

Надпись на кнопке формируется свойством text, местоположение на страни-


це (в данном случае кнопка вкладывается непосредственно в элемент body)
определяется значением свойства renderTo, обработчик события задается
в свойстве handler.
Как это будет выглядеть на экране, показано на рис. 18.1.

Рис. 18.1. Кнопка

Компоновка формы
Теперь на очереди создание формы и компоновка ее вызовом соответствую-
щего способа макетирования. Применим здесь компоновку Absolutelayout,
создавая форму (листинг 18.2).
Листинг 18.2. Форма с Absolutelayout
<html>
<head>
<title>Absolute Forms</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css"
href="ext-2.2/resources/css/ext-all.css"/>
<script type="text/javascript" src="ext-2.2/adapter/ext/ext-base.js">
</script>
<script type="text/javascript" src="ext-2.2/ext-all.js"></script>
<script type="text/javascript" src="absform.js"></script>
Глава 18. Формы 211

</head>
<body>
<h1>Absolute Layout with Forms</h1>
</body>
</html>

Вид формы в браузере представлен на рис. 18.2.

Рис. 18.2. Форма с компоновкой Absolutelayout

Разберем, как работает сценарий из листинга 18.3. FormPanel является стан-


дартным контейнером формы и выполняет компоновку с помощью макетов
(layouts).
По умолчанию данные формы в ExtJS передаются AJAX-запросом, используя
объект Ext.form.Action. Параметр url в листинге 18.3 задает адрес, по кото-
рому эти данные будут отправлены.
В контейнер формы надо включить один или несколько компонентов, задавая
их с помощью параметра items. Если компонентов несколько, то формат их
определения таков: items: [{...}, {...}]. Каждый компонент описывает-
ся внутри фигурных скобок и может относиться к одному и типов xtypes,
описанных в документации по классу Ext.Component. Перечень xtypes,
используемых в формах, включает в себя: form, checkbox, combo, datefield,
field, fieldset, hidden, htmleditor, label, numberfield, radio, textarea,
и
textfield, timefield trigger.
212 Часть IV. Библиотека ExtJS

Позиционируем элементы с помощью соответствующего макета и встраиваем


форму в новое окно. Затем отображаем это окно с помощью функции show.
Листинг 18.3. Сценарий absform.js
Ext.onReady(function() {
var form = new Ext.form.FormPanel({
baseCls: 'x-plain', // Применяемый класс CSS
layout: 'absolute', // Тип компоновки
url: 'save-form.php', // Куда отправлять данные формы
defaultType: 'textfield',
items: [{ // Задание полей формы
x: 0,
y: 5,
xtype: 'label', // xtype создаваемого элемента
text: 'Кому:'
},{
x: 60,
y: 0,
name: 'to',
anchor: '100%' // Ширина в процентах
},{
x: 0,
y: 35,
xtype: 'label',
text: 'Тема:'
},{
x: 60,
y: 30,
name: 'subject',
anchor: '100%' // Ширина в процентах
},{
x:0,
y: 60,
xtype: 'textarea',
name: 'message',
anchor: '100% 100%' // Ширина и высота
}]
});
Глава 18. Формы 213

var window = new Ext.Window({


title: 'Размеры этого окна можно менять',
width: 500,
height: 300,
minWidth: 300,
minHeight: 200,
layout: 'fit',
plain:true,
bodyStyle:'padding:5px;',
buttonAlign:'center',
items: form,
buttons: [{
text: 'Отправить'
},{
text: 'Отменить'
}]
});

window.show();
});

Передача данных формы


на сервер методом submit
Сделаем форму, которая будет отправлять на сервер данные, указанные поль-
зователем. Серверный сценарий проверяет имя и пароль пользователя, в слу-
чае успеха надо перенаправить пользователя на следующую страницу.
Требуемый HTML-документ не заслуживает распечатки и похож на тот, что
показан в листинге 18.2, только ссылка в нем дана на скрипт login.js. Чита-
тель найдет этот файл на компакт-диске под именем login.html.
Проверка нашей формы будет осуществляться серверным сценарием
login.php (листинг 18.4).

Листинг 18.4. Сценарий проверки формы login.php


<?php
$loginUsername =
isset($_POST["loginUsername"]) ? $_POST["loginUsername"] : "";
if ($loginUsername == "user"){
214 Часть IV. Библиотека ExtJS
echo "{success: true}";
} else {
echo "{success: false, errors: { reason: 'Login failed. Try again.'
}}";
}
?>

То есть мы проверяем, действительно ли пользователь указал имя user.


В противном случае не регистрируем его.
Еще нам потребуется сценарий test.php, на который наша форма перебросит
пользователя после успешной регистрации. Внесем в него немудрящее со-
держание:
<?php echo "Привет прошедшим регистрацию!"; ?>

Теперь дошла очередь до самого сценария login.js (листинг 18.5). Логика


действий в нем такова: сначала создаем панель с формой Ext.FormPanel.
Конфигурационные параметры панели таковы:
labelWidth — ширина пометки на форме;
url — куда отправляем данные формы;
frame — скругляем углы рамочек;
monitorValid — значение true заставляет форму отслеживать клиентские
события и запускать необходимые обработчики;
items — поля формы, в них атрибут name задает имя переменной, которая
будет отправлена на сервер;
buttons — кнопки.
Создав форму, мы отображаем ее в новом окне, компонуемом с помощью
fitlayout. Обратите внимание на параметр items, в котором и указано, что
следует отображать в окне.
Листинг 18.5. Сценарий генерации формы login.js
Ext.onReady(function(){
var login = new Ext.FormPanel({
labelWidth: 80,
url: 'login.php',
frame: true,
title: 'Зарегистрируйтесь, пожалуйста',
defaultType: 'textfield',
monitorValid: true,
Глава 18. Формы 215

items:[{
fieldLabel: 'Логин',
name: 'loginUsername',
allowBlank: false
},{
fieldLabel: 'Пароль',
name: 'loginPassword',
inputType: 'password',
allowBlank: false
}],
buttons:[{
text: 'Отправить',
formBind: true,
handler: function(){
login.getForm().submit({
method: 'POST',
waitTitle: 'Соединение с сервером',
waitMsg: 'Отправка данных...',
success:function(){
Ext.Msg.alert('Status', 'Успешно!', function(btn, text){
if (btn == 'ok'){
var redirect = 'test.php';
window.location = redirect;
}
});
},
failure:function(form, action){
if(action.failureType == 'server'){
obj = Ext.util.JSON.decode(action.response.responseText);
Ext.Msg.alert('Регистрация не прошла!', obj.errors.reason);
}else{
Ext.Msg.alert('Warning!', 'Сервер недоступен : ' +
action.response.responseText);
}
login.getForm().reset();
}
});
}
}]
});
var win = new Ext.Window({
216 Часть IV. Библиотека ExtJS
layout: 'fit',
width: 300,
height: 150,
// Не показываем кнопку, позволяющую закрыть это окно
closable: false,
resizable: false, // Запрещаем пользователю менять размер окна
plain: true, // Создаем окно с прозрачным фоном
border: false,
items: [login]
});
win.show();
});

Разберемся теперь, как устроены поля формы. Обработчик события кнопки


описан в свойстве handler. Методом getForm мы получаем в обработчике
доступ к форме, затем для этой формы вызываем метод submit, с его по-
мощью отправляем методом POST данные на сервер, в сценарий login.php.
В случае успеха вызываем функцию, определенную в параметре success, ко-
торая выводит диагностическое сообщение и перенаправляет пользователя на
страницу test.php.
Параметр failure задает действия в случае неудачи. Здесь нам придется опе-
рировать объектом action. Форма создает объект этого класса при отправке
данных и передает его в callback-функции, вызываемые при успехе или
неудаче передачи данных на сервер. В случае неудачи свойство failureType
содержит одно из следующих значений: CLIENT_INVALID, SERVER_INVALID,
CONNECT_FAILURE или LOAD_FAILURE. Поэтому дальнейшие действия преду-
сматривают анализ ответа сервера и вывод сообщений на основании ответа.
Посмотрите на результат наших трудов в браузере (рис. 18.3).

Рис. 18.3. Форма с проверкой данных на сервере


Глава 18. Формы 217

Проверка форм с помощью класса VTypes.


Календарь-подсказка
Особенно эффектно выглядит работа с формами в ExtJS, когда дело доходит
до проверки дат и времени. Уже сам вид полей для ввода дат и времени
DataField и TimeField с выпадающими календарными подсказками произ-
водит сильное впечатление, а уж дополнительные возможности проверки ме-
тодами класса VTypes и вовсе переводят обработку форм на принципиально
иной уровень.
Стандартные текстовые подсказки, появляющиеся около полей формы, хра-
нятся в отдельном файле. Кроме того, в ExtJS встроены календари, с по-
мощью которых легко выбрать дату. Для русификации текстовых и кален-
дарных сообщений надо найти и подключить файл ext-lang-ru.js:
<script type="text/javascript" src="ext-lang-ru.js"></script>

Рис. 18.4. Проверка полей формы с помощью VTypes


218 Часть IV. Библиотека ExtJS

Все, дорогой читатель! Теперь у вас есть красивый русскоязычный кален-


дарь! Сначала полюбуйтесь, как все это выглядит в браузере (рис. 18.4),
а потом начнем разбираться.
Итак, снова HTML-файл adv-vtypes.html со ссылкой на скрипт adv-vtypes.js
можно найти на компакт-диске, но его мы здесь не приводим по причине
тривиальности, а сам сценарий JavaScript — в листинге 18.6.
Листинг 18.6. Сценарий adv-vtypes.js для усовершенствованной проверки
форм
/* Синглетон Ext.form.VTypes определяет параметры проверки форм.
Начинаем здесь задавать типы объектов формы: daterange и password */
Ext.apply(Ext.form.VTypes, {
daterange : function(val, field) {
// Метод parseDate преобразует дату в заданный параметром формат
var date = field.parseDate(val);
if (!date){
return;
}
if (field.startDateField && (!this.dateRangeMax ||
(date.getTime() != this.dateRangeMax.getTime()))) {
var start = Ext.getCmp(field.startDateField);
// Устанавливает максимально допустимое значение даты
start.setMaxValue(date);
start.validate();
this.dateRangeMax = date;
}
else if (field.endDateField && (!this.dateRangeMin ||
(date.getTime() != this.dateRangeMin.getTime()))) {
var end = Ext.getCmp(field.endDateField);
// Устанавливает минимально допустимое значение даты
end.setMinValue(date);
// Проверяет поля на допустимость значений
end.validate();
this.dateRangeMin = date;
}
/* Всегда возвращает true, поскольку мы используем этот VType
только для установки допустимых значений */
return true;
Глава 18. Формы 219

},
password : function(val, field) {
if (field.initialPassField) {
var pwd = Ext.getCmp(field.initialPassField);
return (val == pwd.getValue());
}
return true;
},
passwordText : 'Указаны различные пароли'
});
Ext.onReady(function(){
Ext.QuickTips.init();
// Включаем отображение ошибки во всплывающем окне
Ext.form.Field.prototype.msgTarget = 'side';
var bd = Ext.getBody();
/* ================ Диапазон дат ====================== */
var dr = new Ext.FormPanel({
labelWidth: 125,
frame: true,
title: 'Диапазон дат',
bodyStyle: 'padding:5px 5px 0',
width: 350,
defaults: {width: 175},
defaultType: 'datefield',
items: [{
fieldLabel: 'Начальная дата',
name: 'startdt', // Имя поля начальной даты
id: 'startdt',
vtype: 'daterange',
endDateField: 'enddt' // Идентификатор поля конечной даты
},{
fieldLabel: 'Конечная дата',
name: 'enddt', // Имя поля конечной даты
id: 'enddt',
vtype: 'daterange',
startDateField: 'startdt' // Идентификатор поля начальной даты
}]
});
dr.render('dr');
220 Часть IV. Библиотека ExtJS
/* =============== Проверка пароля ====================== */
var pwd = new Ext.FormPanel({
labelWidth: 125,
frame: true,
title: 'Проверка пароля',
bodyStyle: 'padding:5px 5px 0',
width: 350,
defaults: {
width: 175,
inputType: 'password' // Задаем тип поля ввода
},
defaultType: 'textfield',
items: [{
fieldLabel: 'Пароль', // Задаем метку поля
name: 'pass',
id: 'pass'
},{
fieldLabel: 'Подтвердите пароль',
name: 'pass-cfrm',
vtype: 'password', // Задаем тип поля
initialPassField: 'pass' // Идентификатор первого поля пароля
}]
});
pwd.render('pw'); // Отображаем панель pwd с проверяемой формой
});

Для того чтобы понять логику сценария, проще начать рассматривать его
с середины — с функции Ext.onReady. Действия в ней начинаются с инициа-
лизации механизма всплывающих подсказок. Эту задачу решает функция
Ext.QuickTips.init.
Мы собираемся во всплывающей подсказке оповещать о несовпадающих па-
ролях, поэтому следующее, что надо сделать, — это задать область, где будут
выводиться диагностические сообщения. Для этого надо установить значения
конфигурационной опции msgTarget для класса Ext.form.Field. Изменим
это значение, обратившись к прототипу этого класса. Допустимыми значе-
ниями опции являются:
qtip — отображать подсказку, когда пользователь наводит курсор на поле;
title — отображать дефолтовое для браузера окно сообщения;
Глава 18. Формы 221

under — добавлять блок div с текстом в начало поля, содержащего ошибку;


side — добавлять значок с сообщением об ошибке справа от поля;
[element id] — добавлять текст ошибки прямо в свойство innerHTML вы-
бранного элемента.
Затем создаем две формы. Первая форма предназначена для ввода дат, при-
чем первая дата должна быть более ранней, чем вторая.
Вторая форма предполагает, что пользователь дважды введет пароль, а про-
верка проводится для того, чтобы исключить возможность ввода различных
значений. Как только создаем формы, так сразу отображаем их методом
render.
Параметр vtype полей этих форм связывает их с функциями daterange
и password, определенными в начале скрипта.
А в начале сценария вызывается метод apply, который копирует все указан-
ные ему опции в конфигурируемый объект. Опциями как раз и будут две
упомянутые функции, а также текст диагностического сообщения, указанный
в опции passwordText. А вот объект — это объект класса Ext.form.VTypes,
предоставляющий возможность проверок данных форм, причем проверок,
характер которых можно настраивать.
Глава 19

Визуальные эффекты.
Drag & drop

Свертывание и развертывание блока


Начнем с фрагмента текста, который сворачивается или разворачивается при
щелчке по ссылке. Воспользуемся для этого встроенными в библиотеку ExtJS
методами.
Наша страница будет состоять из нескольких блоков div и небольшого тек-
ста (листинг 19.1).
Листинг 19.1. toggle.html
<!DOCTYPE HTML PUBLIC "-// W3C// DTD HTML 4.01 Transitional// EN"
"http:// www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Слайдер - элемент с переменными размерами</title>
<link rel="stylesheet" type="text/css"
href="ext-2.2/resources/css/ext-all.css" />
<script type="text/javascript"
src="ext-2.2/adapter/ext/ext-base.js"> </script>
<script type="text/javascript" src="ext-2.2/ext-all.js"> </script>
<script type="text/javascript" src="slidingtext.js"> </script>
<style type="text/css">
.click_div {
background-color: #CC00CC;
padding: 20px;
}
Глава 19. Визуальные эффекты. Drag & drop 223

</style>
</head>
<body>
<h2>Щелкай по ссылкам!</h2>
<ul>
<li><a id="textup" href="javascript:;">Текст сворачивается вверх </a>
</li>
<li><a id="textdown" href="javascript:;">Текст сворачивается вниз </a>
</li>
<li><a id="texttoggle" href="javascript:;">Показываем или прячем текст
</a> </li>
</ul>
<p>&nbsp;</p>
<div class="click_div" id="div1">Я div с ID = 1</div>
<div id="slider">Слайдер</div>
<div class="click_div" id="div2">Другой div</div>
<div id="noslide">Здесь слайдера нет</div>
</body>
</html>

Теперь напишем сценарий JavaScript (листинг 19.2). Вначале сделаем так,


чтобы сценарий не запускался до загрузки всех элементов DOM в браузер
(кроме рисунков). Для этого применим функцию Ext.onReady().
Дальше следует перехватить событие, возникающее при щелчке по ссылке.
При щелчке по элементу textup должна вызываться функция slideText
с параметрами up и slider, а затем привлекаем внимание к элементу textup,
используя визуальный эффект из класса ExtJS.FX.
Класс FX снабжает разработчика анимационными визуальными эффектами.
Применяемый нами метод frame создает неяркую расширяющуюся рамочку
вокруг слов, по которым щелкает пользователь. Постарайтесь разглядеть ее
на рис. 19.1 и в своем браузере.
В теле функции onReady() сначала идет определение анонимной функции,
которая принимает два параметра, передаваемые ей обработчиком событий
Ext.get().on. Это параметры — событие event и целевой объект target,
с их помощью можно управлять обработкой события и узнать, по какому
элементу был выполнен щелчок.
224 Часть IV. Библиотека ExtJS

Рис. 19.1. Подсветка активного элемента

Листинг 19.2. sliding.js


// Удостоверьтесь, что путь до файла здесь указан правильно!
Ext.BLANK_IMAGE_URL = 'ext-2.2/resources/images/default/s.gif';
// Вызывается после построения дерева DOM
Ext.onReady(function() {
// Задаем функцию, выполняющую всю анимационную работу
var slideText = function(direction,element){
var slideMe = Ext.get(element);
switch(direction){
// Определяем направление движения
case 'up' :
// Проверяем, виден ли элемент
if (slideMe.isVisible()) {
// Раз мы сюда попали, значит, элемент виден
slideMe.slideOut('t', {
easing: 'easeOut',
duration: .5, // Продолжительность эффекта
remove: false,
useDisplay: true
Глава 19. Визуальные эффекты. Drag & drop 225

});
}
break;
case 'down' :
// Если элемент не виден, не делаем ничего
if (!slideMe.isVisible()) {
// Раз мы сюда попали, значит, элемент виден
slideMe.slideIn('t', {
easing: 'easeOut',
duration: .5
});
}
break;
default :
// Переключаем видимость элемента на противоположную
slideMe.toggle();
break
}
}
Ext.get('textup').on('click',function(e,t){
// Заставляем элемент сворачиваться вверх
slideText('up','slider');
Ext.get(t.id).frame('cccccc',1);
});

Ext.get('textdown').on('click',function(e,t){
// Заставляем элемент сворачиваться вниз
slideText('down','slider');
Ext.get(t.id).frame('cccccc',1);
});
Ext.get('texttoggle').on('click',function(e,t){
// Переключаем видимость элемента на противоположную
slideText('toggle','slider');
Ext.get(t.id).frame('cccccc',1);
});
});

Разберем теперь логику работы, двигаясь по сценарию снизу вверх. Как ни


странно, такой путь часто помогает понять логику JavaScript быстрее.
226 Часть IV. Библиотека ExtJS

Итак, в конце скрипта три раза вызывается метод Ext.get, каждый раз выби-
рая одну из ссылок вверху страницы. Методом on мы для каждой ссылки за-
даем обработчик события click. Обработчик вызывает функцию slideText
с необходимыми параметрами и функцию frame, создающую серую (цвета
#cccccc) подвижную рамку.
Перейдем теперь выше по тексту, к функции slideText. Получив элемент,
на котором произошло событие, мы быстренько записываем его в перемен-
ную slideMe.
Дальше у нас три дороги: параметр direction может принимать одно из сле-
дующих значений — up down или toggle. Последняя — дорога по умолча-
нию. При выборе любой из трех дорог мы вначале проверяем, виден ли раз-
дел слайдера, а потом выполняем необходимые в данном случае действия,
вызывая функции slideIn, slideOut или toggle.
Параметр t сообщает методам slideIn или slideOut, что эффект надо при-
менять, начиная с верхнего края элемента. Можно было указать, например,
l — для применения к левому краю.
При задании конфигурационных параметров метода slideOut мы указали
easing:'easeOut'. Это приводит к тому, что эффект длится полсекунды,
элемент не удаляется из DOM и разрешает соседним элементам занять место,
освободившееся после скрытия нашего элемента, создавая таким образом
требуемый анимационный эффект.

Изменение размеров блока


Создадим блок, размеры которого можно менять, просто ухватившись за
правую или нижнюю границу блока и потащив эту границу в нужную сторо-
ну (листинг 19.3).
Листинг 19.3. Изменение размеров блока
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Resizable Examples</title>
<link rel="stylesheet" type="text/css"
href="ext-2.2/resources/css/ext-all.css" />
<script type="text/javascript"
src="ext-2.2/adapter/ext/ext-base.js"></script>
Глава 19. Визуальные эффекты. Drag & drop 227

<script type="text/javascript" src="ext-2.2/ext-all.js"></script>


<script language="javascript" src="basic.js"></script>
<link rel="stylesheet" type="text/css" href="examples.css" />
<style type="text/css">
#basic{
border: 1px solid #c3daf9;
color: #1e4e8f;
font: bold 14px tahoma,verdana,helvetica;
text-align: center;
padding-top: 20px;
}
</style>
</head>
<body>
<h1>Пример изменения размеров</h1>
<p>
<b>Базовый пример</b><br />
Для изменения размеров поместите курсор мыши вблизи правой или
нижней границы элемента.
</p>
<div id="basic">Я могу измениться!</div>
</body>
</html>

Тут главное — не заблудиться среди стилевых таблиц и скриптов. Но если


справитесь с правильным указанием путей, то наградой вам будет div, раз-
мер которого можно менять, ухватив и потащив за его край. Так, давайте
двигаться дальше, добавляем сценарий (листинг 19.4).
Листинг 19.4. Сценарий basic.js
var ResizableExample = {
init : function(){
var basic = new Ext.Resizable('basic', {
width: 200,
height: 100,
minWidth:100,
228 Часть IV. Библиотека ExtJS
minHeight:50
});
}
};
Ext.EventManager.onDocumentReady(ResizableExample.init,
ResizableExample, true);

Для того чтобы можно было изменять размеры блока с идентификатором


basic, надо создать объект Ext.Resizable, указав ему идентификатор и па-
раметры блока. Мы задали исходные размеры (width и height) и те размеры,
до которых можно уменьшать блок (minWidth и minHeight). В этом случае
растягивать блок можно по всем направлениям и до сколь угодно большого
размера на странице.
Направление растяжения можно задать с помощью не указанного в при-
веденном примере параметра handles, который может ограничивать направ-
ление растяжения или сжатия, если задать ему значения из следующего
списка:
'n' (north) — вверх по экрану;
's' (south) — вниз по экрану;
'e' (east) — можно двигать только правую границу;
'w' (west) — можно двигать только левую границу;
'nw' (northwest), 'sw' (southwest), 'se' (southeast), 'ne' (northeast) — по-
парно объединяем направление изменения размеров;
'all' — все, действует по умолчанию.
Метод
onDocumentReady(Function fn, [Object scope], [boolean options]) : void

вызывается, когда объектная модель загружаемого документа готова. В каче-


стве первого параметра передаем функцию, которая инициализирует наш
объект Ext.Resizable. С созданным объектом связываются события, кото-
рые положено для него обрабатывать, т. е. манипуляции мышью должны
приводить к изменению размеров блока. Во втором параметре задаем кон-
текст исполнения. Имеется в виду, что надо указать тот объект, для которого
будет обрабатываться событие нажатия и движения мыши. И, наконец, зада-
ние значения true для третьего параметра приведет к тому, что к объекту бу-
дут применяться обработчики событий.
Все! Вид блока дан на рис. 19.2.
Глава 19. Визуальные эффекты. Drag & drop 229

Рис. 19.2. Изменение размеров блока

Drag & drop


Создадим блок-контейнер, содержащий несколько вложенных в него блоков.
Зададим для них идентификаторы и стили, как представлено в листинге 19.5.
Листинг 19.5. Страница с перетаскиваемыми элементами (drag &drop)
<!DOCTYPE HTML PUBLIC "-// W3C// DTD HTML 4.01 Transitional// EN"
"http:// www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css"
href="ext-2.2/resources/css/ext-all.css">
<link rel="stylesheet" type="text/css" href="Ext.ux.IconCombo.css">
<script type="text/javascript" src="ext-2.2/adapter/ext/ext-base.js">
</script>
<script type="text/javascript" src="ext-2.2/ext-all-debug.js">
</script>
<script type="text/javascript" src="dd.js"></script>
<script type="text/javascript">Ext.onReady(Tutorial.dd.init, Tutorial.dd);
</script>
230 Часть IV. Библиотека ExtJS
<title> Drag &amp; Drop Tutorial</title>
<style type="text/css">
body {
font-size: 11px;
}
.dd-ct {
position:absolute;
border: 1px solid silver;
width: 180px;
height: 180px;
top: 32px;
background-color: #ffffc0;
}
#dd1-ct {
left: 64px;
}
#dd2-ct {
left: 256px;
}
.dd-item {
height: 18px;
border: 1px solid #a0a0a0;
background-color: #c4d0ff;
vertical-align: middle;
cursor: move;
padding: 2px;
z-index: 1000;
}
.dd-ct .dd-item {
margin: 2px;
}
.dd-proxy {
opacity: 0.4;
-moz-opacity: 0.4;
filter: alpha(opacity=40);
}
.dd-over {
background-color: #ffff60;
}
Глава 19. Визуальные эффекты. Drag & drop 231

</style>
</head>
<body>
<div class="dd-ct" id="dd1-ct">
<div class="dd-item" id="dd1-item1">Элемент 1.1</div>
<div class="dd-item" id="dd1-item2">Элемент 1.2</div>
<div class="dd-item" id="dd1-item3">Элемент 1.3</div>
</div>
</body>
</html>

Итак, у нас есть один контейнер класса dd-ct и три раздела div, которые
можно будет перетаскивать. Теперь — JavaScript-код (листинг 19.6). Для на-
чала задаем пространство имен для наших методов.
Листинг 19.6. Сценарий dd.js для организации перетаскивания блоков
Ext.BLANK_IMAGE_URL = 'ext-2.2/resources/images/default/s.gif';
Ext.namespace('Tutorial');
Tutorial.dd = function() {
return {
// Общедоступные методы
init: function() {
var dd11 = Ext.get('dd1-item1');
dd11.dd = new Ext.dd.DDProxy('dd1-item1', 'group');
var dd12 = Ext.get('dd1-item2');
dd12.dd = new Ext.dd.DDProxy('dd1-item2', 'group');
var dd13 = Ext.get('dd1-item3');
dd13.dd = new Ext.dd.DDProxy('dd1-item3', 'group');
}
};
}();

Обратите внимание, что в начале скрипта задается пространство имен для


последующих функций. Класс dd реализует технологию drag & drop, добав-
ляя в документ элемент, который можно перемещать по экрану движением
курсора мыши. Метод DDProxy выполняет основную работу, указывая в пер-
вом параметре идентификатор элемента, который надо сделать перетаски-
ваемым. Вот так мы и работаем!
232 Часть IV. Библиотека ExtJS

Усложним нашу задачу. Допустим, нам надо иметь два контейнера с элемен-
тами, перетаскивать и бросать блоки в любом месте страницы, но в случае
если блок сброшен над каким-то контейнером, он аккуратно размещается
в нем автоматически. Добавляем в предыдущую страницу еще одну область
с перетаскиваемыми элементами (листинг 19.7).
Листинг 19.7. Страница с двумя контейнерами перетаскиваемых элементов
<!DOCTYPE HTML PUBLIC "-// W3C// DTD HTML 4.01 Transitional// EN"
"http:// www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css"
href="ext-2.2/resources/css/ext-all.css">
<link rel="stylesheet" type="text/css" href="Ext.ux.IconCombo.css">
<script type="text/javascript" src="ext-2.2/adapter/ext/ext-base.js">
</script>
<script type="text/javascript" src="ext-2.2/ext-all-debug.js">
</script>
<script type="text/javascript" src="dd2.js"></script>
<script type="text/javascript">Ext.onReady(Tutorial.dd.init, Tutori-
al.dd);</script>
<title>Custom Drag &amp; Drop Tutorial</title>
<style type="text/css">
// Те же стили, что и в предыдущем примере
</style>
</head>
<body>
<div class="dd-ct" id="dd1-ct">
<div class="dd-item" id="dd1-item1">Элемент 1.1</div>
<div class="dd-item" id="dd1-item2">Элемент 1.2</div>
<div class="dd-item" id="dd1-item3">Элемент 1.3</div>
<div class="dd-ct" id="dd2-ct">
<div class="dd-item" id="dd2-item1">Item 2.1</div>
<div class="dd-item" id="dd2-item2">Item 2.2</div>
<div class="dd-item" id="dd2-item3">Item 2.3</div>
</div>
</div>
</body>
</html>
Глава 19. Визуальные эффекты. Drag & drop 233

Изменяем и скрипт (листинг 19.7), добавив в него метод override, который


перегружает существующие методы, добавляя их измененный код в прототип
используемого класса Ext.dd.DDProxy (листинг 19.8). В этом классе опреде-
лены абстрактные методы, которые в этом сценарии и будут перегружены.
Вот эти методы:
startDrag(X, Y) — абстрактный метод, вызываемый после того, как
пользователь щелкнул по перетаскиваемому объекту; параметры — коор-
динаты события;
onDragOut(Event e, String|DragDrop[] id) — абстрактный метод, вы-
зываемый после того, как событие hover завершилось;
onDragOver(Event e, String|DragDrop[] id) — абстрактный метод, вы-
зываемый, когда текущий элемент находится над другим перетаски-
ваемым элементом. Второй параметр у этого и предыдущего методов —
элемент или идентификатор элемента;
endDrag(Event e) — вызывается при завершении перетаскивания.
Последние три метода в качестве первого параметра принимают произошед-
шее событие.
Листинг 19.8. Сценарий dd2.js
Ext.BLANK_IMAGE_URL = 'ext-2.2/resources/images/default/s.gif';
Ext.namespace('Tutorial');
Ext.override(Ext.dd.DDProxy, {
startDrag: function(x, y) {
var dragEl = Ext.get(this.getDragEl());
var el = Ext.get(this.getEl());
dragEl.applyStyles({border:'10px','z-index':2000});
dragEl.update(el.dom.innerHTML);
dragEl.addClass(el.dom.className + ' dd-proxy');
},
onDragOver: function(e, targetId) {
if ('dd1-ct' === targetId || 'dd2-ct' === targetId) {
var target = Ext.get(targetId);
this.lastTarget = target;
target.addClass('dd-over');
}
},
onDragOut: function(e, targetId) {
234 Часть IV. Библиотека ExtJS
if ('dd1-ct' === targetId || 'dd2-ct' === targetId) {
var target = Ext.get(targetId);
this.lastTarget = null;
target.removeClass('dd-over');
}
},
endDrag: function() {
var dragEl = Ext.get(this.getDragEl());
var el = Ext.get(this.getEl());
if (this.lastTarget) {
// Добавляем вложенный элемент
Ext.get(this.lastTarget).appendChild(el);
el.applyStyles({position:'', width:''});
}
else { // Стилизуем и позиционируем элемент
el.applyStyles({position:'absolute'});
el.setXY(dragEl.getXY());
el.setWidth(dragEl.getWidth());
}
Ext.get('dd1-ct').removeClass('dd-over');
Ext.get('dd2-ct').removeClass('dd-over');
}
});
Tutorial.dd = function() {
return {
// Общедоступные методы
init: function() {
// Области сбрасывания
var dz1 = new Ext.dd.DropZone('dd1-ct', {ddGroup:'group'});
var dz2 = new Ext.dd.DropZone('dd2-ct', {ddGroup:'group'});
// Контейнер 1
var dd11 = Ext.get('dd1-item1'); // Отбираем элементы по id
dd11.dd = new Ext.dd.DDProxy('dd1-item1', 'group');
var dd12 = Ext.get('dd1-item2');
dd12.dd = new Ext.dd.DDProxy('dd1-item2', 'group');
var dd13 = Ext.get('dd1-item3');
dd13.dd = new Ext.dd.DDProxy('dd1-item3', 'group');
// Контейнер 2
var dd21 = Ext.get('dd2-item1');
Глава 19. Визуальные эффекты. Drag & drop 235

dd21.dd = new Ext.dd.DDProxy('dd2-item1', 'group');


var dd22 = Ext.get('dd2-item2');
dd22.dd = new Ext.dd.DDProxy('dd2-item2', 'group');
var dd23 = Ext.get('dd2-item3');
dd23.dd = new Ext.dd.DDProxy('dd2-item3', 'group');
}
};
}();

Мы создали две зоны (dz1 и dz2) для сбрасывания элементов из тех же кон-
тейнеров, которыми оперировали ранее. Кроме собственно перетаскивания
сценарий применяет к блокам на различных этапах различные стили.
В результате усовершенствований сценария блоки можно перетаскивать из
их контейнера и помещать в любом месте на странице. При перетаскивании
элемента по контейнеру последний вспыхивает более ярким цветом. На вре-
мя перетаскивания блок бледнеет. Блок можно бросить и в исходный контей-
нер. Если блок сброшен чуть мимо контейнера, он автоматически сам акку-
ратно встраивается в контейнер. Вот как это выглядит в браузере (рис. 19.3).

Рис. 19.3. Перетаскиваемые элементы


Глава 20

Простые виджеты

Авторы уверены, что термин "виджеты" лучше всего переводится на русский


язык словом "прибамбасы", но едва ли удастся убедить какого-либо литера-
турного редактора, что подобный термин уместен в тексте данной книги. По-
этому в данной книге так и применяется калька с английского слова widgets.
Рассмотрим создание простых виджетов с помощью библиотеки ExtJs.

Всплывающие подсказки
Класс подсказок ToolTip расширяет класс Ext.Panel и дает возможность вы-
водить на экран дополнительную информацию при наведении курсора на
какой-либо элемент.
HTML-документ, в котором работают подсказки, приведен в листинге 20.1.

Листинг 20.1. HTML-документ с подсказками


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Tips Example</title>
<link rel="stylesheet" type="text/css"
href="ext-2.2/resources/css/ext-all.css" />
<script type="text/javascript"
src="ext-2.2/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="ext-2.2/ext-all.js"></script>
<script type="text/javascript" src="qtips.js"></script>
Глава 20. Простые виджеты 237

<style type="text/css">
.tip-target {
width: 100px;
text-align:center;
padding: 5px 0;
border:1px dotted #99bbe8;
background:#dfe8f6;
color: #15428b;
cursor:default;
margin:10px;
font:bold 11px tahoma,arial,sans-serif;
float:left;
}
</style>
<link rel="stylesheet" type="text/css" href="examples.css" />
</head>
<body>
<h1>Примеры всплывающих подсказок</h1>
<h3>Самая простая подсказка</h3>
<div id="tip1" class="tip-target">Базовая подсказка</div>
<div id="tip2" class="tip-target">Запрещено автоматически скры-
вать</div>
<div id="ajax-tip" class="tip-target">Подсказка с Ajax-запросом</div>
<div id="track-tip" class="tip-target">Отслеживание курсора</div>
<div id="tip4" class="tip-target" ext:qtip="Мой QuickTip">Быстрая
подсказка QuickTip</div>
</body>
</html>

В HTML-документе мы создали несколько блоков, указав им идентификато-


ры. В листинге 20.2 можно полюбоваться на код JavaScript, который и созда-
ет подсказки.
Перечислим используемые в примере конфигурационные параметры объекта
класса Ext.ToolTip:
target — HTML-элемент, объект Ext.Element или идентификатор элемента,
для которого требуется всплывающая подсказка;
width — ширина всплывающего элемента в пикселах;
autoLoad — URL загружаемого AJAX-запросом документа. Содержимое
документа будет отображено в подсказке;
238 Часть IV. Библиотека ExtJS

dismissDelay — сколько миллисекунд надо подождать перед тем, чтобы


закрыть подсказку (по умолчанию 5000);
html— фрагмент HTML-документа, который используется в качестве
подсказки;
title — текст в заголовке панели подсказки;
autoHide — значение true приведет к тому, что подсказка скроется после
ухода курсора с целевого элемента или после истечения времени, указан-
ного в dismissDelay;
closable — значение true приведет к включению кнопки закрытия под-
сказки (по умолчанию false);
draggable — true для разрешения перетаскивать подсказку;
trackMouse — true для разрешения подсказке двигаться вслед за кур-
сором.

Листинг 20.2. Сценарий qtips.js


Ext.onReady(function(){
new Ext.ToolTip({
target: 'tip1',
html: 'Очень простая подсказка'
});
new Ext.ToolTip({
target: 'ajax-tip',
width: 200,
// Запрашиваем файл для отображения
autoLoad: {url: 'ajax-tip.html'},
dismissDelay: 15000 // Автоматически прятать через 15 сек
});
new Ext.ToolTip({
target: 'tip2',
html: 'Щелкни X, чтобы закрыть меня',
title: 'My Tip Title',
// Не прятать подсказку после ухода мыши с объекта
autoHide: false,
closable: true,
draggable:true
});
new Ext.ToolTip({
Глава 20. Простые виджеты 239

target: 'track-tip',
title: 'Mouse Track',
width:200,
html: 'Эта подсказка будет следовать за курсором, пока он нахо-
дится на элементе',
trackMouse:true
});
Ext.QuickTips.init();

});

Подсказка, возникающая около блока с идентификатором ajax-tip, отправляет


AJAX-запрос к странице ajax-tip.html (там лежит HTML-фрагмент
"<b>Подсказка Ajax</b><br />") и отображает ответ сервера. Всплывающие
подсказки показаны на рис. 20.1.

Рис. 20.1. Всплывающие подсказки

Вот так! Едва ли какие-либо комментарии еще требуются.


Глава 21

Создание редактируемых таблиц

Редактируемая таблица (grid) — одно из самых красивых и мощных средств


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

Создание базы данных


Для начала создадим базу данных, информация из которой будет отобра-
жаться на странице в виде таблицы. База называется tutorial (листинг 21.1)
и состоит из двух таблиц: parties (перечень партий в разные эпохи в США)
и presidents (список президентов США, пишем по-русски в кодировке UTF-8).
П РИМЕЧАНИЕ
Авторы подавили патриотические порывы переделать содержание базы, при-
веденной в англоязычном руководстве, на более русифицированное после то-
го, как поняли, что сами с интересом изучают полученные сведения.

Листинг 21.1. Создание базы данных tutorial


CREATE TABLE IF NOT EXISTS `parties` (
`IDparty` int(11) NOT NULL auto_increment,
`name` varchar(40) NOT NULL,
PRIMARY KEY (`IDparty`)
);
INSERT INTO `parties` (`IDparty`, `name`) VALUES
Глава 21. Создание редактируемых таблиц 241

(1, 'Нет'),
(2, 'Федералисты'),
(3, 'Демократы-Республиканцы'),
(4, 'Демократы'),
(5, 'Виги'),
(6, 'Республиканцы');
CREATE TABLE IF NOT EXISTS `presidents` (
`IDpresident` int(11) NOT NULL auto_increment,
`IDparty` int(11) NOT NULL,
`firstname` varchar(20) NOT NULL,
`lastname` varchar(20) NOT NULL,
`tookoffice` date NOT NULL,
`leftoffice` date NOT NULL,
`income` decimal(14,2) NOT NULL,
PRIMARY KEY (`IDpresident`)
);
INSERT INTO `presidents` (`IDpresident`, `IDparty`, `firstname`, `last-
name`, `tookoffice`, `leftoffice`, `income`) VALUES
(1, 1, 'Джордж', 'Вашингтон', '1789-04-30', '1797-03-04', 135246.32),
. . .
(40, 6, 'Рональд', 'Рейган', '1981-01-20', '1989-01-20', 99867297.35),
(41, 6, 'Джордж', 'Буш', '1989-01-20', '1993-01-20', 92048204.24),
(42, 4, 'Билл', 'Клинтон', '1993-01-20', '2001-01-20', 12073975.24);

Серверный сценарий для запроса к базе


и генерации ответа клиенту
Затем напишем сценарий, запрашивающий эти данные и передающий их
клиенту (листинг 21.2). Исходим из того, что в нашем распоряжении нахо-
дится модуль PHP версии 5.2 или более новый, что позволяет применить
функцию json_encode для передачи данных в формате JSON клиенту.

Листинг 21.2. Сценарий database.php — запрос данных из базы


<?php
$link = mysqli_connect("localhost", "root", "secret") or
die("Could not connect: " . mysqli_error());
mysqli_select_db($link,"tutorial");
// Проверяем, что сценарий запрошен с определенным параметром
242 Часть IV. Библиотека ExtJS
$task = '';
if ( isset($_POST['task'])){
$task = $_POST['task'];
}
switch($task){
case "LISTING":
getList();
break;
default:
echo "{failure:true}";
break;
}
function getList()
{
global $link;
$query = "SELECT * FROM presidents pr, parties pa WHERE pr.IDparty =
pa.IDparty";
$result = mysqli_query($link,$query);
$nbrows = mysqli_num_rows($result);
if($nbrows>0){
while($rec = mysqli_fetch_array($result)){
// Преобразуем в формат даты ExtJS
$rec['tookoffice']=codeDate($rec['tookoffice']);
$rec['leftoffice']=codeDate($rec['leftoffice']);
$arr[] = $rec;
}
$jsonresult = json_encode($arr);
echo '({"total":"'.$nbrows.'","results":'.$jsonresult.'})';
} else {
echo '({"total":"0", "results":""})';
}
}
// YYYY-DD-MM превращаем в DD/MM/YYYY
function codeDate ($date) {
$tab = explode ("-", $date);
$r = $tab[1]."/".$tab[2]."/".$tab[0];
return $r;
}
Глава 21. Создание редактируемых таблиц 243

Сценарий достаточно ясен: в нем информация о президентах запрашивается


из базы данных. Мы собираемся обращаться к этому сценарию методом POST,
передавая при этом параметр task. Перед началом важных действий прово-
дится проверка того, что параметр task действительно передается, причем
указанным методом. Это несколько повышает безопасность функционирова-
ния сценария. Еще один существенный момент: надо отформатировать дату,
полученную из базы, в соответствии с форматом, используемым в ExtJS, чем
и занимается функция codeDate.

Клиентская часть: HTML и сценарий


JavaScript
HTML-документ будет выглядеть так, как представлено в листинге 21.3.

Листинг 21.3. Страница с редактируемой таблицей


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; UTF-8" />
<title>Tutorial: Grid</title>
<link rel="stylesheet" type="text/css" href="resources/css/ext-all.css"/>
<script type="text/javascript" src="adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="ext-all.js"></script>
<script type="text/javascript" src="mainscript.js"></script>
</head>
<body>
</body>
</html>

Здесь главное — не забыть подключить все необходимое. Далее в этой главе


не будут приводиться листинги соответствующих HTML-документов, т. к.
они различаются только именами JavaScript-сценариев, на которые ссылают-
ся. Мы просто будем указывать имена файлов HTML-документов, под кото-
рыми читатель найдет их на прилагаемом к книге компакт-диске, также каж-
дый раз укажем, ссылка на какой сценарий JavaScript стоит в этом HTML-
документе. Наконец, опишем самую интересную часть нашего кода: создание
сценария и генерацию в нем таблицы (листинг 21.4).
244 Часть IV. Библиотека ExtJS

Листинг 21.4. Сценарий mainscript.js


var PresidentsDataStore; // Задаем имена создаваемых объектов
var PresidentsColumnModel;
var PresidentListingEditorGrid;
var PresidentListingWindow;

Ext.onReady(function(){
/* Активизируем всплывающие подсказки, доступные для объектов,
которые мы будем создавать. */
Ext.QuickTips.init();
/* Класс data.Store организует массивы для хранения данных.
Объект такого класса может обрабатывать данные из любого источника,
например, из XML-документов, данных в формате JSON.
Класс data.Store создает записи, который могут отображаться
в различных видах, например, как содержимое таблицы. */
PresidentsDataStore = new Ext.data.Store({
id: 'PresidentsDataStore',
/* AJAX-запрос данных из серверного скрипта. Можно обращаться
только к тому же домену, откуда пришла HTML-страница. */
proxy: new Ext.data.HttpProxy({
url: 'database.php',
method: 'POST'
}),
// Параметр task передается в HTTP-запросе
baseParams:{task: "LISTING"},
/* Класс Ext.data.JsonReader позволяет создать массив объектов
Ext.data.Record из данных ответа в формате JSON. */
reader: new Ext.data.JsonReader({
// Зададим свойство, содержащее массив объектов строк
root: 'results',
// Свойство, из которого можно узнать, сколько записей
// в наборе данных
totalProperty: 'total',
id: 'id'
},[ /* Формирование свойств для JSON-объекта.
Задаем имя свойства, тип и указываем объект,
из которого берем данные */
{name: 'IDpresident', type: 'int', mapping: 'IDpresident'},
Глава 21. Создание редактируемых таблиц 245

{name: 'FirstName', type: 'string', mapping: 'firstname'},


{name: 'LastName', type: 'string', mapping: 'lastname'},
{name: 'IDparty', type: 'int', mapping: 'IDparty'},
{name: 'PartyName', type: 'string', mapping: 'name'},
{name: 'TookOffice', type: 'date', mapping: 'tookoffice'},
{name: 'LeftOffice', type: 'date', mapping: 'leftoffice'},
{name: 'Income', type: 'float', mapping: 'income'}
]), // Сортируем поле по возрастанию
sortInfo:{field: 'IDpresident', direction: "ASC"}
});
/* Класс ColumnModel определяет, как задаются столбцы таблицы,
т. е. каков тип данных столбцов, можно ли редактировать
эти данные и пр. */
PresidentsColumnModel = new Ext.grid.ColumnModel(
[{
header: '#', // Это поле не показываем
readOnly: true,
dataIndex: 'IDpresident',
width: 50,
hidden: false
},{
header: 'Имя',
dataIndex: 'FirstName',
width: 60,
/* Задаем текстовые поля, описываем в них маску, которая будет
фильтровать данные перед их отображением. Маска создается
в виде шаблона регулярных выражений. */
/* Задаем параметры поля формы, т. е. объекта, который позволяет
обрабатывать связанные с ним события, менять размер поля,
управление вводимыми данными и пр. */
editor: new Ext.form.TextField({
allowBlank: false,
maxLength: 20,
maskRe: /([a-zA-Z0-9\s]+)$/ // Маска для текстового поля
})
},{
header: 'Фамилия',
dataIndex: 'LastName',
width: 80,
246 Часть IV. Библиотека ExtJS
editor: new Ext.form.TextField({
allowBlank: false,
maxLength: 20,
maskRe: /([a-zA-Z0-9\s]+)$/
})
},{
header: 'ID party',
readOnly: true,
dataIndex: 'IDparty',
width: 50,
hidden: true
},{
header: 'Партия',
dataIndex: 'PartyName',
width: 150,
readOnly: true
},{
header: 'Пост принял',
dataIndex: 'TookOffice',
width: 80,
// Зададим функцию отображения даты, которая может быть
// вызвана в дальнейшем снова для форматирования даты.
renderer: Ext.util.Format.dateRenderer('d.m.Y'),
editor: new Ext.form.DateField({
format: 'd-m-Y'
}),
hidden: false
},{
header: 'Пост сдал',
dataIndex: 'LeftOffice',
width: 80,
renderer: Ext.util.Format.dateRenderer('d.m.Y'),
editor: new Ext.form.DateField({
format: 'd.m.Y'
}),
hidden: false
},{
header: "Доход",
dataIndex: 'Income',
Глава 21. Создание редактируемых таблиц 247

width: 150,
renderer: function(v){ return '$ ' + v; },
editor: new Ext.form.NumberField({
allowBlank: false,
allowDecimals: true,
allowNegative: false,
blankText: '0',
maxLength: 11
})
}]
);
PresidentsColumnModel.defaultSortable= true;

/* Наконец, все связываем вместе, создавая редактируемую таблицу


с данными. */
PresidentListingEditorGrid = new Ext.grid.EditorGridPanel({
id: 'PresidentListingEditorGrid',
store: PresidentsDataStore,
cm: PresidentsColumnModel, // Задание модели для столбцов
enableColLock: false,
clicksToEdit: 1,
selModel: new Ext.grid.RowSelectionModel({singleSelect:false})
});

/* Задаем Window - особую панель для размещения содержимого.


Можно менять размеры этой панели, перетаскивать ее по экрану,
группировать с другими объектами этого же класса так,
чтобы создавать эффект наложения панелей одну на другую и пр. */
PresidentListingWindow = new Ext.Window({
id: 'PresidentListingWindow',
title: 'Президенты США',
closable: true,
width: 700, // Ширина окна
height: 350, // Высота окна
plain: true,
layout: 'fit',
items: PresidentListingEditorGrid
});

/* Загружаем кэш записей, полученных с сервера с помощью Proxy.


248 Часть IV. Библиотека ExtJS
При загрузке используем ранее сконфигурированный объект
Ext.data.JsonReader для обработки данных. */
PresidentsDataStore.load();
// Отображаем созданную панель Window
PresidentListingWindow.show();
});

Для отображения редактируемой таблицы нужно иметь массив данных


data.Store и модель отображения этих данных — объект
Ext.grid.ColumnModel. Массив данных data.Store должен перезагружаться
в случае внесения каких-либо изменений в базу данных. Следовательно, надо
предусмотреть механизм отправки запроса к серверу.
Класс Store создает на клиентской стороне кэш с записями данных, которые
надо отображать в табличном виде. Объект Store использует сконфигуриро-
ванный объект DataProxy для получения данных с сервера при помощи
AJAX-запроса. Но сам объект Store не знает, в каком формате данные при-
ходят с сервера. Объект Store использует сконфигурированный объект
DataReader, в нашем примере для этого применяется JSONReader, для созда-
ния строк с данными. Эти данные получаются в результате обработки ответа
сервера.
Класс Data.Jsonreader создает массив записей, состоящих из объектов
Ext.data.Record. Данные для записей берутся из JSON-ответа сервера и при-
меняются в объекте на основании привязки, задаваемой конструктором
Ext.data.Record (см. параметр mapping).
Если вы хотите понять, каков ответ, приходящий с сервера, то поставьте
в листинге 21.2 строку $task = LISTING; перед оператором switch и запус-
тите этот сценарий отдельно. Часть того, что вы увидите на экране, будет вы-
глядеть так:
({"total":"42", "results":[{"0":"1"
,"IDpresident":"1"
,"1":"1"
,"IDparty":"1"
,"2":"\u0414 . . . "
,"lastname":"\u0412 . . ."
,"4":"1789-04-30"
,"tookoffice":"04\/30\/1789"
,"5":"1797-03-04"
,"leftoffice":"03\/04\/1797"
,"6":"135246.32"
,"income":"135246.32"
Глава 21. Создание редактируемых таблиц 249

В этом фрагменте мы часть кодов русских букв заменили многоточием для


удобочитаемости.
Теперь, если вы соберете все вместе и обратитесь к файлу index.html, то
к серверному сценарию database.php будет отправлен POST-запрос с парамет-
ром task = LISTING. Данные из базы будут переданы в клиентскую про-
грамму, обработаны, и вы увидите на экране следующее (рис. 21.1).

Рис. 21.1. Редактируемая таблица

Представление данных в таблице можно менять, выбирая опции, как указано


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

Разработка динамически редактируемой


таблицы
Для обновления данных в базе в сценарий PHP надо добавить фрагмент,
представленный в листинге 21.5.
250 Часть IV. Библиотека ExtJS

Листинг 21.5. Фрагмент сценария database2.php, обновляющий данные в базе


switch($task){
case "LISTING": // Значение параметра task для запроса данных из базы
getList();
break;
case "UPDATEPRES": // Значение параметра task для обновления базы
updatePresident();
break;
default:
echo "{failure:true}";
break;
}
// Функция обновления данных в базе
function updatePresident()
{global $link;
$IDpresident = $_POST['IDpresident'];
$FirstName = addslashes($_POST['FirstName']);
$LastName = addslashes($_POST['LastName']);
$PartyName = $_POST['PartyName'];
$TookOffice = $_POST['TookOffice'];
$LeftOffice = $_POST['LeftOffice'];
$Income = $_POST['Income'];
// Находим номер партии $IDparty
$query = "SELECT IDParty FROM parties WHERE Name='".$PartyName."'";
$result = mysqli_query($link,$query);
if(mysqli_num_rows($result)>0){
$arr = mysqli_fetch_array($result);
$IDparty = $arr['IDParty'];
} else {
echo '0';
}
// Инструкция обновления данных в таблице presidents
$query = "UPDATE presidents SET firstname = '$FirstName', lastname =
'$LastName', tookoffice = '$TookOffice', leftoffice = '$LeftOffice', ID-
party = '$IDparty', income='$Income' WHERE IDpresident=$IDpresident";
$result = mysqli_query($link,$query);
echo '1';
}
Глава 21. Создание редактируемых таблиц 251

Из приведенного только что фрагмента ясно, что, обратившись к серверному


сценарию database2.php с параметром task = UPDATEPRES, можно записать
новые данные в таблицу presidents. Для этого сами данные надо передать
методом POST. Читатель найдет полный текст этого сценария в файле с име-
нем database2.php на приложенном к книге компакт-диске. HTML-документ
сохранен в файле index2.html и содержит ссылку на сценарий mainscript2.js
в следующем теге:
<script type="text/javascript" src="mainscript2.js"></script>

Посмотрим сам сценарий JavaScript (листинг 21.6), из которого, собственно,


и передаются значения параметров для обновления базы данных. Сценарий
этот непрост, и именно подобные сценарии содержат то, ради чего читатель
и брал в руки эту книгу. Поэтому позволим себе привести его полностью.
Листинг 21.6. Сценарий mainscript2.js для динамического обновления таблицы
var PresidentsDataStore;
var PresidentsColumnModel;
var PresidentListingEditorGrid;
var PresidentListingWindow;

Ext.onReady(function(){

Ext.QuickTips.init();
// Функция сохранения изменений в базе данных
function saveThePresident(oGrid_event){
// Создание AJAX-запроса для обновления данных в базе
Ext.Ajax.request({
// Сообщение на период ожидания ответа
waitMsg: 'Подождите, пожалуйста..',
// Адрес запроса
url: 'database2.php',
// Параметры запроса
params: {
task: "UPDATEPRES",
// Имена полей для обновления таблицы в базе данных.
// Значение поля берется из элемента, на котором
// произошло событие.
IDpresident: oGrid_event.record.data.IDpresident,
FirstName: oGrid_event.record.data.FirstName,
252 Часть IV. Библиотека ExtJS
LastName: oGrid_event.record.data.LastName,
PartyName: oGrid_event.record.data.PartyName,
TookOffice: oGrid_event.record.data.TookOffice.format('Y-m-d'),
LeftOffice: oGrid_event.record.data.LeftOffice.format('Y-m-d'),
Income: oGrid_event.record.data.Income
},
// Обработка в случае удачного завершения запроса
success: function(response){
// Считываем ответ сервера
var result=eval(response.responseText);
switch(result){
case 1:
// Выполняем обновление данных
PresidentsDataStore.commitChanges();
// Обновляем данные в клиентском кэше
PresidentsDataStore.reload();
break;
default:
// Выводим сообщение при невозможности записи обновления
Ext.MessageBox.alert('Э-э-э...', 'Сохранить не удается...');
break;
}
},
failure: function(response){
var result=response.responseText;
Ext.MessageBox.alert('error',
'не удается соединиться с сервером. Попытайтесь позже.');
}
});
}
// Формирование кэша данных, отображаемых в таблице
PresidentsDataStore = new Ext.data.Store({
id: 'PresidentsDataStore',
// Запрос данных с сервера
proxy: new Ext.data.HttpProxy({
url: 'database2.php',
method: 'POST'
}),
// Параметр, передаваемый в запросе HTTP
baseParams:{task: "LISTING"},
Глава 21. Создание редактируемых таблиц 253

/* Обработка ответа сервера, приводящая к созданию объекта,


содержащего данные ответа */
reader: new Ext.data.JsonReader({
// Задаем имя свойства, которое содержит массив литеральных объектов
root: 'results',
/* Задаем имя свойства, в котором хранится размер массива,
заданного в предыдущем параметре */
totalProperty: 'total',
// Имя свойства, в котором хранится номер литерального объекта
id: 'id'
},[
// Привязка полей ответа сервера к свойствам создаваемого объекта
{name: 'IDpresident', type: 'int', mapping: 'IDpresident'},
{name: 'FirstName', type: 'string', mapping: 'firstname'},
{name: 'LastName', type: 'string', mapping: 'lastname'},
{name: 'IDparty', type: 'int', mapping: 'IDparty'},
{name: 'PartyName', type: 'string', mapping: 'name'},
{name: 'TookOffice', type: 'date', mapping: 'tookoffice'},
{name: 'LeftOffice', type: 'date', mapping: 'leftoffice'},
{name: 'Income', type: 'float', mapping: 'income'}
]),
sortInfo:{field: 'IDpresident', direction: "ASC"}
});
// Задание модели для формирования столбцов таблицы
PresidentsColumnModel = new Ext.grid.ColumnModel(
[{ // Столбец с номером записи, по умолчанию он не отображается
header: '#',
readOnly: true, // Значение этого поля нельзя менять
dataIndex: 'IDpresident', // Откуда берем данные
width: 40,
hidden: false
},{ // Столбец с именем президента
header: 'Имя',
dataIndex: 'FirstName',
width: 60,
// Задаем возможность редактирования значения поля
editor: new Ext.form.TextField({
allowBlank: false, // Поле не должно быть пустым
maxLength: 20,
// Маска для ввода текста содержит английские и русские буквы
254 Часть IV. Библиотека ExtJS
maskRe: /([а-яА-Яa-zA-Z0-9\s]+)$/
})
},{ // Столбец с фамилией президента
header: 'Фамилия',
dataIndex: 'LastName',
width: 80,
editor: new Ext.form.TextField({
allowBlank: false,
maxLength: 20,
maskRe: /([а-яА-Яa-zA-Z0-9\s]+)$/
})
},{ // Столбец с номером партии, к которой принадлежит президент,
// по умолчанию не отображается.
header: 'ID party',
readOnly: true,
dataIndex: 'IDparty',
width: 50,
hidden: true
},{ // Столбец с названием партии
header: 'Party',
dataIndex: 'PartyName',
width: 150,
/* Создание элемента интерфейса, позволяющего применять
автодополнение поля, загрузку данных с сервера в это поле и другую
функциональность, доступную объектам extJs */
editor: new Ext.form.ComboBox({
// Разрешаем автоподсказку данными из выпадающего списка
typeAhead: true,
/* Указываем, что по завершении редактирования надо выполнить
запрос к серверу */
triggerAction: 'all',
store: new Ext.data.SimpleStore({
fields: ['partyValue', 'partyName'],
// Задание массива данных для выпадающего списка
data: [['1','Нет'],['2','Федералисты'],['3','Демократы-
Республиканцы'],['4','Демократы'],['5','Виги'],['6','Республиканцы']]
}),
/* Указываем, что в выпадающем списке будут отражены локальные данные,
а не загруженные с удаленного хоста */
mode: 'local',
Глава 21. Создание редактируемых таблиц 255

displayField: 'partyName',
valueField: 'partyValue',
// Запрещаем отображать выпадающий список до щелчка по этому полю
lazyRender:true,
/* Определяем стилевой класс, который будет использован при отображении
выпадающего списка */
listClass: 'x-combo-list-small'
})
},{ // Дата вступления в должность
header: 'Пост принял',
dataIndex: 'TookOffice',
width: 80,
/* Форматирование даты. Задание функции, форматирующей дату в окне.
Параметром служит формат даты. */
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
editor: new Ext.form.DateField({
/* Строка с форматом даты. Формат может быть изменен
в соответствии с локализацией приложения. */
format: 'm/d/Y'
}),
hidden: false
},{ // Дата отставки
header: 'Пост сдал',
dataIndex: 'LeftOffice',
width: 80,
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
editor: new Ext.form.DateField({
format: 'm/d/Y'
}),
hidden: false
},{ // Доход президента
header: 'Доход',
dataIndex: 'Income',
width: 100,
// Задаем функцию, которая добавит значок доллара к сумме дохода
renderer: function(v){ return '$ ' + v; },
editor: new Ext.form.NumberField({
// Задаем параметры поля, содержащего положительное
// дробное число
allowBlank: false,
allowDecimals: true,
256 Часть IV. Библиотека ExtJS
allowNegative: false,
blankText: '0',
maxLength: 11
})
}]
);
/* Разрешаем сортировку столбцов, для которых сортировка не задана
по умолчанию */
PresidentsColumnModel.defaultSortable= true;
// Создаем панель редактируемой таблицы
PresidentListingEditorGrid = new Ext.grid.EditorGridPanel({
id: 'PresidentListingEditorGrid',
// Указываем объект с данными для отображения
store: PresidentsDataStore,
// Задаем модель отображения столбцов
cm: PresidentsColumnModel,
enableColLock:false,
clicksToEdit:1,
selModel: new Ext.grid.RowSelectionModel({singleSelect:false})
});
// Создание окна для вывода таблицы
PresidentListingWindow = new Ext.Window({
id: 'PresidentListingWindow',
title: 'Президенты США',
// Разрешаем закрывать таблицу щелчком по кнопке с крестиком
closable: true,
width: 700,
height: 350,
plain: true,
layout: 'fit', // Задание компоновки
// Указание объекта, из которого берутся данные для отображения
items: PresidentListingEditorGrid
});
// Сохранение данных в кэш и вывод данных на экран в новом окне
PresidentsDataStore.load();
PresidentListingWindow.show();
// Задаем функцию-обработчик saveThePresident для события
// afteredit - окончанию редактирования поля
PresidentListingEditorGrid.on('afteredit', saveThePresident);
});
Глава 21. Создание редактируемых таблиц 257

Детали конфигурирования объектов в этом сценарии лучше отследить по


подробным комментариям, которыми снабжен код. Здесь же рассмотрим об-
щую логику программы, двигаясь снизу вверх по тексту.
В самом конце программы мы выводим на экран окно с результатом всей пре-
дыдущей работы и пишем, что функция saveThePresident назначается обра-
ботчиком события, происходящего на объекте PresidentListingEditorGrid.
Значит, нам надо понять, что это за объект. Двигаясь вверх, мы понимаем, что
именно этот объект и выводится в окне. Еще выше мы обнаруживаем, что это
панель PresidentListingEditorGrid, содержащая редактируемую таблицу:
PresidentListingEditorGrid = new Ext.grid.EditorGridPanel({
. . . ,
store: PresidentsDataStore,
cm: PresidentsColumnModel,
. . .
});

Данные для таблицы загружаются из кэша, который хранится в объекте


PresidentsDataStore. Данные попадают в кэш в результате запроса, отправ-
ляемого к серверному сценарию database2.php. При формировании кэша от-
вет сервера, приходящий в формате JSON, считывается и преобразуется
в массив литеральных объектов.
Столбцы таблицы организованы таким образом, что можно менять их вид,
более того, можно изменить значение в столбце и записать это значение
в серверную базу данных. Вся эта функциональность определяется объектом
PresidentsColumnModel, в котором задаются все характеристики столбцов.
Задание модели для формирования столбцов занимает самую большую часть
рассматриваемого сценария. Здесь задаются имена столбцов и данные, кото-
рые следует загружать в них. Описываются опции редактирования и форма-
тирования.
Ну и, наконец, добравшись до самого верха программы, мы обнаруживаем
то, о чем уже начали догадываться: функция saveThePresident обработки
события отправляет на сервер запрос, обновляющий базу данных и вносящий
изменения в отредактированные на клиентской стороне поля.
Изучая в дальнейшем возможности редактируемых таблиц в ExtJS, читатель
обнаружит, что эта глава является не полным их описанием, а лишь крат-
ким предисловием к использованию этой интересной и очень перспективной
библиотеки.
Часть V
jQuery
Глава 22

Знакомство с jQuery

jQuery — это JavaScript-библиотека, которая появилась в январе 2006 года


благодаря стараниям Джона Ризига (John Resig) и команды jQuery и стала
быстро завоевывать популярность и общее признание. На сегодняшний день
ее используют Google и Mozilla, Dell и Bank of America, Wordpress, Drupal
и многие другие компании. Библиотека совместима с большинством совре-
менных и популярных браузеров: IE 6.0+, FF 2+, Safari 2.0+, Opera 9.0+.
В рамках этой книги не подразумевается детальное знакомство с JavaScript-
библиотекой jQuery, но для того чтобы понимать и в полной мере использо-
вать ее возможности для работы с AJAX-технологиями, необходимо общее
понимание основ этой библиотеки. И с самого начала мы попробуем понять,
что может дать эта библиотека разработчику.
Библиотека позволяет повысить скорость разработки приложений, сущест-
венно упрощая работу с объектной моделью документа (DOM), созданием
анимации, обработкой событий и конечно предоставляя прекрасные меха-
низмы взаимодействия с AJAX. Но это только лозунги, а что на практике?
А на практике библиотека jQuery почти полностью снимает с разработчика
заботу об обеспечении совместимости программного кода с основными ти-
пами современных браузеров.
Несомненно, очень важным моментом является и тот факт, что с помощью
jQuery довольно просто отделить поведение элементов от структуры HTML-
документа. Вы никогда не вешали обработчик события onclick непосред-
ственно в HTML-разметке? Тогда вам очень повезло. Еще один важный
момент — при использовании jQuery объем кода сокращается в разы!
Стоит отметить, что в библиотеке предусмотрена возможность расширения
функциональности, чем не замедлили воспользоваться многие разработчики.
На момент написания книги количество плагинов к библиотеке jQuery пере-
валило далеко за тысячу.
262 Часть V. jQuery

Аргументов за то, чтобы заинтересоваться jQuery, пожалуй, уже достаточно.


Если интересно — продолжим и попробуем разобраться, как же это работает?
Первое, что делает практически любой jQuery-код, — выбирает элементы
HTML-страницы в объект jQuery, который мы в дальнейшем будем называть
набором элементов, для дальнейших манипуляций ими. Именно поэтому мы
довольно подробно познакомимся не только с функциями ядра библиотеки,
но и с разделом, рассказывающим о селекторах jQuery.
Затем немного познакомимся с основными и наиболее востребованными
в практическом программировании методами, с помощью которых можно
легко манипулировать элементами объектной модели документа (а иначе,
зачем было вообще создавать какие-то наборы?), посвятим некоторое время
изучению событий jQuery и узнаем, как связывать наборы элементов jQuery
c определенными событиями.
И, наконец, уже понимая, как легко можно с помощью библиотеки jQuery
воплощать в жизнь самые интересные задумки, приступим к изучению API
(интерфейса программирования приложений) для работы с AJAX, который
предлагает jQuery.
В заключение мы разберем несколько совершенно реальных примеров, по-
знакомившись через них с возможностью использовать множество написан-
ных для jQuery плагинов.
Не страшно, если пока что-то звучит несколько непонятно — все примеры,
приведенные в книге, очень просты. Разобрав несколько примеров, вы
с удивлением обнаружите, что уже и сами в состоянии написать какой-либо
код, используя библиотеку jQuery.

Установка библиотеки
Практическое знакомство с библиотекой мы начнем с процесса ее установки,
хотя это и звучит несколько громко. Для того чтобы подключить один-
единственный файл, достаточно написать код, приведенный в листинге 22.1.
Листинг 22.1. Подключение библиотеки jQuery
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Подключение библиотеки jQuery</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
Глава 22. Знакомство с jQuery 263

<script type="text/javascript" src="js/jquery-1.2.6.js"></script>


</head>
<body>
</body>
</html>

Как показано в листинге 22.1, библиотека jQuery подключается к нужной стра-


нице внешним файлом. Скачать самую последнюю версию библиотеки можно на
сайте разработчиков по адресу: http://docs.jquery.com/Downloading_jQuery.

Что такое $()?


$() — это псевдоним функции jQuery(). Возвращает специальный объект
JavaScript — объект jQuery, содержащий массив элементов объектной моде-
ли документа (DOM), которые соответствуют указанному селектору. Такой
объект (договоримся называть его набором элементов) имеет большое коли-
чество методов, которые воздействуют на каждый элемент этого набора.
Но давайте на одном примере ближе познакомимся с функцией $(), увидим
своими глазами селектор, узнаем о событии ready и напишем несколько
строк, с которых начинается практически любой код jQuery.
$(document).ready(function () {... })

В этом подзаголовке мы видим функцию $(), которой в качестве селектора


передан объект document. Другими словами, мы создали набор элементов,
который содержит элементы объектной модели документа, и затем связали
этот набор с событием ready(fn), имеющимся в арсенале библиотеки jQuery.
С событием ready(fn) можно связать функцию, которая должна быть вы-
полнена всякий раз, когда объектная модель документа (DOM) готова к об-
ходу и манипуляциям с ее элементами. Лучше пояснить это на простейшем
примере (листинг 22.2).
Листинг 22.2. Использование события ready(fn)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование события ready(fn)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
264 Часть V. jQuery
</head>
<body>
<script type="text/javascript">
<!--
$(document).ready(function(){
alert("Hello, world!");
});
-->
</script>
</body>
</html>

В примере, приведенном в листинге 22.2, в момент готовности объектной


модели документа к работе, мы увидим знакомое сообщение: "Hello, world!".
Это, вероятно, наиболее важная функция, включенная в модуль событий,
т. к. она позволяет существенно уменьшить время отклика Web-приложения.
Это хорошая замена использованию window.onload. Применение этого мето-
да позволит вызывать необходимую функцию непосредственно в момент го-
товности объектной модели документа (DOM) к работе. То есть пользовате-
лю не придется ожидать окончания загрузки каких-либо данных с удаленных
ресурсов (баннеры, счетчики и т. п.), чтобы приступить к работе с вашим
приложением. Вот поэтому практически любой код jQuery и начинается с тех
нескольких строк, которые были приведены в листинге 22.2.
На странице можно использовать сколько угодно конструкций
$(document).ready(). Но в этом случае необходимо учесть, что выполняться
код будет в порядке его следования. И еще один важный момент: необходи-
мо убедиться, что элемент body не имеет обработчика события onload, в про-
тивном случае конструкция $(document).ready() может и не сработать.
Глава 23

Функции ядра jQuery


Операции обхода и преобразования объектной модели документа начина-
ются, как правило, с отыскания необходимых элементов. Если в "класси-
ческом" JavaScript для выбора элемента мы бы использовали, например,
getElementsByTagName или getElementById, то в jQuery используется синтак-
сис, являющийся "гибридом" CSS и регулярных выражений. Говоря о функ-
циях ядра jQuery, мы имеем в виду самые важные и наиболее часто исполь-
зуемые функции библиотеки.
jQuery(expression, [context])
Эта функция принимает строку, содержащую CSS-селектор, который исполь-
зуется, чтобы создать набор элементов. Функция возвращает объект jQuery.
Вокруг этой функции сконцентрирована вся функциональность ядра библио-
теки jQuery. Параметры:
expression — строка, содержащая поисковое выражение;
context (необязательный аргумент) — элементы DOM, объекты document
или jQuery, используемые для поиска в их контексте.
В большинстве случаев, именно эта функция применяется для поиска эле-
мента или набора элементов. По умолчанию, если не передан аргумент
context, jQuery рассматривает элементы DOM в контексте всего HTML-
документа. Если же аргумент context будет определен, например как эле-
мент DOM или объект jQuery, то в соответствии с выражением, будут выби-
раться элементы именно в указанном контексте.
Рассмотрим пример (листинг 23.1).
Листинг 23.1. Использование функции jQuery(expression, [context]) (вариант 1)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"


"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
266 Часть V. jQuery
<head>
<title>Использование функции jQuery(expression, [context])</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
</head>
<body>
<p>Один</p>
<div><p>Два</p></div>
<p>Три</p>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div > p").css("border", "1px solid #999");
});
-->
</script>
</body>
</html>

Код, приведенный в листинге 23.1, отберет все элементы p, являющиеся пря-


мыми потомками элементов div, и установит для них серую рамку толщи-
ной в 1 пиксел. То есть в нашем случае серая рамка будет установлена для
элемента p, в котором содержится текст "Два". Вы наверняка уже узнали
функцию $(). Селектором в данном случае является выражение div > p —
если вы даже немного знакомы с CSS, тут тоже все будет понятно. Дальше,
с помощью одного из многочисленных методов jQuery мы воздействуем на
CSS-свойство border всех элементов, которые были отобраны в набор.
Теперь рассмотрим случай с передачей обоих возможных аргументов (лис-
тинг 23.2).
Листинг 23.2. Использование функции jQuery(expression, [context])
(вариант 2)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"


"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование функции jQuery(expression, [context])</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
Глава 23. Функции ядра jQuery 267

<style type="text/css">
form {
border:1px dotted #00f;
}
</style>
</head>
<body>
<form>
<input type="text" />
<input type="checkbox" />
<input type="file" />
<input type="radio" />
<input type="text" />
</form>
<form>
<input type="text" />
<input type="checkbox" />
<input type="file" />
<input type="radio" />
<input type="text" />
</form>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("input:text", document.forms[0]).css("background-color","#099");
});
-->
</script>
</body>
</html>

Код, приведенный в листинге 23.2, реализует две абсолютно одинаковые


формы, которые содержат элементы input разных типов. Наша задача —
отыскать только поля для ввода текста, причем поиск должен осуществляться
в контексте первой формы на текущей странице. Передавая в первом аргу-
менте expression значение input:text, мы указываем, что именно мы хотим
отыскать на странице. Передавая же во втором аргументе context значение
document.forms[0], мы указываем, где именно в контексте какого элемента
необходимо осуществить поиск. В заключение мы установим для отобран-
ных элементов CSS-свойство background-color. В результате выполнения
268 Часть V. jQuery

этого кода, первый и пятый элементы input только в первой форме будут
иметь бирюзовый цвет фона.

jQuery(html)

Эта функция создает элементы DOM "на лету", используя переданную в ка-
честве аргумента строку, содержащую HTML-код.
Единственный параметр — html. Это строка, содержащая HTML-код для
создания элементов DOM.
Функции можно передавать строку HTML-кода как написанную "руками",
так и созданную автоматически, например шаблонными движками или пла-
гинами. Строку HTML-кода можно передавать в функцию и через AJAX.
Пример представлен в листинге 23.3.

Листинг 23.3. Использование функции jQuery(html) (вариант 1)


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование функции jQuery(html)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
</head>
<body>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("<div><p>Hello, world!</p></div>").appendTo("body");
});
-->
</script>
</body>
</html>

В листинге 23.3 приведен код, который создает элемент div и внутри него
параграф со знакомым выражением "Hello, world!". Затем созданные элемен-
ты добавляются в элемент body. И опять все начинается с функции $().
Только здесь ей передается не селектор, а строка HTML-кода.
Глава 23. Функции ядра jQuery 269

Необходимо отметить некоторые ограничения, существующие при создании


элементов input (листинг 23.4).
Листинг 23.4. Использование функции jQuery(html) (вариант 2)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование функции jQuery(html)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
</head>
<body>
<script type="text/javascript">
<!--
$(document).ready(function(){
// Неправильно:
// $("<input/>").attr("type", "checkbox").appendTo("body");
// Правильно:
$("<input type='checkbox'/>").appendTo("body");
});
-->
</script>
</body>
</html>

В листинге 23.4 приведены примеры правильного и неправильного (строка


закомментирована) кода при создании элементов input. При создании эле-
мента input необходимо указать его тип, в противном случае, попытка уста-
новить тип элемента input после его создания приведет к возникновению
ошибки.
Существует ограничение на передачу в качестве аргумента строки, которая
может содержать слэши, например путь к файлу изображения. В этом случае
необходимо использовать предваряющий "обратный" слэш.
jQuery(elements)

Эта функция "обертывает" функциональность jQuery вокруг одного или мно-


гих элементов DOM. Принимает в качестве аргумента elements элемент или
270 Часть V. jQuery

массив элементов. В качестве аргумента могут быть переданы также и XML-


документы.
Рассмотрим пример (листинг 23.5).
Листинг 23.5. Использование функции jQuery(elements)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование функции jQuery(elements)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
</head>
<body>
<script type="text/javascript">
<!--
$(document).ready(function(){
$(document.body).css("background-color", "#000");
});
-->
</script>
</body>
</html>

Здесь мы устанавливаем значение #000 для CSS-свойства background-color


элемента body, т. е., попросту говоря, устанавливаем черный цвет фона стра-
ницы.
jQuery(callback)

Это — просто короткая форма записи для $(document).ready(). Позволяет


связать выполнение функции с моментом окончания загрузки объектной мо-
дели документа (DOM).

Доступ к объекту jQuery


Библиотека предлагает несколько методов для доступа к элементам объекта
jQuery. Мы разберем только наиболее часто используемые из них.
Глава 23. Функции ядра jQuery 271

each(callback)

Принимает в качестве аргумента функцию, которая будет выполняться в кон-


тексте каждого элемента объекта jQuery. В выполняющуюся функцию в ка-
честве единственного аргумента передается позиция элемента. Все станет
гораздо понятнее, если внимательно изучить пример, приведенный в листин-
ге 23.6. В реальном программировании этот код, конечно, не стоит использо-
вать, но в качестве примера, демонстрирующего возможности использования
метода each(callback), он вполне подходит.
Листинг 23.6. Использование функции each(callback)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование функции each(callback)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style>
body {
border:1px dotted #000;
}
div {
border:1px solid #F00;
text-align:center;
cursor:pointer;
font-weight:bold;
width:300px;
margin-bottom:10px;
}
</style>
</head>
<body>
<div>Элемент №1</div>
<div>Элемент №2</div>
<div>Элемент №3</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
272 Часть V. jQuery
$(document.body).click(function () {
$("div").each(function (i) {
if(i==0) { // Если элемент первый в наборе
// Если CSS-свойство color не имеет значения red
if(this.style.color != "red") {
// Установим ему это значение
this.style.color = "red";
// Если CSS-свойство color имеет значения red
} else {
// Вернем color значение по умолчанию
this.style.color = "";
}
} else if(i==1) { // если элемент второй в наборе
// Если CSS-свойство color не имеет значения blue
if(this.style.color != "blue") {
// Установим ему это значение
this.style.color = "blue";
// Если CSS-свойство color имеет значения blue
} else {
// Вернем color значение по умолчанию
this.style.color = "";
}
} else { // Если элемент третий в наборе
// Если CSS-свойство color не имеет значения green
if(this.style.color != "green") {
// Установим ему это значение
this.style.color = "green";
// Если CSS-свойство color имеет значения green
} else {
// Вернем color значение по умолчанию
this.style.color = "";
}
}
});
});
});
-->
</script>
</body>
</html>
Глава 23. Функции ядра jQuery 273

Итак, мы имеем элемент body, который содержит три элемента div. Элемен-
ты div в свою очередь содержат текст. Шрифт текста имеет по умолчанию
черный цвет. При щелчке мышью в любом месте элемента body будет создан
набор элементов, содержащий все div-элементы на текущей странице, и для
каждого из них последовательно будет выполнена функция, которую мы пе-
редали методу each в аргументе callback. Для первого элемента набора бу-
дет установлен красный цвет шрифта, для второго — синий, для третьего —
зеленый.
length

Собственно, length — это свойство объекта jQuery. Возвращаемое число —


количество элементов в объекте. Пример приведен в листинге 23.7.
Листинг 23.7. Использование свойства length
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование свойства length</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
</head>
<body>
<p class="result"></p>
<span>Элемент SPAN</span>
<div>Элемент DIV</div>
<p>Элемент P</p>
<div>Элемент DIV</div>
<span>Элемент SPAN</span>
<div>Элемент DIV</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
var n = $("div").length;
$("p.result").text('Найдено элементов div - ' + n + ' шт.');
});
274 Часть V. jQuery
-->
</script>
</body>
</html>

В этом примере на странице имеется несколько элементов div, span и p.


Наша задача — подсчитать количество только элементов div. Довольно про-
сто — отбираем все элементы div в объект jQuery и обращаемся к свойству
length. Результат записываем в переменную n, а затем вставляем значение
этой переменной как текст в элемент p, который имеет класс result.
Глава 24

Селекторы jQuery

Как правило, первое, с чего начинается большинство кодов jQuery, — это


выбор элемента или элементов, с которыми и будут происходить дальнейшие
манипуляции. Неграмотный подход к выбору элементов может потрепать
много нервов разработчику. А ключом к грамотному выбору является четкое
понимание селекторов jQuery. Ничего особенно сложного здесь нет, ведь
библиотека следует CSS-синтаксису и даже дополняет его своими методами
выбора элементов. Несколько реже приходится использовать что-то похожее
на регулярные выражения. Подбирая и используя совместно различные типы
селекторов, можно выбирать элементы и наборы элементов практически
с "хирургической" точностью. Поскольку эта тема достаточно важна, мы на
простых примерах рассмотрим большинство селекторов.

Базовые селекторы
Базовые селекторы — наиболее употребляемые на практике, понадобятся
в большинстве случаев.
#id
Этот селектор выбирает единственный элемент, который имеет соответст-
вующее значение атрибута id (листинг 24.1).
Листинг 24.1. Использование селектора #id
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора #id</title>
276 Часть V. jQuery
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
</head>
<body>
<div>Элемент DIV без id</div>
<div id="myDiv">Элемент DIV с id="myDiv"</div>
<div id="otherDiv">Элемент DIV с id="otherDiv"</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("#myDiv").css("border","1px solid red");
});
-->
</script>
</body>
</html>

В этом примере на странице присутствуют три элемента div. Один из них


вообще не имеет id, два других имеют идентификаторы. Нам необходимо
отыскать элемент со значением идентификатора myDiv, что мы с успехом и
делаем. А для наглядности мы устанавливаем для найденного элемента крас-
ную рамку толщиной в 1 пиксел.
Необходимо особо обратить внимание на выбор элементов, идентификаторы
которых имеют специфические символы, такие как точка или квадратные
скобки. Такая ситуация может возникнуть, например, при использовании ка-
ких-либо frameforks, генерирующих значение идентификаторов с помощью
специальных символов. Пример из листинга 24.2 иллюстрирует правильный
и неправильный подходы.
Листинг 24.2. Использование селектора #id со специфическими символами
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора #id со специфическими символами</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
</head>
Глава 24. Селекторы jQuery 277

<body>
<div id="eid.25">div с идентификатором eid.25</div>
<div id="eid.50">div с идентификатором eid.50</div>
<div id="eid[25]">div с идентификатором eid[25]</div>
<div id="eid[50]">div с идентификатором eid[50]</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("#eid.25").css("background-color","#ccc"); // это не работает!
$("#eid\\.50").css("background-color","#ccc"); // а это работает!
$("#eid[25]").css("background-color","#ccc"); // это не работает!
$("#eid\\[50\\]").css("background-color","#ccc"); // а это работает!
});
-->
</script>
</body>
</html>

Если вы воспроизведете этот пример, то сможете убедиться, что серый цвет


фона будет установлен только для второго и четвертого элементов div.
element
Этот селектор выбирает элементы указанного в нем типа (листинг 24.3).
Листинг 24.3. Использование селектора element
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора element</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
</head>
<body>
<span>Элемент SPAN</span>
<div id="myDiv">Элемент DIV</div>
<p class="someclass">Элемент P</p>
<div>Элемент DIV</div>
278 Часть V. jQuery
<em>Элементе EM</em>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div").css("border","1px solid red");
});
-->
</script>
</body>
</html>

В данном примере на странице имеются два элемента div, элементы span, p


и em. Используя селектор element, мы выбираем все элементы div и, чтобы
убедиться в том, что все было сделано правильно, устанавливаем для них
красную рамку в 1 пиксел толщиной. Мы могли бы выбрать любой другой
элемент на странице, указав его тип в селекторе.
.class
Селектор класса в соответствии со своим названием выбирает все элементы,
имеющие соответствующий класс. Пример представлен в листинге 24.4.
Листинг 24.4. Использование селектора .class
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора .class</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
</head>
<body>
<div class="myDiv">Элемент DIV с классом myDiv</div>
<div class="otherDiv">Элемент DIV с классом otherDiv</div>
<div class="myDiv">Элемент DIV с классом myDiv</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$(".myDiv").css("border","1px solid red");
Глава 24. Селекторы jQuery 279

});
-->
</script>
</body>
</html>

Здесь имеем на странице три элемента div. С помощью селектора .class вы-
берем только те из них, которые имеют класс myDiv, и для них установим
рамку красного цвета.

П РИМЕЧАНИЕ
Запись в селекторе значения div.myDiv была бы более грамотной, такой код
отработал бы быстрее. А вот в случае с селектором #id, напротив, правильнее
было бы написать #myDiv, а вовсе не div#myDiv.

*
Селектор "звездочка" выбирает все имеющиеся на странице элементы, вклю-
чая элементы body и head. Пример представлен в листинге 24.5.

Листинг 24.5. Использование селектора *


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора *</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
</head>
<body>
<div class="myDiv">Элемент DIV с классом myDiv</div>
<div class="otherDiv">Элемент DIV с классом otherDiv</div>
<div>Элемент DIV</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("*").css("border","1px solid green");
});
280 Часть V. jQuery
-->
</script>
</body>
</html>

В данном примере мы отыскали все элементы на странице и установили им


рамку... А вот и не угадали — зеленого цвета.

Иерархические селекторы
ancestor descendant

Этот селектор выбирает все элементы, определенные как потомок


(descendant) по отношению к элементу, определенному как предок
(ansector). Звучит несколько запутанно, но на примере из листинга 24.6 все
станет очень понятно.
Листинг 24.6. Использование селектора ansector descendant
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора ansector descendant</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
body {
font-size:14px;
}
form {
border:2px green solid;
padding:2px;
margin:0;
background:#efe;
}
div {
color:red;
}
Глава 24. Селекторы jQuery 281

fieldset {
margin:1px;
padding:3px;
}
</style>
</head>
<body>
<form>
<div>Форма заключена в зеленую рамку</div>
<label>Ребенок:</label>
<input type="text" name="name" />
<fieldset>
<label>Внук:</label>
<input type="text" name="newsletter" />
</fieldset>
</form>
Сестринский элемент по отношению к форме: <input type="text" name="none"
/>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("form input").css("border", "2px dotted brown");
});
-->
</script>
</body>
</html>

Рассмотрим внимательнее код, приведенный в листинге 24.6. Нас интересует


элемент form, а точнее — элементы input внутри него. Первый элемент
input является потомком элемента form, а вот второй input — это уже
"внук" элемента form, и его предком является элемент fieldset. Третий
input вообще за пределами элемента form — это сестринский элемент по от-
ношению к форме. Теперь задача — необходимо отыскать все элементы
input внутри form. Вот тут на помощь и придет селектор ansector
descendant. Посмотрите выделенную часть кода в листинге 24.6 — в селек-
торе мы указываем form input, выбирая, таким образом, только те элементы
input, которые находятся внутри form. Установим для них коричневую рам-
ку в 2 пиксела, чтобы убедиться, что элемент input, находящийся вне формы,
такую рамку не получил.
282 Часть V. jQuery
parent > child

Этот селектор выбирает все элементы, определенные как прямой потомок


(child) по отношению к элементу, определенному как родитель (parent).
Не менее запутанно, чем в предыдущем примере, но также довольно просто,
если разобраться на примере из листинга 24.7.
Листинг 24.7. Использование селектора parent > child

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"


"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора parent > child</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
body {
font-size:14px;
}
form {
border:2px green solid;
padding:2px;
margin:0;
background:#efe;
}
div {
color:red;
}
fieldset {
margin:1px;
padding:3px;
}
</style>
</head>
<body>
<form>
<div>Форма заключена в зеленую рамку</div>
<label>Ребенок:</label>
<input type="text" name="name" />
Глава 24. Селекторы jQuery 283

<fieldset>
<label>Внук:</label>
<input type="text" name="newsletter" />
</fieldset>
</form>
Сестринский элемент по отношению к форме: <input type="text" name="none"
/>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("form > input").css("border", "2px dotted brown");
});
-->
</script>
</body>
</html>

Если вы очень внимательно посмотрите на код, приведенный в листинге 24.7,


то найдете только одно отличие — эта строка в коде выделена. В селекторе,
вместо form input мы записали form > input, применив, таким образом, се-
лектор parent > child. Разметка при этом осталась точно такой же, как
и в предыдущем примере. Это сделано специально, чтобы более наглядно
показать разницу между селекторами. В примере из листинга 24.7 коричне-
вую рамку получит только элемент input, который является прямым потом-
ком элемента form. Элемент input, находящийся внутри fieldset, и тем бо-
лее input, который находится вне элемента form, таких рамок не получат.
prev + next

Этот селектор выбирает все элементы, определенные как следующие (next)


за элементом, который определен как предыдущий (prev). В общем и целом
ясно, но, тем не менее, изучить пример из листинга 24.8 не помешает.
Листинг 24.8. Использование селектора prev + next

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"


"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора prev + next</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
284 Часть V. jQuery
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
body {
font-size:14px;
}
form {
border:2px green solid;
padding:2px;
margin:0;
background:#efe;
}
div {
color:red;
}
fieldset {
margin:1px;
padding:3px;
}
</style>
</head>
<body>
<form>
<div>Форма заключена в зеленую рамку</div>
<label>Ребенок:</label>
<input type="text" name="name" />
<fieldset>
<input type="text" name="newsletter" />
</fieldset>
</form>
<label>Сестринский элемент по отношению к форме:</label><input
type="text" name="none" />
<script type="text/javascript">
<!--
$(document).ready(function(){
$("label + input").css("border", "2px dotted brown")
.attr("value","Label");
});
-->
</script>
</body>
</html>
Глава 24. Селекторы jQuery 285

В коде из листинга 24.8, по сравнению с предыдущим примером, изменилось


не многое. Обратите внимание на элементы label в HTML-разметке. Изме-
нился и JavaScript-код. Теперь мы используем селектор label + input для
того, чтобы отыскать все элементы input, которые следуют сразу за элемен-
том label. Когда эти элементы найдены, мы как обычно установим для них
коричневую рамочку и заодно добавим в атрибут value найденных элементов
input значение "Label".

prev ~ siblings

Этот селектор выбирает все элементы, определенные в siblings и являю-


щиеся сестринскими по отношению к элементу, определенному как prev, но
следующие после него. Тут уж точно все совсем непонятно, но на помощь
придет очередной пример из листинга 24.9.
Листинг 24.9. Использование селектора prev ~ siblings

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"


"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора prev ~ siblings</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
div, span {
display:block;
width:120px;
height:120px;
margin:5px;
padding:5px;
background:#bfa;
float:left;
font-size:14px;
}
div#small {
width:80px;
height:50px;
padding:5px;
286 Часть V. jQuery
font-size:12px;
background:#fab;
}
</style>
</head>
<body>
<div>div (не выбран, поскольку следует перед #prev)</div>
<div id="prev">div#prev - выбор относительно этого элемента</div>
<div>сестринский div</div>
<div>сестринский div <div id="small">Этот div не сестринский</div></div>
<span>span - сестринский элемент, но не div</span>
<div>сестринский div</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("#prev ~ div").css("border", "3px double blue");
});
-->
</script>
</body>
</html>

В листинге 24.9 найдите элемент div, имеющий идентификатор prev. Отно-


сительно него мы постараемся отыскать все сестринские элементы div, сле-
дующие после него, записав в селекторе #prev ~ div. А найденным таким
образом элементам установим двойную рамку синего цвета.

Основные фильтры
Почти все эти фильтры точнее будет назвать селекторами-фильтрами выбора
элементов по их расположению относительно других элементов. С их помо-
щью можно выбирать первые и последние, четные и нечетные элементы,
а также элементы, находящиеся на конкретных позициях.
:first

Этот селектор "отфильтрует" набор элементов, оставив в нем только самый


первый. Пример приведен в листинге 24.10.
Глава 24. Селекторы jQuery 287

Листинг 24.10. Использование селектора :first


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :first</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
td {
color:#00f;
font-weight:bold;
}
</style>
</head>
<body>
<table>
<tr><td>Строка 1</td></tr>
<tr><td>Строка 2</td></tr>
<tr><td>Строка 3</td></tr>
</table>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("tr:first").css("font-style", "italic");
});
-->
</script>
</body>
</html>

Данный код выбирает только первую строку из таблицы и устанавливает для


нее наклонное начертание шрифта.
:last

Единственное отличие от предыдущего селектора в том, что селектор :last


"отфильтрует" набор элементов, оставив в нем только самый последний (лис-
тинг 24.11).
288 Часть V. jQuery

Листинг 24.11. Использование селектора :last


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :last</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
td {
color:#00f;
font-weight:bold;
}
</style>
</head>
<body>
<table>
<tr><td>Строка 1</td></tr>
<tr><td>Строка 2</td></tr>
<tr><td>Строка 3</td></tr>
</table>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("tr:last").css("font-style", "italic");
});
-->
</script>
</body>
</html>

Точно так же, как и в предыдущем примере, в листинге 24.11 мы имеем таб-
лицу, состоящую из трех строк. Только теперь мы выбираем последнюю
строку и устанавливаем для нее наклонное начертание шрифта.
:not(selector)

Этот селектор создаст набор, в который будут включены абсолютно все


элементы кроме указаных в selector. Логически он несколько выпадает
Глава 24. Селекторы jQuery 289

из общего ряда рассматриваемых селекторов, но зато с легкостью позволяет


"инвертировать" набор элементов.

П РИМЕЧАНИЕ
Селектор, а вернее, селектор-фильтр :not, можно применять только к селек-
торам-фильтрам, которые можно опознать по символу двоеточия (:) или квад-
ратной скобки ([). Другие селекторы внутри селектора-фильтра :not исполь-
зовать нельзя.
Рассмотрим пример из листинга 24.12.

Листинг 24.12. Использование селектора :not(selector)


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :not(selector)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
</head>
<body>
<div>
<input type="checkbox" name="a" />
<span>Вера</span>
</div>
<div>
<input type="checkbox" name="b" />
<span>Надежда</span>
</div>
<div>
<input type="checkbox" name="c" checked="checked" />
<span>Любовь</span>
</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("input:not(:checked) + span").css("background-color", "yellow");
$("input").attr("disabled", "disabled");
});
290 Часть V. jQuery
-->
</script>
</body>
</html>

В данном примере мы отыскиваем все невыбранные элементы input (в на-


шем случае это checkbox-элементы) и устанавливаем желтый цвет фона для
расположеных сразу за ними сестринскими элементами span.
П РИМЕЧАНИЕ
Следующий за выделенной строкой код просто запрещает выбор элементов
checkbox, поскольку в простом примере из листинга 24.12 мы не устанавлива-
ем никаких обработчиков событий, связанных с ними.

:even

Этот селектор оставит в наборе только четные элементы, включая и нулевой.


То есть в наборе останутся элементы с индексами 0, 2, 4, ... и т. д. Пример
представлен в листинге 24.13.
Листинг 24.13. Использование селектора :even
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :even</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
table {
background-color:#eee;
border:1px solid #00f;
}
</style>
</head>
<body>
<table>
<tr><td>Строка с индексом #0</td></tr>
<tr><td>Строка с индексом #1</td></tr>
<tr><td>Строка с индексом #2</td></tr>
Глава 24. Селекторы jQuery 291

<tr><td>Строка с индексом #3</td></tr>


</table>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("tr:even").css("background-color", "#bbf");
});
-->
</script>
</body>
</html>

Здесь мы имеем таблицу, состоящую из четырех строк. Для нее установлен


серый цвет фона. Воспользовавшись селектором :even, мы выбираем все
четные строки этой таблицы и устанавливаем их CSS-свойству background-
color значение #bbf.

:odd

Этот селектор оставит в наборе только нечетные элементы. То есть в наборе


останутся элементы с индексами 1, 3, 5,... и т. д. Пример приедставлен в лис-
тинге 24.14.

Листинг 24.14. Использование селектора :odd


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :odd</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
table {
background-color:#eee;
border:1px solid #00f;
}
</style>
</head>
<body>
292 Часть V. jQuery
<table>
<tr><td>Строка с индексом #0</td></tr>
<tr><td>Строка с индексом #1</td></tr>
<tr><td>Строка с индексом #2</td></tr>
<tr><td>Строка с индексом #3</td></tr>
</table>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("tr:odd").css("background-color", "#bbf");
});
-->
</script>
</body>
</html>

Пример из листинга 24.14 практически полностью повторяет предыдущий


пример, за одним исключением — мы делаем все то же самое, только для
нечетных строк таблицы.
:eq(index)

Этот селектор выбирает единственный элемент с указанным индексом (лис-


тинг 24.15).
Листинг 24.15. Использование селектора :eq(index)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :eq(index)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
table {
background-color:#eee;
border:1px solid #00f;
}
</style>
Глава 24. Селекторы jQuery 293

</head>
<body>
<table>
<tr><td>TD #0</td><td>TD #1</td><td>TD #2</td></tr>
<tr><td>TD #3</td><td>TD #4</td><td>TD #5</td></tr>
<tr><td>TD #6</td><td>TD #7</td><td>TD #8</td></tr>
</table>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("td:eq(2)").css("color", "red");
});
-->
</script>
</body>
</html>

Разберем код. У нас есть таблица, состоящая из трех строк и трех столбцов,
т. е. имеется девять элементов td с индексами от 0 до 8. Мы находим элемент
td с индексом 2 и устанавливаем для содержимого этой ячейки таблицы
красный цвет шрифта. Это будет третья ячейка в первой строке.

:gt(index)
Этот селектор выбирает все элементы с индексами большими, чем указан-
ный. Пример представлен в листинге 24.16.

Листинг 24.16. Использование селектора :gt(index)


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :gt(index)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
table {
background-color:#eee;
border:1px solid #00f;
294 Часть V. jQuery
}
</style>
</head>
<body>
<table>
<tr><td>TD #0</td><td>TD #1</td><td>TD #2</td></tr>
<tr><td>TD #3</td><td>TD #4</td><td>TD #5</td></tr>
<tr><td>TD #6</td><td>TD #7</td><td>TD #8</td></tr>
</table>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("td:gt(4)").css("text-decoration", "line-through");
});
-->
</script>
</body>
</html>

В примере из листинга 24.16 таблица осталась такой же, но наша задача из-
менилась. Нам необходимо выбрать все элементы td с индексами более 4
и с помощью CSS-свойств "зачеркнуть" текст, находящийся в этих ячейках таб-
лицы. Что мы с успехом можем проделать, использовав селектор :gt(index).
:lt(index)

Этот селектор выбирает все элементы с индексами меньшими, чем указан-


ный. Пример представлен в листинге 24.17.
Листинг 24.17. Использование селектора :lt(index)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :lt(index)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
table {
Глава 24. Селекторы jQuery 295

background-color:#eee;
border:1px solid #00f;
}
</style>
</head>
<body>
<table>
<tr><td>TD #0</td><td>TD #1</td><td>TD #2</td></tr>
<tr><td>TD #3</td><td>TD #4</td><td>TD #5</td></tr>
<tr><td>TD #6</td><td>TD #7</td><td>TD #8</td></tr>
</table>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("td:lt(5)").css("text-decoration", "line-through");
});
-->
</script>
</body>
</html>

В листинге 24.17 HTML-код остался прежним, но теперь мы легко и просто


выбираем уже элементы td с индексами меньше 5, используя селектор
:lt(index), и так же "зачеркиваем" находящийся в них текст.

:header

Этот селектор выбирает все элементы, которые являются заголовками. На-


пример, элементы h1, h2, h3 и т. д. Пример приведен в листинге 24.18.
Листинг 24.18. Использование селектора :header
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :header</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
296 Часть V. jQuery
body {
font-size: 10px;
font-family: Arial;
}
h1, h2 {
margin: 3px 0;
}
</style>
</head>
<body>
<h1>Заголовок - элемент h1</h1>
<p>Какое-то содержимое в элементе p</p>
<h2>Заголовок - элемент h2</h2>
<p>Какое-то содержимое в элементе p</p>
<script type="text/javascript">
<!--
$(document).ready(function(){
$(":header").css({ background:'#ccc', color:'#00f' });
});
-->
</script>
</body>
</html>

В данном коде имеются два элемента p и элементы h1 и h2. Воспользовав-


шись селектором :header, мы можем выбрать элементы h1 и h2 и установить
для них серый цвет фона и синий цвет шрифта.
:animated

Этот селектор выбирает все элементы, которые в данный момент являются


анимированными. Пример представлен в листинге 24.19.
Листинг 24.19. Использование селектора :animated
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :animated</title>
Глава 24. Селекторы jQuery 297

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />


<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
div {
background-color:yellow;
border:1px solid #AAA;
width:80px;
height:80px;
margin:5px;
float:left;
}
div.colored {
background-color:green;
}
</style>
</head>
<body>
<button id="run">Выполнить</button>
<div></div>
<div id="mover"></div>
<div></div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("#run").click(function(){
$("div:animated").toggleClass("colored");
});
function animateIt() {
$("#mover").slideToggle("slow", animateIt);
}
animateIt();
});
-->
</script>
</body>
</html>

Разберем подробнее пример, приведенный в листинге 24.19. На странице


имеются три элемента div, один из которых с идентификатором mover —
именно он будет играть роль анимированного элемента, который нам необ-
298 Часть V. jQuery

ходимо будет отыскать. Есть также кнопка, по щелчку на которой мы будем


отыскивать анимированный элемент и переключать для него стилевой класс.
Теперь обратим внимание на JavaScript-код. Функция animateIt() связывает
элемент div с идентификатором mover с методом slideToggle() (один из ме-
тодов, реализующий эффекты в jQuery), который в свою очередь вновь вызы-
вает функцию animateIt(), заставляя целевой элемент быть постоянно
в процессе анимации. Но нас больше интересует другая часть кода, которая
будет выполнена по щелчку на кнопке. Указав в селекторе div:animated, мы,
таким образом, отыскиваем нужный нам элемент и по каждому следующему
нажатию кнопки добавляем или удаляем стилевой класс .colored, что при-
водит к изменению цвета этого элемента с желтого на зеленый и обратно.

Фильтры содержимого
Иногда бывает необходимо выбрать элементы, имеющие внутри себя какое-
либо содержимое. Это может быть текст или какой-нибудь HTML-элемент.
С другой стороны, может понадобиться отыскать элементы, не имеющие во-
обще никакого содержимого.

:contains(text)

Этот селектор выберет все элементы, которые содержат указанный текст.


В аргументе text задают строку текста, которую необходимо обнаружить.
Поиск производится с учетом регистра. Пример представлен в листинге 24.20.

Листинг 24.20. Использование селектора :contains(text)


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :contains(text)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
</head>
<body>
<div>John Resig</div>
<div>George Martin</div>
<div>Malcom John Sinclair</div>
<div>J. Ohn</div>
Глава 24. Селекторы jQuery 299

<div>b byJohns a</div>


<script type="text/javascript">
<!--
$(document).ready(function(){
$("div:contains('John')").css("text-decoration", "underline");
});
-->
</script>
</body>
</html>

В примере из листинга 24.20 имеются пять элементов div, каждый из кото-


рых содержит некоторый текст. Указав в селекторе div:contains('John'),
мы отыскиваем только те элементы div, внутри которых встречается иско-
мый текст, и с помощью CSS-свойств подчеркиваем весь текст внутри
элемента. Таким образом, в данном примере будет подчеркнут текст в пер-
вом, третьем и пятом элементах div.
:empty

Этот селектор выберет все элементы, которые не имеют потомков. Если


элемент имеет внутри себя текстовый узел, он тоже расценивается как пото-
мок. Пример представлен в листинге 24.21.
Листинг 24.21. Использование селектора :empty
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :empty</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
td {
text-align:center;
border:1px solid #00f;
}
</style>
</head>
300 Часть V. jQuery
<body>
<table>
<tr><td>TD #0</td><td></td></tr>
<tr><td>TD #2</td><td></td></tr>
<tr><td></td><td>TD #5</td></tr>
</table>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("td:empty").text("Была пустая!")
.css("background", "rgb(255,102,0)");
});
-->
</script>
</body>
</html>

Приведенный код реализует таблицу, состоящую из трех строк и двух


столбцов. Некоторые ячейки этой таблицы пусты. Мы используем селектор
td:empty, чтобы отыскать эти ячейки, вставить в них текст "Была пустая!"
и установить для этих ячеек оранжевый цвет фона с помощью CSS-свойств.
:has(selector)

Этот селектор выберет все элементы, которые содержат внутри себя как ми-
нимум один элемент, указанный в аргументе selector. Пример приведен
в листинге 24.22.
Листинг 24.22. Использование селектора :has(selector)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :has(selector)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
.test {
border: 3px inset red;
Глава 24. Селекторы jQuery 301

}
</style>
</head>
<body>
<div><p>Это текст в параграфе</p></div>
<div>Этот просто текст</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div:has(p)").addClass("test");
});
-->
</script>
</body>
</html>

Код из листинга 24.22 добавляет ко всем элементам div, внутри которых


имеется хотя бы один элемент p, класс .test, т. е. красную рамку.
:parent

Этот селектор выберет все элементы, являющиеся родительскими, т. е.


имеющие внутри себя элементы потомки. Потомками будут считаться также
и текстовые узлы. Пример представлен в листинге 24.23.
Листинг 24.23. Использование селектора :parent
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :parent</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
td {
width:40px;
background:green;
border:1px solid #0ff;
}
302 Часть V. jQuery
</style>
</head>
<body>
<table>
<tr><td>Значение_1</td><td></td></tr>
<tr><td>Значение_2</td><td></td></tr>
</table>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("td:parent").fadeTo(1500, 0.3);
});
-->
</script>
</body>
</html>

В листинге 24.23 с помощью селектора :parent мы отыскиваем все элементы


td, имеющие элементы-потомки, и применяем для них эффект затухания, ис-
пользуя метод fadeTo() (один из методов, реализующий эффекты в jQuery).

Фильтры видимых и невидимых элементов


:hidden

Этот селектор выберет все элементы, которые являются скрытыми, или эле-
менты input, имеющие тип hidden. Пример приведен в листинге 24.24.
Листинг 24.24. Использование селектора :hidden
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :hidden</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
div {
Глава 24. Селекторы jQuery 303

width:70px;
height:40px;
background:#e7f;
margin:5px;
float:left;
}
span {
display:block;
clear:left;
color:#f00;
}
.starthidden {
display:none;
}
</style>
<script type="text/javascript">
<!--
$(document).ready(function(){
// В некоторых браузерах селектор :hidden выберет также
// элементы head, title, script и т. д.
// Чтобы обойти это, селектор нужно применить в контексте body.
$("span:first").text("Всего найдено " +
$(":hidden", document.body).length + " скрытых элементов.");
$("div:hidden").show(3000);
$("span:last").text("из них " + $("input:hidden").length +
" элементов input type=hidden.");
});
-->
</script>
</head>
<body>
<span></span>
<div></div>
<div style="display:none;">Был скрыт!</div>
<div></div>
<div class="starthidden">Был скрыт!</div>
<div></div>
<form>
<input type="hidden" />
304 Часть V. jQuery
<input type="hidden" />
<input type="hidden" />
</form>
<span></span>
</body>
</html>

Данный HTML-код реализует пять элементов div, два из которых скрыты


с помощью CSS-правил, форму, имеющую три элемента input с типом
hidden. Два элемента span служат лишь для того, чтобы вставить в них тек-
сты сообщений.
П РИМЕЧАНИЕ
В некоторых браузерах селектор :hidden выберет также элементы head,
title, script и т. д. Чтобы обойти это, селектор нужно применить в контексте
body.

Итак, воспользовавшись свойством length объекта jQuery, мы для начала


подсчитаем все скрытые элементы, имеющиеся в body, и вставим получен-
ный результат в первый элемент span на странице. В последний элемент span
на странице мы вставим результат подсчета только элементов input, которые
имеют тип hidden. Кроме этого, мы попробуем в деле эффект show() (один
из методов, реализующий эффекты в jQuery) для того, чтобы отобразить
скрытые элементы div в течение трех секунд после готовности DOM к работе.
:visible

Этот селектор выберет все элементы, которые являются видимыми. Пример


приведен в листинге 24.25.
Листинг 24.25. Использование селектора :visible
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :visible</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
div {
Глава 24. Селекторы jQuery 305

width:50px;
height:40px;
margin:5px;
border:3px outset #00f;
float:left;
}
.starthidden {
display:none;
}
</style>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div:visible").click(function () {
$(this).css("background", "yellow");
});
$("button").click(function () {
$("div:hidden").show("fast");
});
});
-->
</script>
</head>
<body>
<button>Показать скрытые</button>
<div></div>
<div class="starthidden"></div>
<div></div>
<div></div>
<div style="display:none;"></div>
</body>
</html>

Итак, на странице имеются пять элементов div, два из которых скрыты с по-
мощью CSS-правил. Элемент button выступает в качестве управляющего
элемента. Щелчок мышью на этом элементе отобразит скрытые ранее эле-
менты div с помощью метода show() (один из методов, реализующий эффек-
ты в jQuery). Для того чтобы убедиться, что селектор :visible работает, мы
напишем еще немного кода. Указав в селекторе div:visible, мы создадим
306 Часть V. jQuery

набор, включающий в себя только элементы div, которые были и являются


видимыми. Назначим этому набору обработчик события click, по которому
будет вызываться простенькая функция. Эта функция должна будет устано-
вить желтый цвет фона для того элемента div, по которому был совершен
щелчок. Обратите внимание, что для элементов div, которые были ранее
скрыты, эта функция не будет выполняться, т. к. эти элементы не будут
включены в набор.

Фильтры атрибутов
При работе с фильтрами атрибутов библиотека jQuery использует синтаксис,
очень похожий на синтаксис регулярных выражений.

[attribute]

Этот селектор выберет все элементы, которые имеют указанный атрибут.


Пример представлен в листинге 24.26.

Листинг 24.26. Использование селектора [attribute]


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора [attribute]</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
div {
width:100px;
height:100px;
margin:5px;
padding:3px;
border:1px solid #00f;
background-color:#39f;
float:left;
cursor:pointer;
}
</style>
</head>
Глава 24. Селекторы jQuery 307

<body>
<div>Элемент div без атрибута id</div>
<div id="test">Элемент div с атрибутом id</div>
<div id="attribute">Элемент div с атрибутом id</div>
<div>Элемент div без атрибута id</div>
<div id="selector">Элемент div с атрибутом id</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div[id]").click(function(){
alert('id = ' + $(this).attr("id"));
});
});
-->
</script>
</body>
</html>

Разберемся, что же мы написали в листинге 24.26. Есть пять элементов div,


из которых только три имеют атрибут id — именно они будут нас интересо-
вать. В селекторе мы указали div[id], создав, таким образом, набор только
из тех элементов div, которые имеют указанный атрибут. Затем назначили
обработчик события click() для этого набора — по щелчоку на элементе
div, имеющему атрибут id, мы будем выводить в alert() значение этого ат-
рибута. Если сделать щелчок на элементе div, не имеющем атрибута id, ни-
чего не произойдет, поскольку эти элементы не были включены в набор.

[attribute=value]

Этот селектор выберет все элементы, которые имеют соответствующее зна-


чение указанного атрибута.

Листинг 24.27. Использование селектора [attribute=value]


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора [attribute=value]</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
308 Часть V. jQuery
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
div {
width:100px;
height:100px;
margin:5px;
padding:3px;
border:1px solid #00f;
background-color:#39f;
float:left;
cursor:pointer;
}
.test {
background-color:#f93;
}
.testing {
background-color:#9f3;
}
</style>
</head>
<body>
<div>Элемент div без атрибута class</div>
<div class="test">Элемент div с атрибутом class=test</div>
<div class="test">Элемент div с атрибутом class=test</div>
<div class="testing">Элемент div с атрибутом class=testing</div>
<div class="test">Элемент div с атрибутом class=test</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div[class='test']").click(function() {
$(this).css("background","#fff");
});
});
-->
</script>
</body>
</html>
Глава 24. Селекторы jQuery 309

В примере, приведенном в листинге 24.27, на странице имеются пять элемен-


тов div, из которых три имеют атрибут class со значением test, один эле-
мент div вообще не имеет атрибута class, и еще один элемент div имеет ат-
рибут class со значением testing. Нашей задачей будет отыскать только те
элементы div, которые имеют атрибут class="test". Сделать это очень лег-
ко, записав в селекторе div[class='test']. А чтобы убедиться в том, что все
сделано правильно, назначим и обработчик события click(), в котором бу-
дем устанавливать белый цвет фона элементу div в том случае, если он имеет
указанный класс и, соответственно, включен в набор.
[attribute!=value]

Этот селектор выберет все элементы, которые удовлетворяют двум прави-


лам: или элемент вообще не имеет указанного атрибута, или такой атрибут
есть, но его значение не соответствует значению, указанному в селекторе.
Пример представлен в листинге 24.28.
Листинг 24.28. Использование селектора [attribute!=value]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора [attribute!=value]</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
div {
width:100px;
height:100px;
margin:5px;
padding:3px;
border:1px solid #00f;
background-color:#39f;
float:left;
cursor:pointer;
}
.test {
background-color:#f93;
}
310 Часть V. jQuery
.testing {
background-color:#9f3;
}
</style>
</head>
<body>
<div>Элемент div без атрибута class</div>
<div class="test">Элемент div с атрибутом class=test</div>
<div class="test">Элемент div с атрибутом class=test</div>
<div class="testing">Элемент div с атрибутом class=testing</div>
<div class="test">Элемент div с атрибутом class=test</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div[class!='test']").click(function() {
$(this).css("background","#fff");
});
});
-->
</script>
</body>
</html>

Код в листинге 24.28 полностью повторяет код из предыдущего примера.


Разница в JavaScript-коде, а точнее в селекторе. Теперь мы записали
div[class!='test'], и, соответственно, в набор будут отобраны два из пяти
элементов div. Первый — вообще не имеющий атрибута class, второй —
с атрибутом class='testing'. Именно для них будет теперь устанавливаться
белый цвет фона при щелчке.
[attribute^=value]

Этот селектор выберет все элементы, соответствующий атрибут которых на-


чинается с указанного значения. Рассмотрим пример из листинга 24.29.
Листинг 24.29. Использование селектора [attribute^=value]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
Глава 24. Селекторы jQuery 311

<head>
<title>Использование селектора [attribute^=value]</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
div {
width:100px;
height:100px;
margin:5px;
padding:3px;
border:1px solid #00f;
background-color:#39f;
float:left;
cursor:pointer;
}
.test {
background-color:#f93;
}
.testing {
background-color:#9f3;
}
</style>
</head>
<body>
<div>Элемент div без атрибута class</div>
<div class="test">Элемент div с атрибутом class=test</div>
<div class="test">Элемент div с атрибутом class=test</div>
<div class="testing">Элемент div с атрибутом class=testing</div>
<div class="test">Элемент div с атрибутом class=test</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div[class^='test']").click(function() {
$(this).css("background","#fff");
});
});
-->
</script>
</body>
</html>
312 Часть V. jQuery

HTML-код в листинге 24.29 оставляем точно таким же, как и в предыдущих


примерах. Но теперь мы указываем в селекторе div[class^='test'], чтобы
выбрать все элементы div, значение атрибута class которых начинается
с test, и только для них будем устанавливать белый цвет фона, если будет
совершен щелчок по соответствующему элементу. Какие элементы div полу-
чат в итоге белый цвет фона, догадались? Верно — все, кроме первого.

[attribute$=value]

Этот селектор выберет все элементы, соответствующий атрибут которых за-


канчивается указанным значением. Пример приведен в листинге 24.30.

Листинг 24.30. Использование селектора [attribute$=value]


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора [attribute$=value]</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
div {
width:100px;
height:100px;
margin:5px;
padding:3px;
border:1px solid #00f;
background-color:#39f;
float:left;
cursor:pointer;
}
.test {
background-color:#f93;
}
.testing {
background-color:#9f3;
}
</style>
</head>
Глава 24. Селекторы jQuery 313

<body>
<div>Элемент div без атрибута class</div>
<div class="test">Элемент div с атрибутом class=test</div>
<div class="test">Элемент div с атрибутом class=test</div>
<div class="testing">Элемент div с атрибутом class=testing</div>
<div class="test">Элемент div с атрибутом class=test</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div[class$='est']").click(function() {
$(this).css("background","#fff");
});
});
-->
</script>
</body>
</html>

И вновь оставим без изменений HTML-код, но поменяем правило для выбора


элементов в селекторе, записав div[class$='est'], чтобы выбрать все эле-
менты, атрибут class которых заканчивается последовательностью знаков
est. Теперь по щелчку на элементах div белый цвет фона будет установлен
на втором, третьем и пятом элементах.
[attribute*=value]

Этот селектор выберет все элементы, соответствующий атрибут которых со-


держит указанное значение. Пример приведен в листинге 24.31.
Листинг 24.31. Использование селектора [attribute*=value]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора [attribute*=value]</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
div {
314 Часть V. jQuery
width:100px;
height:100px;
margin:5px;
padding:3px;
border:1px solid #00f;
background-color:#39f;
float:left;
cursor:pointer;
}
em {
font-weight:bold;
color:#fc9;
}
</style>
</head>
<body>
<div>Элемент div без атрибута class</div>
<div class="bigtest">Элемент div с атрибутом
class=big<em>test</em></div>
<div class="testsmall">Элемент div с атрибутом
class=<em>test</em>small</div>
<div class="easy-testing">Элемент div с атрибутом class=easy-
<em>test</em>ing</div>
<div class="test">Элемент div с атрибутом class=<em>test</em></div>
<div class="other">Элемент div с атрибутом class=other</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div[class*='test']").click(function() {
$(this).css("background","#fff");
});
});
-->
</script>
</body>
</html>

Код в листинге 24.31 немного изменен по сравнению с предыдущими примера-


ми. Мы добавили еще один элемент div и немного поменяли имена классов для
них, чтобы иметь возможность показать все вероятные варианты использования
Глава 24. Селекторы jQuery 315

селектора. Итак, если в селекторе записать div[class*='test'], то будут выбра-


ны все элементы div, значение атрибута class которых содержит последова-
тельность знаков test. Причем совершенно неважно, в каком месте встретится
эта последовательность: в начале, в конце или в середине названия класса. В дан-
ном примере белый цвет фона при щелчке на элементе div будет установлен для
элементов со второго по пятый включительно.

[attributeFilter1] [attributeFilter2] ... [attributeFilterN]

Этот селектор выберет элемент или элементы, которые удовлетворяют всем


имеющимся фильтрам атрибутов. Пример представлен в листинге 24.32.

Листинг 24.32. Использование селектора


[attributeFilter1][attributeFilter2]...[attributeFilterN]

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"


"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора [attributeFil-
ter1][attributeFilter2]...[attributeFilterN]</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
div {
width:100px;
height:100px;
margin:5px;
padding:3px;
border:1px solid #00f;
background-color:#39f;
float:left;
cursor:pointer;
}
</style>
</head>
<body>
<div id="first">Элемент div id=first</div>
<div id="second" class="test">Элемент div id=second class=bigtest</div>
<div class="test">Элемент div class=test</div>
<div class="easy-testing">Элемент div class=easy-testing</div>
316 Часть V. jQuery
<div id="fifth" class="test">Элемент div id=fifth class=test</div>
<div class="other">Элемент div class=other</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div[id^='fi'][class='test']").click(function() {
$(this).css("background","#fff");
});
});
-->
</script>
</body>
</html>

В данном примере имеется шесть элементов div. Пусть нам необходимо оты-
скать только такие элементы, атрибут id которых начинается с последова-
тельности знаков 'fi', а такому правилу соответствуют два элемента — пер-
вый и пятый, но при этом элемент должен иметь еще и атрибут class со
значением test, а такому общему правилу будет соответствовать уже только
один элемент div — пятый. И лишь он, при щелчке по нему мышью, получит
белый цвет фона.

Фильтры потомков
Эти селекторы-фильтры позволяют отыскивать элементы на основе отноше-
ний "родитель — потомок".
:nth-child(index/even/odd/equation)

Этот селектор выбирает элементы-потомки по индексу (index), четные (even)


или нечетные (odd) и даже по формуле, определяющей шаг, с которым надо
отбирать элементы, например первый, четвертый, седьмой и т. д. (equation)
для каждого элемента-родителя. Здесь ключевые слова — "для каждого эле-
мента-родителя". Понять логику работы этого селектора проще на примере,
приведенном в листинге 24.33.
Листинг 24.33. Использование селектора :nth-child(index/even/odd/equation)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
Глава 24. Селекторы jQuery 317

<title>Использование селектора :nth-child(index/even/odd/equation)</title>


<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
button {
display:block;
font-size:12px;
width:100px;
}
div {
float:left;
margin:10px;
font-size:10px;
border:1px solid #000;
}
span {
color:#00f;
font-size:18px;
}
#inner {
color:#f00;
}
td {
width:50px;
font-size:14px;
text-align:center;
}
</style>
</head>
<body>
<div>
<button>:nth-child(even)</button>
<button>:nth-child(odd)</button>
<button>:nth-child(3n)</button>
<button>:nth-child(2)</button>
</div>
<div>
<button>:nth-child(3n+1)</button>
<button>:nth-child(3n+2)</button>
<button>:even</button>
<button>:odd</button>
318 Часть V. jQuery
</div>
<div>
<table>
<tr><td>John</td></tr>
<tr><td>Karl</td></tr>
<tr><td>Brandon</td></tr>
<tr><td>Benjamin</td></tr>
</table>
</div>
<div>
<table>
<tr><td>Sam</td></tr>
</table>
</div>
<div>
<table>
<tr><td>Glen</td></tr>
<tr><td>Tane</td></tr>
<tr><td>Ralph</td></tr>
<tr><td>David</td></tr>
<tr><td>Mike</td></tr>
<tr><td>Dan</td></tr>
</table>
</div>
<span>tr<span id="inner"></span></span>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("button").click(function () {
var str = $(this).text();
$("tr").css("background", "#fff");
$("tr" + str).css("background", "#f00");
$("#inner").text(str);
});
});
-->
</script>
</body>
</html>
Глава 24. Селекторы jQuery 319

Разберем пример довольно подробно. Сначала обратим внимание на кноп-


ки — элементы button содержат текст, который является каким-либо из ва-
риантов использования селектора. Следом идут три "подопытных" таблицы.
Все они состоят только из одного столбца, но имеют разное количество
строк. Есть еще элемент span с идентификатором inner — он тоже нам при-
годится. Перейдем к рассмотрению JavaScript-кода. Он представляет собой
обработчик события click() для элементов button. Что же будет сделано
при щелчке на какой-либо кнопке? Сначала в переменную str будет записан
текст, который содержится в выбранном элементе button. Следующая строка
кода выберет все элементы tr на странице и установит для них белый цвет
фона. Самое главное в следующей строке — здесь селектор формируется из
названия элемента tr и к нему добавляется тот текст, который ранее был со-
хранен в переменной str, а поскольку текст мы получаем из элементов
button, то имеем возможность наблюдать работу селектора в разных вариан-
тах, просто щелкая по соответствующим кнопкам. Выбранные в соответст-
вии с селектором строки таблиц будут иметь красный цвет фона. А какой
именно вариант селектора мы используем, видно в элементе #inner, куда мы
тоже вставляем соответствующий текст.
Стоит заметить, что кнопки :even и :odd добавлены в пример специально для
того, чтобы помочь лучше уяснить разницу между использованием селекто-
ров, например :nth-child(even) и просто :even. При щелчке на кнопке
:even будут выбраны все четные элементы tr независимо от принадлежно-
сти к какой-либо таблице, т. е. красный цвет фона будет установлен для яче-
ек: "John", "Brandon", "Sam", "Tane", "David" и "Dan". А вот при щелчке
на кнопке :nth-child(even) — для ячеек "Karl", "Benjamin", "Tane", "David"
и "Dan", т. е. тоже четные строки, но отсчет будет начинаться каждый раз
с новой таблицы.

:first-child

Этот селектор выбирает все элементы, являющиеся первыми потомками сво-


его родителя. Рассмотрим пример из листинга 24.34.

Листинг 24.34. Использование селектора :first-child


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :first-child</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
320 Часть V. jQuery
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
span {
color:#008;
}
</style>
</head>
<body>
<div>
<span>John,</span>
<span>Karl,</span>
<span>Brandon</span>
</div>
<div>
<span>Glen,</span>
<span>Tane,</span>
<span>Ralph</span>
</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div span:first-child").css("text-decoration", "underline");
});
-->
</script>
</body>
</html>

Это очень простенький пример, демонстрирующий работу селектора


:first-child. Собственно, даже название селектора вполне "говорящее" —
будут выбраны элементы span, которые являются потомками элементов div,
причем его первыми потомками. Текст, находящийся в выбранных элемен-
тах, будет подчеркнут. Таким образом, "подчеркнутыми" у нас окажутся
"John" и "Glen".

:last-child

Этот селектор выбирает все элементы, являющиеся последними потомками


своего родителя. Пример представлен в листинге 24.35.
Глава 24. Селекторы jQuery 321

Листинг 24.35. Использование селектора :last-child


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :last-child</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
span {
color:#008;
}
</style>
</head>
<body>
<div>
<span>John,</span>
<span>Karl,</span>
<span>Brandon</span>
</div>
<div>
<span>Glen,</span>
<span>Tane,</span>
<span>Ralph</span>
</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div span:last-child").css("text-decoration", "underline");
});
-->
</script>
</body>
</html>

Здесь мы используем тот же самый код, что и в предыдущем примере, только


с помощью селектора :last-child выберем элементы span, которые явля-
ются потомками элементов div, но теперь уже его последними потомками.
322 Часть V. jQuery

Точно так же подчеркнем текст, находящийся в выбранных элементах.


В этом случае "подчеркнутыми" у нас окажутся "Brandon" и "Ralph".
:only-child

Этот селектор выбирает все элементы, являющиеся единственными потом-


ками своего родителя. Пример приведен в листинге 24.36.
Листинг 24.36. Использование селектора :only-child
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селектора :only-child</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.2.6.js"></script>
<style type="text/css">
div {
width:100px;
height:80px;
margin:5px;
float:left;
background:#b9e;
}
</style>
</head>
<body>
<div>
<button>Sibling</button>
<button>Sibling</button>
</div>
<div>
<button>Sibling</button>
</div>
<div>
None
</div>
<div>
Глава 24. Селекторы jQuery 323

<button>Sibling</button>
<button>Sibling</button>
<button>Sibling</button>
</div>
<div>
<button>Sibling</button>
</div>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("div button:only-child").text("Alone")
.css("border", "2px solid #00f");
});
-->
</script>
</body>
</html>

Данный код описывает пять элементов div, четыре из которых имеют разное
количество элементов-потомков, а один таких элементов не имеет. С помо-
щью селектора :only-child мы выберем только те элементы, которые явля-
ются единственными потомками своего элемента-родителя div, вставим
в них текст "Alone" и установим рамку. Нашим условиям будут удовлетво-
рять только элементы button внутри второго и пятого элементов div.

Селекторы в формах
Селекторы-фильтры предназначены для точного отбора необходимых эле-
ментов внутри форм. После краткого описания назначения каждого фильтра
мы познакомимся с их возможностями на единственном примере:
:input — выбирает все элементы input, а также элементы select,
textarea и button;
:text — выбирает все элементы input, имеющие тип text;
:password — выбирает все элементы input с типом password;
:radio — выбирает все элементы input с типом radio;
:checkbox — выбирает все элементы input с типом checkbox;
:submit — выбирает все элементы input с типом submit;
:image — выбирает все элементы input с типом image;
324 Часть V. jQuery

:reset — выбирает все элементы input с типом reset;


:button — выбирает все элементы input с типом button;
:file — выбирает все элементы input с типом file;
:hidden — выбирает все элементы input с типом hidden.
Работу всех этих селекторов удобнее разобрать на одном примере, приведен-
ном в листинге 24.37.

Листинг 24.37. Использование селекторов форм


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<title>Использование селекторов форм</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jque