Введение в библиотеку jquery. Часть 1

August 27, 2007

JQuery: Быстрый старт в мире javascript. Часть 1



С каждым годом все большее внимание уделяется вопросу разработки веб-приложений с достаточно сложным интерфейсом, асинхронно загружающих нужные для работы данные с сервера. Одновременно с этим ряд, как достаточно крупных компаний, так и open source сообществ разрабатывают и предлагают продукты для автоматизации рутинных и повторяющихся действий в ходе разработки подобных приложений. Например, библиотеки элементов для проектирования пользовательского интерфейса (деревья, наборы закладок, таблицы). Плохо, что достаточно много таких библиотек и проектов носят экспериментальный характер и не претендуют на роль каких-либо де-факто стандартов, вынуждая разработчиков изобретать очередной велосипед. Некоторые проекты умирают из-за потери интереса и необходимости выдерживать достаточно жесткий timeline; некоторые проекты сливаются в единое целое. Достаточно примеров, когда разработчики излишне жаждут денег, обрекая проект на скудную базу подписчиков и, в конце концов, загнивание (ох, Laszlo, Laszlo, что же с тобой творится). Но все же философский закон перехода количества в качество и наоборот, работает и способствует появлению некоторых достаточно устойчивых framework-ов для javascript|html|css. Сегодня я расскажу о библиотеке JQuery, ее домашний сайт http://jquery.com/. Ее основное назначение - упрощение работы с документами DOM из javascript.

Мне трудно выделить и сказать что jQuery “самая-самая”. По крайней мере, я не встречался с проблемами совместного использования jQuery и других, более специализированных библиотек. Также в плюсы можно записать и небольшой размер библиотеки - порядка 15 кб (в сжатом виде, естественно), и совместимость с основными браузерами. Периодически в Интернет проводятся всяческие голосования на тему “кто на свете всех милее”. Например, такое исследование я нашел на сайте http://www.habrahabr.ru
Процент выбора Предпочитаемый framework
1.7% Dojo Toolkit
4.11% YUI
30.64% Prototype
27.52% jQuery
4.4% ExtJS
1.28% MochiKit
8.65% Mootools
21.7% Иное
Как вы знаете, в стандарте html не было предусмотрено каких-либо высокоуровневых средств для проектирования, собственно, интерактивного интерфейса пользователя. Все что у вас есть это таблицы, списки, картинки, текст, раскрашенный различными цветами размерами и шрифтами. С появлением CSS ситуация не изменилась к лучшему, мы всего лишь приобрели отличный способ изменять внешний вид каких либо элементов управления но ни на грош не добавили им “умности”. Например, если вы хотите создать выпадающее меню, то вынуждены сами реализовать обработку событий мыши, так при наведении мыши на какой либо текстовый блок (заголовок меню) вам нужно динамически показать еще один блок, содержащий собственно пункты этого меню. Когда мышь ходит по подпунктам меню, вам нужно менять их подсветку для выделения текущего пункта. Или возможно показать еще одно подменю. С одной стороны это дает нам гибкость, так как мы не ограничены в вариантах внешнего вида (дизайна) страниц, с другой стороны возникает множество проблем связанных со специфическими багами браузеров (в основном конечно одного – internet explorer). Кроме того, такой поход снижает скорость разработки, заставляет нас тратить множество времени на отладку одноразовых алгоритмов. Так что рано или поздно мы вынуждены или создать собственную высокоуровневую библиотеку пользовательских элементов, либо использовать какое-нибудь стандартное решение. Увы, в составе JQuery нет поддержки сложных графических компонентов. Если вам нужны деревья, меню, таблицы, то я рекомендую применять yahoo controls.Там же вы найдете различные эффекты (может, хватит в который раз подряд писать алгоритм плавного “выезжания” выпадающего меню, или перехода прозрачности). JQuery специализируется на решении другой проблемы, которую я назвал “Давайте же, наконец, что-нибудь сделаем”. Не забывая, кроме того, об ajax, и еще паре мелочей.

Итак, для того чтобы обратиться и изменить внешний вид” чего-то” - вам необходимо найти это “чего-то” среди всех остальных узлов дерева DOM. В ходе этого поиска часто возникают трудности связанные с тем, что нет механизмов, позволяющих декларативно записать/задать набор узлов на которые идет воздействие, а мы вынуждены применять морально устаревший императивный подход с кучей циклов, условий и т.д. Давайте для примера рассмотрим задачу создания некоторого выпадающего меню. Предположим, что собственно ряд пунктов первого уровня реализован в виде таблицы из одной строки. Каждая ячейка, которой содержит следующий набор вложенных тегов:
  1. <td>
  2.   <div class=”menu_item”>
  3.     <span class=”simply”>
  4.       <a href=”…”>It’s First Menu Item</a>
  5.     </span>
  6.   </div>
  7. </td>
