Сложные интерфейсы на javascript вместе c Yahoo UI. Часть 4

YUI по праву заслужила право называться одной из самых лучших javascript-библиотек. Она отлично подходит в том случае, если вам нужно создать сложный интерфейс веб-страницы: мы можем проектировать внешний вид приложения из таких "кубиков", как меню, таблицы, деревья, наборы закладок. Внешний вид интерфейса получается унифицированным с тем, к которому привык пользователь, работая с windows, а стилевые возможности css позволяют придать интерфейсу свою изюминку.

В прошлый раз я подробно рассказал о том, как использовать компонент "меню", мы научились работать с контекстным меню и гибко управлять внешним видом его пунктов. Сегодняшний же материал будет посвящен семейству компонентов, предназначенных играть роль "основы", "каркаса", "контейнера" для размещения других элементов управления: так мы познакомимся с набором закладок, попробуем создать диалоговое окно и научимся пользоваться всплывающими подсказками. В библиотеке YUI особое место занимает модуль Module. Хотя мы никогда не будем пользоваться этим элементом интерфейса напрямую, но, раз он лежит в основе почти всех остальных визуальных компонентов, то сказать пару слов о нем стоит. Подключение модуля container выполняется автоматически при подключении любого сложного компонента (например, диалогового окна). Либо можно выполнить подключение явно (так же, как и в прошлых примерах, я использую для загрузки YUI-модулей возможности компонента YUI Loader):
loader = new YAHOO.util.YUILoader();
// загружаем модуль container и все нужное для его работы
loader.require(["container"]);
loader.loadOptional = true;
loader.base = 'js/';
loader.filter = 'DEBUG';
loader.insert({ onSuccess: loadCompleted});

Функция loadCompleted будет вызвана тогда, когда модуль container будет загружен и готов к работе. Сделать я хочу "наисложнейшую" вещь: просто спрятать некоторый фрагмент исходной html-страницы — например, этот:
<div id="block">
<div class="hd">Header</div>
<div class="bd">Content</div>
<div class="ft">Footer</div>
</div>

Обратите внимание на то, что каждому из тегов div, входящих в состав "block", назначены специальные имена css-классов. Считается, что контейнер состоит из трех частей: заголовок (hd — header), "тело" контейнера (bd — body) и "подвал" (ft — footer). Чтобы поместить эти четыре тега внутрь Module, я делаю так:
m = new YAHOO.widget.Module("block");
m.render ();
m.hide ();

