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

September 4, 2008

Сегодняшняя статья будет посвящена разработке с помощью javascript сложных и визуально богатых веб-приложений. Нет, даже не так, очень сложных и очень визуально богатых веб-приложений. Современный посетитель сайта уже привык к тому, что на практически каждом посещаемом им сайте активно используются идеи ajax, когда часть содержимого страницы меняется без перезагрузки всей страницы. Привык к тому, что активно используются всплывающие подсказки, из-за угла страницы плавно выезжают диалоговые окна и под курсором мыши что-то “блестит, подсвечивается, перемещается”. Удобно, приятно и, главное, легко реализуемо даже не слишком опытным веб-программистом, благо существует множество javascript-библиотек, которые автоматизируют создание подобных визуальных эффектов. Я и сам ранее написал две серии статей посвященных популяризации подобных библиотек: jquery и mootools. Теперь, спустя полгода, я снова хотел бы вернуться к теме javascript-библиотек и созданию с их помощью эффективных веб-интерфейсов, но только немного с другой стороны.

Идея с переносом ряда классических desktop приложений в internet, крайне популярна в последние годы (и, что особенно приятно, коммерчески выгодна). Вот только инструментарий и методики, которые используются для разработки сайта с “рюшечками” и серьезного приложения (например, равного по функциональности gmail) отличны. И если в первом случае нам хватает javascript-библиотеки ориентированной на визуальные эффекты. То во втором случае нам нужны javascript-библиотеки более похожие на библиотеки визуальных компонентов доступные для программистов на Delphi/.net. Наличие в такой библиотеке большого количества сложных визуальных элементов управления вроде таблицы, дерева, набора закладок недостаточно. Нам нужно, чтобы код этих компонентов был написан крайне (я подчеркиваю) крайне эффективно. Я сам несколько раз слышал и видел во что превращается веб-страница, на которой расположена табличка с сотней-другой записей. Загрузка процессора под 100% и полгигабайта оперативной памяти, которые “скушал” браузер, это еще “цветочки”. И никакой пользователь не будет терпеть сайт (пусть даже с очень хорошим информационным наполнением), который ведет себя подобным образом. В этом плане я крайне приветствую приход на наш рынок технологий flex, silverlight 2, javafx. Сейчас Flex (спустя два с половиной года после появления первых версий flex sdk 2) уже начинает появляться на рынке (и даже в списке белорусских вакансий). А вот с silverlight2 (на момент написания статьи эта технология все еще в бетах) и javafx все гораздо хуже. Не решены вопросы с наличием и качественной работой этих технологий под различные версии О.С., нет критической массы установленных плагинов, нет достаточной массы ни специалистов, ни заказов. Так что создавать веб-приложения с “богатым” (сложным и удобным) интерфейсом можно только либо с помощью flex, либо с помощью javascript.

И эта статья будет посвящена именно наличным (значит качественным и рабочим) средствам, ориентированным на javascript. Необходимо понимать, что существует огромная разница между приложениями, предназначенными для работы в internet и в intranet. В первом случае мы должны уделять внимание скорости загрузки страниц (следовательно, чем меньше по размеру библиотека – тем лучше), следует помнить, что “парк” компьютеров у пользователей достаточно различен и кроме “новых и производительных” есть и “старые и медленные” компьютеры. Еще раз напомню, что основная масса пользователей рассматривает сайт как средство развлечения, а не “тот самый” источник информации, ради доступа к которой можно терпеть все. Совсем другое дело, если мы говорим о приложениях предназначенных для использования в intranet. Веб-приложения размещены на внутреннем сервере компании, доступ к которым осуществляется по высокоскоростным сетям, унифицированный (хочется верить) как аппаратно, так и программно компьютерный “парк” и наличие системного администратора “под боком”. Когда пару лет назад я впервые столкнулся с javascript-библиотекой YUI (темой нашего разговора), то использовал средства этой библиотеки для создания именно “внутреннего проекта” и ни разу не пожалел. Недавно мне пришлось использовать YUI для обычного сайта, и я с радостью обнаружил, что прошедшее время крайне положительно оказало воздействие на yui. Так, появились новые элементы управления, javascript-код стал работать корректнее в разных версиях браузера, а грамотное разделение css-кода от javascript дает гибкие возможности по созданию не “сухих” офисных приложений, а “ярких и цветастых” сайтов для массового посещения. Подросшая пропускная способность сетей вкупе с алгоритмами сжатия javascript-файлов и грамотно настроенная система кэширования могут снять последний аргумент “почему сайт так медленно грузится”.

