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

September 30, 2008

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

В прошлый раз я подробно рассказал о том, как использовать компонент “меню”, мы научились работать с контекстным меню и гибко управлять внешним видом пунктов меню. Сегодняшний же материал будет посвящен семейству компонентов предназначенных играть роль “основы”, “каркаса”, “контейнера” для размещения других элементов управления: так мы познакомимся с набором закладок, попробуем создать диалоговое окно и научимся пользоваться всплывающими подсказками.

В библиотеке YUI особое место занимает модуль Module. Хотя мы никогда не будем пользоваться этим элементом интерфейса напрямую, но раз он лежит в основе почти всех остальных визуальных компонентов, то сказать пару слов о нем стоит. Подключение модуля container выполняется автоматически при подключении любого сложного компонента (например, диалогового окна). Либо можно выполнить подключение явно (так же как и в прошлых примерах, я использую для загрузки YUI-модулей возможности компонента YUI Loader):
  1. loader = new YAHOO.util.YUILoader();
  2. // загружаем модуль container и все нужное для его работы
  3. loader.require(["container"]);
  4. loader.loadOptional = true;
  5. loader.base = 'js/';
  6. loader.filter = 'DEBUG';
  7. loader.insert({ onSuccess: loadCompleted});
Функция loadCompleted будет вызвана тогда, когда модуль container будет загружен и готов к работе. Сделать я хочу “наисложнейшую” вещь: просто спрятать некоторый фрагмент исходной html-страницы, например, этот:
  1. <div id="block">
  2.   <div class="hd">Header</div>
  3.   <div class="bd">Content</div>
  4.   <div class="ft">Footer</div>
  5. </div>
Обратите внимание на то, что каждому из тегов div входящих в состав “block”, назначены специальные имена css-классов. Считается, что контейнер состоит из трех частей: заголовок (hd - header), “тело” контейнера (bd - body) и “подвал” (ft - footer). Чтобы поместить эти четыре тега внутрь Module, я делаю так:
  1. m = new YAHOO.widget.Module("block"); 	
  2. m.render ();
  3. m.hide ();
В примере выше я создал объект Module, передал в качестве параметра его конструктору имя html-элемента. Затем я выполнил инициализацию (render) компонента и последним шагом функция hide спрятала блок. Для того чтобы его отобразить, например, по нажатию на кнопку, используется функция с говорящим именем show(). Откровенно говоря, но большой пользы от такого кода нет: спрятать блок или показать – не слишком важная задача и решается гораздо проще без использования yui. Гораздо интереснее познакомиться с классом Overlay (этот класс наследник от Module). Overlay умеет позиционироваться вне основного потока элементов страницы. Например, когда мы будем разбираться с тем, как создать диалоговое окно, которое можно было бы перетаскивать мышью за его заголовок, то возможности Overlay нам пригодятся (в действительности класс диалоговых окон является наследником от Overlay). А пока парочка простых примеров. Для каждого из них я использую тот же фрагмент html с четырьмя блоками div, что и в предшествующем примере. При создании Overlay я указываю не только имя блока div с его информационным наполнением, но вторым параметром конструктора я задаю список переменных управляющих особенностями отображения Overlay на экране:
  1. m = new YAHOO.widget.Overlay("block", {fixedcenter: true, width: 200, height: 200});
Например, так я задал размеры “block” в виде квадрата со стороной 200 px, и позиционировал его точно посередине страницы. Причем это центрирование будет сохраняться неизменным при любом размере страницы или ее скроллинге. А так я указал абсолютные координаты на html-странице, где должен быть размещен компонент Overlay:
  1. m = new YAHOO.widget.Overlay("block", {xy: [100, 100], width: 200, height: 200});
Возможно, вам пригодится такая функция Overlay как относительное позиционирование. В этом случае я должен задать при создании Overlay сведения об другом html-элементе играющем роль “якоря”, например, так я сказал, что блок Overlay должен прилипнуть своим левым верхним углом к нижнему правому углу блока “якоря” (“tl” расшифровывается как “top left”, а “br” – как “bottom right”):
  1. m = new YAHOO.widget.Overlay("block", {context:["port","tl","br"], width: 200, height: 200});
А что еще можно делать с Module и Overlay? По большему счету – ничего. Однако, если вы решили, что эти компоненты бесполезны, то напомню еще раз: они лежат в основе многих визуальных компонентов. Кроме того, знание “внутренностей” Module (представление его содержимого в виде трех областей) позволит точнее настраивать внешний вид компонентов. Теперь же рассмотрим что-то более “визуально богатое” - компонент плавающей панели. Этот компонент является аналогом привычного нам диалогового окна windows (которым нас о чем-то извещают или спрашивают). Окно поддерживает возможности перетаскивания в пределах экрана, есть средства настройки внешнего вида (скины) окна, и где будут располагаться управляющие им кнопки, например, можно разместить кнопку закрытия окна (“крестик”) не в правом верхнем углу, а в левом верхнем.
  1. m = new YAHOO.widget.Panel("block", {
  2.   context:["port","tl","br"], 
  3.   width: 200, 
  4.   height: 100, 
  5.   draggable:true, 
  6.   close:true, 
  7.   constraintoviewport: 
  8.   true , 
  9.   modal: true
  10. }); 	
  11.  
  12. m.render ();