Комбинация вложенного div, span служит для задач визуализации, также возможна замена “обрамления” некоторых из этих пунктов чем-то вроде
  1. <div class=”cde”>
  2.      <p class=”abc”>
  3.         <a href=”…”></a>
  4.      </p>
  5.   </div>
Теперь вы хотите добавить для собственно пунктов меню две функции onmouseover и onmouseout, вызываемых, когда мышь наводится и убирается с элемента DOM.
  1. <div class=”menu_item” onmouseover=”makeShowMenu();” onmouseout=”makeHideMenu()”>
Что же неплохо, но не гибко. Если вы уверены, что в таком виде код будет отдан заказчику, то можете успокоиться, и дальше не читать. Если же вы чувствуете, что код придется перерабатывать. Например, функция makeShowMenu может на каком-то этапе начать получать некоторые параметры, возможно, что эти значения должны быть как-либо рассчитаны, возможно, вам потребуется вызвать другую функцию вместо makeShowMenu, или еще что-нибудь. В любом случае вы столкнетесь с проблемой не читаемости кода и сложностью его правки. Когда в одном файле перемешан и html|css с одной стороны, и javascript-код с другой, то вносить своевременные (главное, точные) правки в каждый из них достаточно сложно. Поэтому применяют другой подход, когда исходный html код не содержит ссылок на javascript функции, а вместо этого, где-то в отдельном js-файле, вызывающемся после завершения загрузки всей страницы, выполняется программное добавление свойств onmouseover, onmouseout и их значений. Например, так (осторожно, это псевдо-код и он не работает):
  1. 1) var tds = document.getElementsByTagName (‘td’);
  2. 2) for (var I = 0; I < tds.length; i++){
  3. 3)   var td = tds [i];
  4. 4)   var divs = td.getElementsByTagName (‘div’);
  5. 5)   for (var j =0; j < divs.length; j++)
  6. 6)     if (divs[j].className = ‘menu_item’ ){
  7. 7)       var spans = divs[i].getElementsByTagName (‘span’);
  8. 8)        for (var k = 0; k < spans.length; k++)
  9. 9)             spans [k].addListener  (….);
  10. 10)    } }
Этот кусок кода можно было сократить на пару строчек, но сути дела это не меняет. JavaScript откровенно говоря кажется просто ужасным языком для работы с DOM. С другой стороны вспомните как изящно вы записывали в мире CSS некоторый селектор:
  1. #tab_menus td div.menu_item span a {color : red};
Вы просто перечисли цепочку узлов (точнее, характеристик, которыми должны обладать эти узлы) и правило, которое будет применено к узлу находящемуся в конце этой цепочки. В примере кода выше только строка #9 несет хоть какой-нибудь смысл. Там выполняется привязка к элементу ссылке “a” некоторого обработчика событий. Остальные 9 строк занимаются очевидным, но от этого не становящимся менее нудным перебором иерархии узлов. В примере я еще забыл добавить в самом начале фрагмент кода решающий задачу поиска нужной таблицы. Т.к. ячеек таблицы в документе может быть достаточно много, но, при этом, не для всех нужно добавлять специфическое поведение меню. Вы думаете - это все, ну уж нет! Особенность модели DOM зависит не только от браузера, но и от режима html. Например, на рис. 1 я показал, как будут выглядеть два дерева узлов для одного и того же документа отличающегося только типом документа: html или же xhtml.
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  2.  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4.  <head>
  5.     <title>DOM bagged</title>
  6.  </head>
  7.  <body>  
  8.     <table>
  9.      <tr> 
  10.         <td>User FIO</td>
  11.         <td>User Age:</td>
  12.      </tr>
  13.     </table>
  14.  </body>
  15. </html>


Первое что сразу бросается в глаза, так это различие в регистре имен тегов. Далее в html-варианте (слева) элемент таблица содержит дочерний элемент TBODY, и уже в него помещаются строки и столбцы. Для xhtml (вариант справа) такого “обертывания” нет. Естественно, что это не единственный пример, вы легко можете найти в Интернете особенности формирования DOM в случае различных форматов. И, следовательно, при создании алгоритма поиска некоторых элементов страницы нужно учитывать такие “назойливые” подробности.

