Сложные интерфейсы на javascript вместе 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.