Внешний вид панели показан на рис. 1.



Вторым параметром конструктора Panel помимо уже знакомых нам переменных, я указал draggable – можно или нет перетаскивать панель за ее заголовок, close – будет ли отображаться “крестик” закрытия окна. Параметр constraintoviewport задает ограничение и не позволяет пользователю переместить панель за пределы окна браузера. Очень приятна функция имитации модального диалогового окна (параметр modal). В этом режиме изображение самого окна страницы меняет фоновый цвет на серый, также блокируется доступ к ее содержимому.

Хотя созданная шагом ранее панелька выглядит очень неплохо со стилем по-умолчанию, но я попробую добавить “немного красок” и изменить ее оформление. Для начала рассмотрим во что превратились исходные четыре div-блока:
  1. <div style="visibility: inherit; width: 200px; height: 100px;" class="yui-module yui-overlay yui-panel" id="block">
  2.  <div id="block_h" style="cursor: move;" class="hd">Header</div>
  3.    <div class="bd">Content</div>
  4.    <div class="ft">Footer</div>
  5.    <span class="container-close"></span>
  6.  </div>
  7.  <div class="underlay" /> 
  8. </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):


  1. <div class="hd"> 
  2.      Header with pic <img src="diskget_sm.gif" border="0" /> 
  3.   </div>
Интересный вопрос о том, как узнать, что диалоговое окно было показано или спрятано. Все компоненты библиотеки YUI используют унифицированную методику подписки на события. К примеру, открываем справку по компоненту Panel, видим, что в списке зарегистрированных событий есть событие “showEvent”, и назначаем функцию обработчик так:
  1. // подписка на показ окна
  2. m.showEvent.subscribe (onShow);
  3. // и подписка на событие “окно спрятано”
  4. m.hideEvent.subscribe (onHide); 
  5.  
  6. function onShow (){ 
  7.   alert ('show'); 
  8. }
Проектируя интерфейс веб-приложения можно столкнуться с проблемой, когда содержимое всплывающего окна сообщения формируется динамически и мы не можем заранее точно узнать какой должен быть размер окна панели. Так в ранее показанных примерах создания Panel, я явно указывал размер окна по ширине и высоте – и это не хорошо. И хотя сам класс Panel-и не обладает требуемой функциональностью, но в состав библиотеки YUI входит еще один визуальный компонент Resizer. Назначение которого – “обернуть” произвольный фрагмент страницы (текст, картинку) и добавить в правый нижний угол активную зону, которую можно “тягать” мышкой и тем самым изменять размер окна:
  1. // создаем плавающую панель
  2. m = new YAHOO.widget.Panel("random", {
  3.   width: 200, 
  4.   height: 100, 
  5.   draggable:true, 
  6.   close:true, 
  7.   constraintoviewport: true, 
  8.   modal: false
  9. }); 	
  10. m.render (document.body);
  11.  
  12. // и оборачиваем ее компонентом Resize
  13. r = new YAHOO.util.Resize('random', {
  14.   handles: ['br'], 
  15.   ratio: true,
  16.   minWidth: 200, 
  17.   minHeight: 100,
  18.   status: true
  19. });
В примере выше я сначала создал панель (идентификатор html-элемента “pane”). Затем, при вызове конструктора объекта Resize, первым параметром я указал идентификатор “random” (какой компонент нужно наделить способностью к изменению размера). Второй же параметр конструктора – ассоциативный массив с конфигурационными параметрами. Так параметры minWidth и minHeight задают минимальные размеры окна Resizer-а. Параметр “handles” равен массиву строк, каждая из которых кодирует название угла окна панели к которой будет добавлена “активный уголок” (например, “br” – правый нижний). Параметр “status” приводит к тому, что при изменении размера панели возле активного уголка будет показываться всплывающая подсказка с текущим размером панели (см. рис. 3).



Изначально вы можете изменять размеры окна панели произвольным образом, вытягивая или растягивая его. Имеет смысл наложить ограничение, так чтобы изменение размера не приводило к искажению пропорций, за это отвечает параметр ratio. Естественно, что для того чтобы компонент Resize был найден YUI, нам необходимо подправить код загрузки модулей:
  1. loader = new YAHOO.util.YUILoader();
  2. // загружаем в дополнение к модулю container еще и модуль resize
  3. loader.require(["container", "resize"]);
  4. loader.loadOptional = true;
  5. loader.base = 'js/';
  6. loader.insert({ onSuccess: loadCompleted});