Перед тем как я перейду к практическому рассмотрению yui необходимо разобраться с вопросами лицензирования. Библиотека распространяется бесплатно и с открытыми исходными кодам (лицензия BSD). Фактически суть лицензии BSD можно свести к высказыванию “вот исходный код библиотеки, вы можете делать с ней все что угодно, вы можете модифицировать этот код, вы можете создать на базе этого кода коммерческий продукт и никому не обязаны показывать его исходники”. Единственное, что нельзя делать, так это говорить, что именно вы автор данной библиотеки – других ограничений нет. Еще YUI часто сравнивают с другой известной библиотекой extJS. Так что стоит сказать пару слов об истории взаимоотношения этих продуктов. В недалеком 2006 г. некто Jack Slocum работал над созданием для yui дополнительных расширений и называлась эта библиотека yui-ext (распространялась также под лицензией BSD). Вскоре, yui-ext приобрела такую популярность среди разработчиков, что Jack Slocum решил отделить свое детище от yahoo ui и создал коммерческую компанию, которая посветила себя развитию extjs. Extjs распространяется под двойной лицензией и, в общем случае, для коммерческого использования является платным. В настоящее время extjs приобрел достаточную популярность и уже появились библиотеки интегрирующие extjs с популярными серверными framework-ами (java, php).

Если вы заинтересуетесь yui, то настоятельно советую добавить в закладки своего браузера сайт http://yuiblog.com, на котором публикуют свои блоги разработчики yahoo работающие над yui. На домашнем сайте YUI (расшифровывается как Yahoo User Interface library) - http://developer.yahoo.net/yui вы можете скачать архив с библиотекой. Распаковав архив, вы увидите 7 папок: build, docs, examples, as-docs, as-src, assets (не обращайте на нее внимания), tests. В папке build содержатся javascript-файлы, собственно, библиотеки. Обратите внимание на то, что каждый файл существует в трех версиях, например: animation.js, animation-debug.js, animation-min.js. Если назначение третьего файла очевидно, то с первыми двумя стоит разобраться подробнее. В состав YUI входит модуль журналирования. Условно говоря, с его помощью где-нибудь на html-странице можно создать плавающее окошко, в котором будут выводиться сообщения от самого yui, либо вы можете при написании собственного кода вместо “надоедливых и неудобных” окошек alert использовать вывод в стандартизированной форме. Я обязательно расскажу об использовании модуля журналирования, но позже. Возвращаясь к рассмотрению устройства архива библиотеки yui, второй каталог – docs – содержит документацию по библиотеке (документация очень хорошая и написана подробно). Я настоятельно рекомендую в дополнение к библиотеке загрузить с сайта yahoo еще и так называемые Cheat Sheets. Cheat Sheets – это набор pdf-файлов-шпаргалок. Каждая “шпаргалка” описывает один из модулей yui и содержит основные сведения для “быстрого старта”, так что начать использовать yui можно и без долгого штудирования манула. Третий каталог, который вы найдете в архиве yui – это examples. Там вы можете найти для каждого из модулей “пачку” примеров. YUI в последних своих версиях (я описываю в этой серии статей версию 2.5.2) состоит не только из “чистого” javascript. Так для функционирования некоторых модулей (построение графиков и загрузка файлов на сервер) нужен flash-код. Исходники этих flash-роликов находятся в папке as-src, а документация в подкаталоге as-docs. Подкаталог tests содержит набор js-файлов с тестами модулей yui (не слишком полезен для нас). Теперь перейдем к рассмотрению каждого из модулей образующих yui подробно и на практике.