В примере выше я создал объект Module, передал в качестве параметра его конструктору имя html-элемента. Затем я выполнил инициализацию (render) компонента, и последним шагом функция hide спрятала блок. Для того, чтобы его отобразить, например, по нажатию на кнопку, используется функция с говорящим именем show(). Откровенно говоря, большой пользы от такого кода нет: спрятать блок или показать — не слишком важная задача и решается гораздо проще без использования yui. Гораздо интереснее познакомиться с классом Overlay (этот класс — наследник от Module). Overlay умеет позиционироваться вне основного потока элементов страницы. Например, когда мы будем разбираться с тем, как создать диалоговое окно, которое можно было бы перетаскивать мышью за его заголовок, то возможности Overlay нам пригодятся (в действительности класс диалоговых окон является наследником от Overlay. А пока парочка простых примеров. Для каждого из них я использую тот же фрагмент html с четырьмя блоками div, что и в предшествующем примере. При создании Overlay я не только указываю имя блока div с его информационным наполнением, но и вторым параметром конструктора задаю список переменных, управляющих особенностями отображения Overlay на экране:

m = new YAHOO.widget.Overlay("block", {fixedcenter: true, width: 200, height: 200});

Например, так я задал размеры "block" в виде квадрата со стороной 200 px и позиционировал его точно посередине страницы. Причем это центрирование будет сохраняться неизменным при любом размере страницы или ее скроллинге. А так я указал абсолютные координаты на html-странице, где должен быть размещен компонент Overlay:

m = new YAHOO.widget.Overlay("block", {xy: [100, 100], width: 200, height: 200});

Возможно, вам пригодится такая функция Overlay, как относительное позиционирование. В этом случае я должен задать при создании Overlay сведения о другом html-элементе, играющем роль "якоря" — например, так я сказал, что блок Overlay должен прилипнуть своим левым верхним углом к нижнему правому углу блока "якоря" ("tl" расшифровывается как "top left", а "br" — как "bottom right"):

m = new YAHOO.widget.Overlay("block", {context:["port","tl","br"], width: 200, height: 200});

А что еще можно делать с Module и Overlay? По большему счету, ничего. Однако, если вы решили, что эти компоненты бесполезны, то напомню еще раз: они лежат в основе многих визуальных компонентов. Кроме того, знание "внутренностей" Module (представление его содержимого в виде трех областей) позволит точнее настраивать внешний вид компонентов. Теперь же рассмотрим что-то более "визуально богатое" — компонент плавающей панели. Он является аналогом привычного нам диалогового окна windows (которым нас о чем-то извещают или спрашивают). Окно поддерживает возможности перетаскивания в пределах экрана, есть средства настройки внешнего вида (скины) окна и где будут располагаться управляющие им кнопки — например, можно разместить кнопку закрытия окна ("крестик") не в правом верхнем углу, а в левом верхнем.

m = new YAHOO.widget.Panel("block", {context:["port","tl","br"], width: 200, height: 100, draggable:true, close:true, constraintoviewport: true, modal: true});
m.render ();

Внешний вид панели показан на рис. 1. Вторым параметром конструктора Panel, помимо уже знакомых нам переменных, я указал draggable — можно или нет перетаскивать панель за ее заголовок, close — будет ли отображаться "крестик" закрытия окна. Параметр constraintoviewport задает ограничение и не позволяет пользователю переместить панель за пределы окна браузера. Очень приятна функция имитации модального диалогового окна (параметр modal). В этом режиме изображение самого окна страницы меняет фоновый цвет на серый, также блокируется доступ к ее содержимому. Хотя созданная шагом ранее панелька выглядит очень неплохо со стилем по умолчанию, но я попробую добавить "немного красок" и изменить ее оформление. Для начала рассмотрим, во что превратились исходные четыре div-блока:
<div style="visibility: inherit; width: 200px; height: 100px;" class="yui-module yui-overlay yui-panel" id="block">
<div id="block_h" style="cursor: move;" class="hd">Header</div>
<div class="bd">Content</div>
<div class="ft">Footer</div>
<span class="container-close"> </span>
</div>
<div class="underlay" />
</div>

Для тега div с идентификатором "block" добавились новый css-стиль yui-panel — его, равно как и другие стили, вы можете найти в файле container/assets/skins/sam/container.css (здесь "sam" — название скина компонента). Очевидно, что при значительных переделках внешнего вида компонента лучше всего создать новый подкаталог стилей (новый скин). В дополнение к созданным нами четырем тегам div yui поместил внутрь панели еще один тег div с классом underlay — он будет играть роль обрамляющей "окошко" панели тени. Также был добавлен тег span с классом "container- close" — его назначение очевидно — это кнопка закрытия панели. Естественно, вы можете назначить для этого стиля "container-close" такие координаты позиции, что кнопка будет размещена не в правом верхнем углу, а, например, в левом верхнем (в стиле Macintosh). Содержимым панели (точнее, областей hd, bd, ft) может быть не только текст, как в примере выше, но и произвольное содержимое — например, картинка (см. рис. 2):

<div class="hd"> Header with pic <img src="diskget_sm.gif" border="0" /> </div>

Интересный вопрос о том, как узнать, что диалоговое окно было показано или спрятано. Все компоненты библиотеки YUI используют унифицированную методику подписки на события. К примеру, открываем справку по компоненту Panel, видим, что в списке зарегистрированных событий есть событие "showEvent" и назначаем функцию-обработчик так:

// подписка на показ окна
m.showEvent.subscribe (onShow);
// и подписка на событие "окно спрятано"
m.hideEvent.subscribe (onHide);
function onShow (){ alert ('show'); }

Проектируя интерфейс веб-приложения, можно столкнуться с проблемой, когда содержимое всплывающего окна сообщения формируется динамически, и мы не можем заранее точно узнать, какой должен быть размер окна панели. Так, в ранее показанных примерах создания Panel я явно указывал размер окна по ширине и высоте — и это нехорошо. И хотя сам класс Panel'и не обладает требуемой функциональностью, но в состав библиотеки YUI входит еще один визуальный компонент Resizer, назначение которого — "обернуть" произвольный фрагмент страницы (текст, картинку) и добавить в правый нижний угол активную зону, которую можно "тягать" мышкой и тем самым изменять размер окна:

// создаем плавающую панель
m = new YAHOO.widget.Panel("random", {width: 200, height: 100, draggable:true, close:true, constraintoviewport: true, modal: false}); m.render (document.body);
// и оборачиваем ее компонентом Resize
r = new YAHOO.util.Resize('random', {
handles: ['br'], ratio: true,
minWidth: 200, minHeight: 100,
status: true});

В примере выше я сначала создал панель (идентификатор html-элемента "pane"). Затем при вызове конструктора объекта Resize первым параметром указал идентификатор "random" (какой компонент нужно наделить способностью к изменению размера). Второй же параметр конструктора — ассоциативный массив с конфигурационными параметрами. Так, параметры minWidth и minHeight задают минимальные размеры окна Resizer'а. Параметр "handles" равен массиву строк, каждая из которых кодирует название угла окна панели, к которой будет добавлена "активный уголок" (например, "br" — правый нижний). Параметр "status" приводит к тому, что при изменении размера панели возле активного уголка будет показываться всплывающая подсказка с текущим размером панели (см. рис. 3). Изначально вы можете изменять размеры окна панели произвольным образом, вытягивая или растягивая его. Имеет смысл наложить ограничение — так, чтобы изменение размера не приводило к искажению пропорций — за это отвечает параметр ratio. Естественно, для того, чтобы компонент Resize был найден YUI, нам необходимо подправить код загрузки модулей:

loader = new YAHOO.util.YUILoader();
// загружаем в дополнение к модулю container еще и модуль resize
loader.require(["container", "resize"]);
loader.loadOptional = true;
loader.base = 'js/';
loader.insert({ onSuccess: loadCompleted});

Завершая рассмотрение класса Panel, я упомяну о том, как можно создать диалоговое окно целиком только с помощью javascript, без предварительной html-разметки. Для Panel, равно как и для любого другого компонента, основанного на классе Module, есть методы, изменяющие содержимое каждой из трех его частей (setHeader, setBody, setFooter). Также обратите внимание в следующем примере, что первый параметр для конструктора Panel равен "random". В исходном html-документе нет тега с таким значением идентификатора, поэтому блок div будет создан автоматически. При вызове метода render я должен обязательно передать ссылку на тот элемент страницы, который будет играть роль контейнера для панели. И последнее: при вызове методов setHeader, setBody, setFooter я могу передавать не только "простой текст", но и произвольный html-код:
m = new YAHOO.widget.Panel("random", {width: 200, height: 100, draggable:true, close:true, constraintoviewport: true});
m.setHeader("Header");
m.setBody("Content of panel");
m.setFooter("<b>footer for panel</b>");
m.render (document.body);

Как только я сказал, что содержимым любой из областей окна панели может быть произвольный html-текст, самое время задаться вопросом: можно ли разместить на панели кнопки, падающие списки, превратив тем самым панель в настоящее диалоговое окно. Да можно и более того: в YUI уже предусмотрели набор инструментов, автоматизирующих создание диалогового окна. В частности, есть два класса-компонента Dialog и SimpleDialog, содержащих средства для создания типовых диалоговых окон, с набором кнопок (OK, CANCEL), размещенных внизу окна, с иконкой типа сообщения (например, диалоговое окно с сообщением об ошибке будет иметь отличный внешний вид от окошка сообщения о подсказке). В следующем примере я создал html-заготовку содержимого диалогового окна (без области footer, т.к. ее содержимое в любом случае будет замещено набором управляющих диалогом кнопок):

<div id="block">
<div class="hd"> Header with pic <img src="diskget_sm.gif" border="0" /> </div>
<div class="bd"> Information Message</div>
</div>

Теперь я должен подправить код загрузчика модулей: мне для работы обязательно нужен модуль "container", а для того, чтобы кнопки на созданной YUI заготовке диалогового окна имели предопределенное стилевое оформление (см. рис. 4) я подключил модуль "button". Завершив подготовку, пора создать само диалоговое окно:

// создаем диалоговое окно
d = new YAHOO.widget.SimpleDialog("block", { width : "400px",
icon: YAHOO.widget.SimpleDialog.ICON_INFO,
fixedcenter : true, visible : false, constraintoviewport : true, buttons : [ { text:"Accept", handler:onAccept}, { text:"Discard",
handler:onDiscard, isDefault:true} ] } );
d.render ();// параметра нет, т.к. содержимое диалога уже определено в теле html-страницы
// и теперь показываем окошко диалога на экране
d.show ();
// а вот пример функции обработчика события "нажатие кнопки"
function onAccept (){
alert ('onAccept'); }

Вызывая конструктор класса SimpleDialog, я передаю два параметра: идентификатор html-блока страницы с информацией (если такого блока нет, то он будет автоматически создан). Второй же параметр конструктора перечисляет конфигурационные параметры для диалога. Часть из них нам знакома по компоненту Module и Overlay (fixedcenter, visible, constraintoviewport). Специфическим для Dialog является параметр "buttons". Это массив объектов, описывающих кнопки диалогового окна. Количество кнопок может быть любым, и каждая из них определяется text'ом (надписью), handler'ом (функцией — обработчиком события) и какая из них будет выбрана по умолчанию (isDefault) — визуально на рис. 4 эта кнопка "Discard" имеет особое стилевое оформление. Кроме показанной в примере константы "YAHOO.widget.SimpleDialog.ICON_INFO", задающей для диалога соответствующее изображение картинки-иконки, можно использовать константы ICON_BLOCK, ICON_WARN, ICON_HELP, ICON_TIP и ICON_ALARM. При динамическом конструировании диалогового окна имеет смысл указать значение текстовой надписи (body) в качестве еще одного параметра конструктора SimpleDialog (text), а не создавать в теле html-страницы теги-шаблоны. Предположим, что вы создали некоторую форму, не забыв указать тегу "form" значение атрибута "action", т.е. адрес некоторого php-скрипта, обрабатывающего содержимое формы. Естественно, что форма была наполнена текстовыми полями, падающими списками select и радиокнопками. И тут возникает самый главный вопрос: что с этим делать? Как данные, введенные клиентом в форму, послать на сервер? Прежде всего, нам нужно какой-нибудь из кнопок назначить специальную функцию — обработчик события (я заменил код функции, обрабатывающей нажатие на кнопку "Accept"):

function onAccept (){
d.submit ();
}
В этом примере переменная d хранит ссылку на созданный ранее объект SimpleDialog. Давайте попробуем, что происходит по нажатию на кнопку? Да ничего: форма исчезает, но данные никуда не отправляются. Дело в том, что разработчики YUI решили "научить" свою форму отправлять данные как синхронно (т.е. страница перезагружается), так и асинхронно (данные отправляются с помощью ajax, а сама страница не перезагружается). Однако по умолчанию режим работы формы "игнорировать", и его нужно изменить. Для этого при вызове конструктора класса SimpleDialog нужно передать еще один конфигурационный параметр — postmethod. Его значение должно быть либо "form" (синхронная отправка), либо "async" — для асинхронной. Режим же "none" говорит, что YUI ничего не делает с содержимым формы, и мы должны самостоятельно обработать данные внутри функции onAccept. В практике веб-разработки требуется, чтобы перед тем, как форма уходит на сервер, обязательно проверить правильность ее заполнения. Например, предположив, что в форме есть текстовое поле с именем (не id — просто name) "fio", то я могу назначить функцию валидации:

d.validate = function (){
return d.getData ().fio.length > 0; }

Результатом вызова функции должно быть булево значение: true — если форму можно отправить, и false в противном случае (никаких сообщений об ошибках YUI не выводит — просто-напросто форма остается на экране). Большой ценности от такой валидации формы нет, но ее можно использовать как базис для собственных "более умных" и "более дружелюбных к пользователю" проверок.

В следующий раз я завершу рассказ о компоненте диалога SimpleDialog. Еще мы попробуем "поиграть" с асинхронной отправкой данных на сервер и продолжим изучение остальных компонентов библиотеки YUI.

black-zorro@tut.by, black-zorro.com


Компьютерная газета. Статья была опубликована в номере 34 за 2008 год в рубрике программирование

©1997-2024 Компьютерная газета