Завершая рассмотрение класса Panel, я упомяну о том, как можно создать диалоговое окно целиком только с помощью javascript, без предварительной html-разметки. Для Panel, равно как и для любого другого компонента основанного на классе Module, есть методы изменяющие содержимое каждой из трех его частей (setHeader, setBody, setFooter). Также обратите внимание в следующем примере, что первый параметр для конструктора Panel равен “random”. В исходном html-документе нет тега с таким значением идентификатора, поэтому блок div будет создан автоматически. При вызове метода render я должен обязательно передать ссылку на тот элемент страницы, который будет играть роль контейнера для панели. И последнее: при вызове методов setHeader, setBody, setFooter я могу передавать не только “простой текст”, но и произвольный html-код:
  1. m = new YAHOO.widget.Panel("random", {
  2.   width: 200, 
  3.   height: 100, 
  4.   draggable:true, 
  5.   close:true, 
  6.   constraintoviewport: true
  7. }); 	
  8.  
  9. m.setHeader("Header");    	
  10. m.setBody("Content of panel"); 
  11. m.setFooter("<b>footer for panel</b>"); 
  12. m.render (document.body);
Как только я сказал, что содержимым любой из областей окна панели может быть произвольный html-текст, самое время задаться вопросом: можно ли разместить на панели кнопки, падающие списки, превратив тем самым панель в настоящее диалоговое окно. Да можно и более того, в YUI уже предусмотрели набор инструментов автоматизирующих создание диалогового окна. В частности есть два класса-компонента Dialog и SimpleDialog, содержащих средства для создания типовых диалоговых окон, с набором кнопок (OK, CANCEL) размещенных внизу окна, с иконкой типа сообщения (например, диалоговое окно с сообщением об ошибке будет иметь отличный внешний вид от окошка сообщения о подсказке). В следующем примере я создал html-заготовку содержимого диалогового окна (без области footer, т.к. ее содержимое в любом случае будет замещено набором управляющих диалогом кнопок):
  1. <div id="block">
  2.   <div class="hd"> Header with pic <img src="diskget_sm.gif" border="0" /> </div>
  3.   <div class="bd"> Information Message</div>
  4. </div>
Теперь я должен подправить код загрузчика модулей: мне для работы обязательно нужен модуль “container”, а для того чтобы кнопки на созданной YUI заготовке диалогового окна имели предопределенное стилевое оформление (см. рис. 4) я подключил модуль “button”.



Завершив подготовку, пора создать само диалоговое окно:
  1. // создаем диалоговое окно
  2. d = new YAHOO.widget.SimpleDialog("block", { 
  3.    width : "400px", 
  4.    icon: YAHOO.widget.SimpleDialog.ICON_INFO,
  5.    fixedcenter : true, 
  6.    visible : false, 
  7.    constraintoviewport : true, 
  8.    buttons : [ 
  9.      { text:"Accept", handler:onAccept},
  10.      { text:"Discard", handler:onDiscard, isDefault:true} 
  11.    ] 
  12. }); 
  13.  
  14. d.render ();// параметра нет, т.к. содержимое диалога уже определено в теле html-страницы
  15.  
  16. // и теперь показываем окошко диалога на экране
  17. d.show ();
  18.  
  19. // а вот пример функции обработчика события “нажатие кнопки”
  20. function onAccept (){
  21.  alert ('onAccept’); 
  22. }
Вызывая конструктор класса 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”):
  1. function onAccept (){
  2.  d.submit ();
  3. }
В этом примере переменная d – хранит ссылку на созданный ранее объект SimpleDialog. Давайте попробуем, что происходит по нажатию на кнопку? Да ничего: форма исчезает, но данные никуда не отправляются. Дело в том, что разработчики YUI решили “научить” свою форму отправлять данные как синхронно (т.е. страница перезагружается) так и асинхронно (данные отправляются с помощью ajax, а сама страница не перезагружается). Однако по-умолчанию режим работы формы - “игнорировать” и его нужно изменить. Для этого при вызове конструктора класса SimpleDialog нужно передать еще один конфигурационный параметр – postmethod. Его значение должно быть либо “form” (синхронная отправка), либо “async” – для асинхронной. Режим же “none” говорит, что YUI ничего не делает с содержимым формы, и мы должны самостоятельно обработать данные внутри функции onAccept. В практике веб-разработки требуется, чтобы перед тем как форма уходит на сервер, обязательно проверить правильность ее заполнения. Например, предположив, что в форме есть текстовое поле с именем (не id, просто name) “fio”, то я могу назначить функцию валидации:
  1. d.validate = function (){
  2.  return d.getData ().fio.length > 0; 
  3. }
Результатом вызова функции должно быть булево значение: true – если форму можно отправить и false в противном случае (никаких сообщений об ошибках YUI не выводит, просто-напросто форма остается на экране). Большой ценности от такой валидации формы нет, но ее можно использовать как базис для собственных “более умных” и “более дружелюбных к пользователю” проверок.

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