Условно, модули образующие yui делятся на четыре группы. Во-первых, поддержка css. Эта часть, гордо называемая css-framework, включает наборы css-стилей для создания веб-интерфейсов построенных на сетках (что это за такой вид верстки можно почитать на сайте http://habrahabr.ru/blog/css/40234.html). Затем идут модули, образующие ядро yui (global, dom, event). Третья часть yui – это “всякое разное”. В эту категорию входят модули для создания анимации веб-страниц, поддержки D&D, управление загрузкой ресурсов. Четвертая часть yui – основная часть материала будет посвящена именно ей – образована различными графическими компонентами (деревья, сетки, менюшки, кнопки, падающие списки …).

Рассказ об css-grid-ах я пропускаю, а сейчас начну с рассмотрения возможностей “ядра yui”. Если вы вспомните мою серию статей про jquery, то увидите что больше всего там я “восхищался” средствами для поиска заданных узлов (html-элементов) на странице. Вместо того, чтобы организовывать “страшные” циклы и много-много if-ов, я мог записать компактное выражение, перечислив цепочку css-характеристик узлов, через которые нужно пройтись в поисках нужного dom-узла. Рассмотрение YUI я начну тоже с этой темы. Прежде всего, нужно создать html-файл в котором подключаются следующие js-модули yui:
  1. <script type="text/javascript" src="js/yahoo/yahoo.js"></script>
  2. <script type="text/javascript" src="js/event/event.js"></script>
  3. <script type="text/javascript" src="js/dom/dom.js"></script>
  4. <script type="text/javascript" src="js/selector/selector-beta.js"></script>
Для последующих тестов я создал html-файл с множеством html-элементов различных типов вложенных друг в друга. Завершающим штрихом будет создание кнопки с обработчиком события вызывающим функцию doAction:
  1. <button onclick="doAction()">do something</button>
Внутри функции doAction я обращаюсь к объекту YAHOO.util.Selector и вызываю метод query в качестве параметра, которому передается строка условия поиска:
  1. function doAction (){
  2.   alert (YAHOO.util.Selector.query ('#block span')); 
  3. }
Результатом вызова метода query будет список всех тегов span находящихся внутри тега с идентификатором “block” на всех уровнях вложенности. Для того чтобы найти только те теги span, которые непосредственно вложены внутрь block, нужно использовать такой синтаксис: “#block > span”. Чтобы отобрать только те теги span, которые помечены классом “warning”, используем запись “#block > span.warning”. А такая форма записи “b + i” означает, что нужно найти элемент “i” непосредственно (именно непосредственно) следующий за тегом “b” (т.е. “b” является sibling-ом тега “i”). В случае если нужно найти тег “i” следующий после “b”, но ограничения на непосредственность нет, то можно использовать такую запись “b ~ i”. В этом случае мы найдем тег “i” даже если между ним и “b” есть какие-то еще теги. YUI Selector поддерживает некоторое подмножество CSS3 селекторов, например “div > span:first-child” означает, что непосредственно внутри тега “div” нужно найти первый (на это указывает псевдо-класс “:first-child”) дочерний тег “span”. Аналогично, псевдо-класс “:last-child” поможет вам выбрать последний тег заданного типа. Запись “span em:only-child” позволяет найти тег span содержащий один единственный элемент “em”. Если же нужно найти тот тег “div”, содержимое которого (напрямую самого “div”-а или одного из вложенных элементов) содержит слово “help”, то используйте такую запись “div:contains(help)” (слово “help” должно идти без кавычек).

Что же это, конечно, не все возможности селекторов yahoo, но пора идти дальше. Функция query получает не только единственный параметр со строкой “что искать”, но можно указать dom-элемент страницы относительно которого нужно начать поиск (второй параметр функции query). Третий параметр (логического типа) управляет тем, будет ли yui искать все элементы по заданному вами шаблону или будет возвращен только первый из подошедших. В состав yui модуля Selector еще входит функция filter, в качестве первого параметра ей передается список узлов, а второй параметр играет роль фильтра условия (увы, но нельзя использовать все возможности фильтрации описанные выше). Таким образом, можно из списка узлов отбросить те, которые не подходят дополнительному условию. Похожа на filter и функция test, только ей в качестве первого параметра должен быть передан одиночный узел и в случае, если этот узел соответствует условию заданному вторым параметром, то функция test вернет “true”.

Научившись пользоваться модулем Selector и функцией query, неизбежно возникает вопрос: и что с того? Действительно, обычно мы получаем ссылку на некоторые узлы в html-документе для того чтобы в последующем как-то их обработать, например, назначить определенное стилевое оформление. В составе модуля YAHOO.util.Dom есть несколько методов позволяющих назначить элементу некоторый css-класс или избавиться от оного. Для демонстрации этих возможностей я решил развить пример с поиском внутри dom-дерева документа некоторых узлов и уже не выводить их на экран функцией alert, а выделять визуально, например, красным цветом. Для этого я создал css-класс red:
  1. <style>
  2.   .red {color: red;}
  3. </style>
Html-код страницы был немного модифицирован: я создал текстовое поле расположенное непосредственно перед кнопкой и дал ему название “reg”. Теперь пользователь может ввести в это поле выражение поиска, нажать кнопку и увидеть как на html-странице некоторые элементы меняют цвет на красный. Однако перед тем как применять новое оформление, необходимо удалить старое (те элементы, которые были помечены красным в прошлой итерации, должны снова принять свой обычный внешний вид):
  1. //создаем переменную, хранящую список узлов выделенных в прошлый раз
  2. var old = null;
  3. function doAction (){
  4.   // в том случае если какие-то элементы были выделены, то необходимо удалить от них класс red
  5.   if (old != null)
  6.     YAHOO.util.Dom.removeClass (old, 'red');   
  7.   // а теперь ищем узлы по заданному условию  
  8.   old = YAHOO.util.Selector.query ( YAHOO.util.Selector.query('#reg')[0].value );
  9.   // и применяем к ним выделение
  10.   YAHOO.util.Dom.addClass (old, 'red'); 
  11. }
Наверняка, вместе с функциями addClass и removeClass вам пригодится и функция replaceClass (для кого, старое, новое). Ее назначение – заменить для элемента старое значение css-класса на новое (внимание: если у элемента нет старого класса, то новый класс ему все равно будет назначен). Еще одна полезная функция для работы со стилями – hasClass (для кого, класс). Она позволяет узнать назначен ли некоторый css-класс для заданного элемента.

Помимо изменения стилевого оформления элементов, второе наиболее частое действие с узлами DOM-документа – это их динамическое добавление, удаление, перемещение. И тут, скажем прямо, yui не слишком хорош. Дело в том, что хотя в состав yui объекта Dom входят методы insertAfter и insertBefore (первый параметр этих функций то, что нужно вставить, а второй параметр – элемент с позицией вставки). Так вот фокус в том, что мы не можем задать наподобие jquery что-то вроде (внимание, этот код не работает):
  1. YAHOO.util.Dom.insertAfter (<span>new text</span>, targetBox)
В jquery функции вставки узлов в случае если “то, что нужно вставить” не представлял собой конкретный html-элемент, то выполнялось автоматическое создание на основании строки набора html-элементов, которые затем и вставлялись в искомую позицию. В yahoo мы должны явно создать вставляемые элементы, собственно говоря, это не трудно, но неприятно. В состав модуля Dom входит еще несколько функций, позволяющий получить информацию об элементах страницы. Например, getX, getY, setX, setY – функции возвращающие координаты элементов и, соответственно, устанавливающие их. Функция getStyle возвращает сведения о стилях примененных к элементу с помощью атрибута “style”, а функция setStyle позволяет присвоить новое значение стилю:
  1. YAHOO.util.Dom.getStyle(elts, 'color');
  2. YAHOO.util.Dom.setStyle(elts, 'color', ‘red’);
Теперь перейдем к рассмотрению еще одного модуля yui образующего его ядро – Event. Этот модуль предназначен для управления событиями. В качестве первого примера я перепишу показанный ранее фрагмент кода с кнопкой и явно заданной через атрибут “onclick” функцией обработки события “на кнопку нажали”. Первое о чем нужно задуматься, так это о методике подписки на событие “дерево DOM готово к использованию”. Это стандартная функциональность для любой уважающей себя библиотеки javascript заключается в наличии способа сказать, что некоторая функция должна быть вызвана не на событие “страница загрузилась”, а на событие “DOM готов к использованию”. Которое выбрасывается тогда, когда загружена структура документа, и мы можем назначить обработчики для кнопок, меню не дожидаясь завершения загрузки всей страницы (а этого, может быть, придется ждать очень долго, особенно если на страничке много больших картинок, баннеров загружаемых с других сайтов …)
  1. function initYUI (){
  2.   YAHOO.util.Event.addListener ( 
  3.     YAHOO.util.Selector.query('button') ,'click' , doAction 
  4.   );
  5. }
  6. YAHOO.util.Event.onDOMReady (initYUI);
Здесь я с помощью вызова onDOMReady назначаю функцию инициализации initUI. Которая в свою очередь вызвав метод addListener (приятно, что мы можем назначить таким образом обработчик события сразу для нескольких элементов) привязывает к тегу “button” и событию “click” обработчик события doAction. Кроме привязки событий мы можем их и отсоединять (вызвав метод removeListener с такими же самыми параметрами). Особенный интерес представляет идея с созданием собственных типов событий: это дает возможность построения модульных приложений, в которых модули извещают друг друга об изменениях, именно, с помощью рассылки подобных “custom”-событий.

В следующий раз я продолжу рассказ об yui и перейду к рассмотрению модуля поддержки анимации, также нас ждут различные widget-ы (меню, набор закладок, таблицы).