Так вот, перебор в строках 1-8 с помощью jquery будет заменен на, всего лишь, одну строку кода. Также вы можете забыть об особенностях используемого браузера и не писать больше такой ужасный код:
  1. if (element.addEventListener) {
  2.   //присоединяем в стиле firefox | opera
  3. } 
  4. else 
  5.    if (element.attachEvent) {
  6.      //Присоединяем обработчик события в стиле internet explorer
  7.    }
  8.    else{ 
  9.      //element.имя_события = …;
  10.    }
На этом хватит, давайте перейдем к примерам. Первая типовая задача – это вызов некоторой функции, которая должна “донастроить” страницу. Например, всем ссылкам, размещенным внутри таблицы заголовка, назначить некоторый вычисляемый стилевой атрибут. Традиционно такая задача решается с помощью присвоения специальной переменной “window.onload” ссылки на функцию, которая и должна быть вызвана. Даже здесь возникает две проблемы. Первая заключается в сложности назначения сразу нескольких функций, которые будут вызываться друг за другом для internet explorer 6.
  1. window.onload = func_1;
  2. window.onload = func_2;
Например, в таком коде будет вызвана только вторая функция. Метод с использованием addListener не подходит для ie. В этом случае следует применять нечто подобное:
  1. window.attachEvent("onload", func_1); 
  2.     window.attachEvent("onload", func_2);
Вторая проблема не столь заметна. Давайте сначала ответим на вопрос: a когда именно вызывается событие onload? Ответ: тогда, когда полностью будут загружены все ресурсы, на которые ссылается страница. А если на странице есть парочка огромного размера картинок или ссылка на баннер загружающийся с другого сайта? Таким образом, получаются довольно странные эффекты, когда страница кажется полностью загруженной, но, например, навигация меню не работает. И только потом, спустя десяток секунд ожидания, меню – раз!- и заработало.

В jquery используется другой подход. Мы не ждем, когда загрузится абсолютно вся страница, а только момента, когда будет сформировано и доступно для использования дерево DOM. Прежде всего, необходимо подключить библиотеку к вашему файлу html.
  1. <script type="text/javascript" src="то_место_где_находится_библиотека_jquery.js"></script>
  2. Затем мы выполняем привязку функции к самому документу:
  3. $(document).ready(function(){
  4.    // какие-то действия
  5.  });
В этом примере я использовал нотацию “$(путь).операция(параметры)”. Для того чтобы обратиться к некоторому узлу или подмножеству узлов дерева документа DOM вам необходимо записать эту цепочку как параметр функции $. Здесь я явно передал ссылку на объект, к которому должна быть применена “операция”. Возможно, указать цепочку узлов в виде строки текста в специальной нотации. Ее я рассмотрю позже.

При вызове функции “$” вы можете не только передать первым параметром строку запроса, но и указать ссылку на узел, относительно которого будет выполнен поиск. В противном случае поиск будет начат с самой вершины дерева dom – html. Более того, в jquery предусмотрена функция расширения его возможностей с помощью плагинов. Так что если вы обратитесь к сайту http://www.softwareunity.com/sandbox/JQueryMoreSelectors/ то сможете найти там еще несколько интересных видов синтаксиса запроса.

Полное описание возможных форматов записи “пути” довольно велико и я приведу лишь некоторые возможные варианты. Если же вам интересны иные виды селекторов, то прошу http://docs.jquery.com/Category:Selectors, откуда я и взял часть нижеследующих примеров.
Селектор Примечание
"p[a]" Найти в документе все теги “p”, внутри которых содержится тег “а”
“ul/li” Все элементы “li” неупорядоченных списков “ul”
“p.foo[a]” Все теги “p” с классом “foo” и вложенным тегом “a”
“input[@name=bar]" Все теги “input” с атрибутом “name” значение которого равно “bar”
"input[@type=radio][@checked]" Найти все теги “input”, которые имеют значение атрибута type равным “radio”, а также установленное значение атрибута “checked”
“p,span,td” Все элементы тегов “p” или “span” или “td”
“p#secret” Элемент параграфа “p”, с идентификатором “id” равным слову “secret”
“p span” Тег “span” являющийся потомком тега “p” (не обязательно прямым потомком). Если же это вам необходимо, то рекомендую применять “p > span” или “p/span”
p[@foo^=bar] Тег “p”, содержащий атрибут “foo” текстовое значение которого начинается на слово “bar”
P[@foo$=bar] Тег “p”, атрибут “foo” которого содержит значение заканчивающееся на “bar”
P[@foo*=bar] Тег “p”, атрибут которого “foo” содержит в произвольном месте подстроку “bar”
p//span Тег “span” являющийся потомком (не обязательно прямым) к тегу “p”
p/../span Тег “span” являющийся “внуком” тега “p”
Общее замечание: при записи пути к отбираемым элементам мы пишем код, комбинируя возможности xpath и css. Так вы записываете путь, к некоторому элементу чередуя имена тегов, которые необходимо пройти через “/” (эта традиционная запись xpath). Также вы можете указать каким классом должен обладать тег (.foo – традиционная для css запись). Более того, вы можете задать в квадратных скобках некоторое условие, которому должны удовлетворять отбираемые теги. Внутри этих квадратных скобок вы можете ссылаться на атрибуты тегов (перед их именем мы ставим “@”), также возможна запись нескольких условий отбора.

Кроме того возможна запись, которая указывает, какой именно, узел среди множества возможных следует выбрать на основании его порядкового номера. Например, если тег p содержит 10 тегов span, то вы можете использовать следующие условия отбора:
 1.	<strong>p/span:eq{4}</strong> – четвертый но номеру элемент “span”;
 2.	<strong>p/span:lt{5}</strong> – элементы “span”, номер которых менее чем 5;
 3.	<strong>p/span:gt{2}</strong> – элементы “span”, номер которых превосходит 2;
 4.	<strong>p/span:first</strong> – первый элемент “span” среди своих “siblings”;
 5.	<strong>p/span:last</strong> – последний элемент “span” среди своих “siblings”.
После того как мы разобрались, как может выглядеть “путь” я перечислю возможные значения “операции”. Начнем мы с самого простого. Все о чем я говорю, может быть найдено на следующих страницах: http://docs.jquery.com/DOM/Attributes, http://docs.jquery.com/CSS.

Для изменения внешнего вида найденных элементов можно использовать метод css. Который, если его вызвать с единственным параметром – именем некоторого css-свойства, возвращает значение этого свойства, или же если его вызвать передав в качестве параметра некоторый ассоциативный массив с перечислением пар “ключ:значение”, то в этом случае ко всем найденным узлам будут применены новые стили.
  1. <div id="content" style="font-size: 12px;">
  2.   …. Много-много кода …
  3. </div>
  4.  
  5. <script>
  6.  alert ($("div#bodyContent").css ('font-size'));
  7.  $("div").css(
  8.    { color: "red", background: "blue" }
  9.  );
  10. </script>
В результате выполнения данного кода появится окно сообщения со значением “12px” а затем все блоки “div” приобретут синий фон и красный цвет текста.

Также возможно определить текущее значение размеров элемента или же установить новое значение высоты или широты блока:
  1. alert ($("div#bodyContent").height())
  2.  $("div#bodyContent").height ('200px');
  3.  alert ($("div#bodyContent").width())
  4.  $("div#bodyContent").width ('200px');
Есть операция “attr” работающая подобно “css”, но применяющаяся не к набору стилей элемента, а к нему самому. Так вы можете, вызвав эту функцию с единственным параметром – строкой с названием некоторого атрибута получить его значение. Или же передав внутрь операции ассоциативный массив пар, можно поменять свойства тега, например (это не все возможности attr, но наиболее полезные):
  1. alert ($("img").attr("src"));
  2. $("img").attr( { src: "test.jpg", alt: "Test Image" });
Также для изменения внешнего вида элемента возможно использовать специализированную операцию “addClass”. С ее помощью вы можете применить к элементу некоторый новый класс css. Обратите внимание, что добавление класса не приводит к утере старого значения, класс просто добавляется, например, так:
  1. <p class=”foo bar tar var”> 
  2.   some text
  3. </p>
Если же вам необходимо удалить некоторый класс из элемента, то применяйте метод removeClass. Также приятна возможность “переключать” класс (если класса нет, то он назначается, иначе удаляется из элемента). Для этого используйте метод “toggleClass”:
  1. $("div").addClass ('bar');
  2. $("div").removeClass ('bar');
  3. $("div").toggleClass ('foo');
Для того чтобы изменить содержимое некоторого узла (в классическом javascript вы применяли свойство innerHTML) в jquery следует использовать метод html. Если в качестве параметров ничего не указать то будет возвращено содержимое узла, в противном случае это содержимое будет замещено:
  1. alert ( $("h1#firstHeading").html() );
  2.  $("h1#firstHeading").html('<p>hello </p>');
На этом позвольте откланяться. В следующий раз мы будем продолжать знакомство с библиотекой jquery. Нас ждет работа с ajax и эффекты.