Введение в jQuery
Шевчук Антон

Содержание


Лекция 1. О HTML, CSS и JavaScript

Начнём знакомство с jQuery с повторения (или изучения) основ правильного употребления связки HTML и CSS с небольшой примесью JavaScript.

Если не хотите упасть в грязь лицом перед коллегами — то не пропускайте данную главу "мимо ушей".

HTML — о нём стоит помнить две вещи – семантический и правильный.

Семантическая вёрстка

Семантическая вёрстка HTML документа подразумевает использование тегов по прямому назначению, т.е. если вам необходим заголовок – то вот тег <h1> и собратья, необходима табличное представление данных – используйте тег <table> и только его.

Иногда, избавляясь от табличной верстки, доходит до абсурда, и тег <table> становится изгоем, и его перестают использовать даже для табличного представления данных, не стоит повторять эту ошибку.

Забегая чуть-чуть вперёд, стоит упомянуть теги из спецификации HTML5:

<article>, <aside>, <header>, <footer>, <menu>, <section> и т.д. — используйте их, не бойтесь.

Не бояться — это правильно, но использовать тоже надо с умом, рекомендую ресурс http://htmlbook.ru/html5 — очень хорошо и подробно расписано о нововведениях спецификации HTML5.

И еще парочка интересных ресурсов в нагрузку:

Старайтесь избегать избыточных элементов на странице, большинство HTML страниц грешат лишними блочными элементами:

<div id="header"> 
<div id="logo">
<h1><a href="/">Мой блог</a></h1> 
</div>
<div id="description">
<h2>Тут я делюсь своими мыслями</h2> </div>
</div>

Данную конструкцию можно легко упростить, и при этом код станет более читаемым, изменения в CSS будут минимальными (или даже не потребуются):

<header> 
<h1>
<a href="/">Мой блог</a>
</h1>
<h2>Тут я делюсь своими мыслями</h2> 
</header>

В английском языке есть термин "divits" – сим термином награждают HTML- разметку с чрезмерным использованием div’ов без потребности, я же обзываю такие творения "дивными". Обычно таким грешат новички, которые для применения стилей CSS оборачивают элементы в div’ы, что и приводит к их размножению без нужды.

Ещё одним обязательным пунктом для создания "правильного" HTML является использование названий классов и идентификаторов, которые однозначно говорят нам о содержимом элемента, а не о каких либо нюансах оформления, приведу пример:

Плохо
red, green и т.д.в какой-то момент захотите перекрасить, и элемент с классом "red" будет синего цвета
wide, small и т.д.сегодня широкий, а завтра?
h90w490наверное, это элемент с высотой 90px и шириной 490px, или я ошибаюсь?
b_1, ax_9эти название тоже ни о чем не говорят
color1, color2 и т.д.иногда встречается для "скинованных" сайтов, но создают такие классы из лени
element1...20такое тоже встречается, и ничем хорошим не пахнет

Ну и примеры правильного именования:

Хорошо
logo, contentлоготип, основной контент
menu, submenuменю и подменю
even, oddчётный и нечётный элементы списка
paginatorпостраничная навигация
copyrightкопирайт

Есть ещё один момент – это форматирование HTML и CSS кода, я не буду заострять на нём внимание, но весь код в книге будет отформатирован отступами, и, возможно, это даст свои плоды в ваших творениях.

Валидный HTML

Зеленый маркер W3C validator'а – это правильно, и к этому надо стремится, так что не забывайте закрывать теги, да прописывать обязательные параметры, приведу пример HTML кода, в котором допущено 6 ошибок (согласно спецификации HTML5), найдите их:

<h2>Lorem ipsum
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc urna metus, ultricies eu, congue vel, laoreet id, justo. Aliquam fermentum adipiscing pede. Suspendisse dapibus ornare quam. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. <p>
<a href="/index.php?mod=default&act=image"><img src="/img001.jpg"></a>

CSS-правила и селекторы

Теперь приступим к CSS, и начнём, пожалуй, с расшифровки аббревиатуры CSS это Cascading Style Sheets, дословно "каскадная таблица стилей", но:

— Почему же она называется каскадной? — этот вопрос я часто задаю на собеседованиях претендентам. Ответом же будет аналогия, ибо она незыблема как перпендикулярная лягушка: представьте себе каскад водопада, вот вы стоите на одной из ступенек каскада с чернильницей в руках, и выливаете ее содержимое в воду — вся вода ниже по каскаду будет окрашиваться в цвет чернил. Применив эту аналогию на HTML — создавая правило для элемента, вы автоматически применяете его на все дочерние элементы (конечно, не все правила так работают, но о них позже) — это наследование стилей. Теперь, если таких умников с чернильницей больше чем один, и цвета разные, то в результате получим смешение цветов, но это в жизни, а в CSS работают правила приоритетов, если кратко:

Если голова ещё не бо-бо, то я также упомяну, что при расчёте, чьи же правила главней, ещё анализируется специфичность селекторов, и тут считается следующим образом:

за каждый идентификатор получаем [1:0:0] (#id)

за каждый класс, либо псевдо класс — [0:1:0] (.my :pseudo)

за каждый тег — [0:0:1] (div a)

При этом [1:0:0] > [0:x:y] > [0:0:x].

Пример селекторов, выстроенных по приоритету (первые важнее):

#my p#id — [2:0:1] 
#my #id — [2:0:0] 
#my p — [1:0:1] 
#id — [1:0:0]
.wrapper .content p — [0:2:1]
.content div p — [0:1:2]
.content p — [0:1:1] 
p — [0:0:1]

Пример HTML-кода:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Приоритет CSS селекторов</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
	<script type="text/javascript" src="js/jquery.js"></script>
	<script type="text/javascript" src="js/code.js"></script>
    <script>
        function appendStyle(rule) {
            $('head').append('<style>'+rule+'</style>');
        }
    </script>
</head>
<body>
<div class="wrapper">
    <menu>
        <a href="css.float.html" title="go prev" class="button alignleft" rel="prev">← Prev </a>
        <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
        <a href="css.selectors.html" title="go next" class="button alignright" rel="next">Next →</a>
        <hr/>
		<h3>Шпаргалка</h3>
<pre><code><em>// weight</em>
#id    = [1,0,0]
.class = [0,1,0]
tag    = [0,0,1]
<em>// compare</em>
[1,0,0] > [0,1,0] > [0,0,1]
[1,0,0] > [0,100,100]
[0,1,0] > [0,0,100]
</code></pre>
        <hr/>
        <pre><code contenteditable="true">appendStyle(<span>'p { color:orange }'</span>)</code></pre>
        <button type="button" class="code">Run Code</button>
        <pre><code>appendStyle(<span>'.content p { color:green }'</span>)</code></pre>
        <button type="button" class="code">Run Code</button>
        <pre><code>appendStyle(<span>'article.content p { color:blue }'</span>)</code></pre>
        <button type="button" class="code">Run Code</button>
        <pre><code>appendStyle(<span>'.wrapper .content p { color:red }'</span>)</code></pre>
        <button type="button" class="code">Run Code</button>
        <pre><code>appendStyle(<span>'#id { color:darkblue }'</span>)</code></pre>
        <button type="button" class="code">Run Code</button>
        <pre><code>appendStyle(<span>'#my p { color:darkcyan }'</span>)</code></pre>
        <button type="button" class="code">Run Code</button>
        <pre><code>appendStyle(<span>'#my #id { color:darkgreen }'</span>)</code></pre>
        <button type="button" class="code">Run Code</button>
        <pre><code>appendStyle(<span>'#my p#id { color:black }'</span>)</code></pre>
        <button type="button" class="code">Run Code</button>
    </menu>
    <header>
        <h1>Приоритет CSS селекторов</h1>
        <h2>Сохраните страницу и попробуйте изменить CSS стили</h2>
    </header>
    <article id="my" class="content">
        <h2>Article Title</h2>
        <p id="id">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
        lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
        in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
        euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
        eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
        at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
        nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
        tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
        fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
        lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
    </article>
    <footer>
        ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
    </footer>
    <script type="text/javascript">
        var _gaq = _gaq || [];
        _gaq.push(['_setAccount', 'UA-1669896-2']);
        _gaq.push(['_trackPageview']);
        (function() {
         var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
         ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
         var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
        })();
    </script>
</div>
</body>
</html>
<div class="wrapper">
<div id="my" class="content"> 
<p id="id">
Lorem ipsum dolor sit amet, consectetuer...
</p>
</div>
</div>

При равенстве счета — последний главный.

Говорят, что правило с 255 классами будет выше по приоритету, нежели правило с одним "id", но я надеюсь, такого кода в реальности не существует

Вот такое краткое вступительное слово, но пора вернуться к jQuery. Так вот, работая с jQuery, вы должны "на отлично" читать правила CSS, а также уметь составлять CSS-селекторы для поиска необходимых элементов на странице. Но давайте обо всем по порядку, возьмём следующий простенький пример вполне семантического HTML:

<!DOCTYPE html>
<html dir="ltr" lang="en-US"> 
<head>
<meta charset="UTF-8"/> 
<title>Page Title</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/> 
<style type="text/css">
body {
font: 62.5%/1.6 Verdana, Tahoma, sans-serif; color: #333333;
}
h1, h2 {
color: #ff6600;
}
header, main, footer { margin: 30px auto; width: 600px;
}
#content { padding: 8px;
}
.box {
border:1px solid #ccc; border-radius:4px; box-shadow:0 0 2px #ccc;
}
</style>
</head>
<body>
<header>
<h1>Page Title</h1> 
<p>Page Description</p>
</header>
<main id="content" class="wrapper box"> <article>
<h2>Article Title</h2> <p>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc urna metus, ultricies eu, congue vel, laoreet...
</p>
</article>
<article>
<h2>Article Title</h2> <p>
Morbi malesuada, ante at feugiat tincidunt, enim massa gravida metus, commodo lacinia massa diam vel eros...
</p>
</article>
</main>
<footer>©copyright 2014</footer> 
</body>
</html>

Это пример простого и правильного HTML5 с небольшим добавлением CSS3. Давайте разберём селекторы в приведённом CSS-коде (я предумышленно не выносил CSS в отдельный файл, ибо так наглядней):

body – данные правила будут применены к тегу <body> и всем его потомкам

h1,h2 – мы выбираем теги <h1> и <h2>, и устанавливаем цвет шрифта #content – выбираем элемент с "id="content"" и применяем правила

.box – выбираем элементы с "class="box""

Теперь подробнее и с усложнёнными примерами:

h1ищем элементы по имени тега
#containerищем элемент по идентификатору "id=container" (идентификатор уникален, значит, на странице он должен быть только один)
div#containerищем <div> c идентификатором container, но предыдущий селектор работает быстрее, но этот важнее
.newsвыбираем элементы по имени класса "class="news""
div.newsвсе элементы <div> c классом news (так работает быстрее в IE8, т.к. в нём не реализован метод "getElementsByClassName()")
#wrap .postищем все элементы с классом post внутри элемента с "id = wrap"
.cls1.cls2выбираем элементы с двумя классами "class="cls1 cls2""
h1,h2,.postsперечисление селекторов, выберем всё перечисленное
.post > h2выбираем элементы <h2>, которые являются непосредственными потомками элемента с классом "post"
a + spanбудут выбраны все элементы <span> следующие сразу за элементом <a>
a[href^=http]будут выбраны все элементы <a> у которых атрибут "href" начинается с http(предположительно, все внешние ссылки)

Это отнюдь не весь список, описание же всех CSS3 селекторов можно найти на соответствующей страничке W3C: http://www.w3.org/TR/css3-selectors/

40% задач, которые вы будете решать с помощью jQuery, сводятся к поиску необходимого элемента на странице, так что знание CSS селекторов обязательно. Вот еще кусочек CSS для тренировки, напишите соответствующий ему HTML (это тоже вопрос с собеседования ;):

#my p.announce, .tt.pm li li a:hover+span { color: #f00; }

Пишите прям тут:

CSS. Погружение

Этот раздел будет полезен начинающим верстальщикам, и тем, кто захочет сделать чуть больше, нежели вывод окна сообщения по клику.

О форматировании

Нет, я не властен над собой, и таки приведу в качестве примера CSS форматирование, которое я использую:

/*header*/ 
header {
margin-bottom: 16px; 
font-weight: 400;
}
header h1 { 
color: #999;
}
header p {
font-size: 1.4em; 
margin-top: 0;
}
/*/header*/

Почему это хорошо:

Я не настаиваю на своём варианте, но хотелось, чтобы вы приняли на вооружение один из многих стандартов форматирования, и всегда следовали ему.

Именование классов и идентификаторов

Я уже затрагивал эту тему, когда рассказывал о релевантности HTML, так вот – имена классов могут быть даже такими: "b-service-list__column b-service-list__column_right" и это будет круто, и "must be" – но лишь в рамках действительно больших проектов, и собственно чего я распинаюсь, дам ссылки – информации из них хватит еще на одну книгу ;):

Обязательно изучите – полезно для расширения кругозора, и прокачки скилов

О цветах

В WEB используется цветовая модель RGB, следовательно, красный цвет можно записать не только как red, но и ещё несколькими способами:

p { color: red }
p { color: #ff0000 }
p { color: #f00 } /* сокращенная запись, экономит 3 байта */
p { color: rgb(255, 0, 0) }

Теперь вы без запинки должны назвать цвета #f00, #0f0, #00f, а те, у кого по рисованию было "отлично", назовут и #ff0, #0ff и #f0f ;)

С появлением CSS3, указывая цвет, мы также можем задать значение ?-канала, т.е. прозрачность:

p { color: rgba(255, 0, 0, 1) } /* обычный текст */
p { color: rgba(255, 0, 0, 0.5) } /* полупрозрачный текст */

Ещё одна примочка CSS3 – это возможность использования цветовой модели HSL (hue saturation lightness – тон, насыщенность и светлота) и HSLA (HSL + α-канал):

p { color: hsl(  0,	100%,	50%) }	/* красный */
p { color: hsl(120,	100%,	50%) }	/* зелёный */
p { color: hsl(240,	100%,	50%) } /* синий */
p { color: hsla( 0,	100%,	50%, 0.5) } /* полупрозрачный красный */

Для перевода из HSL в RGB существует простой алгоритм, но пока не стоит им себя грузить

Да кто этим HSL пользуется? Не морочьте себе голову.

Для тех, кого вопрос со смешанием каналов RGB поставил в тупик, то вот вам наглядное руководство:



Блочные и строчные элементы

Опять я буду ссылаться на чей-то учебник – на этот раз от Ивана Сагалаева - http://softwaremaniacs.org/blog/category/primer/, и пусть вас не смущают даты написания статей – они повествуют об основах и актуальность они не потеряют ещё очень долго

Возможно, вы ещё не знаете, но HTML теги делятся на блочные (block) и строчные (inline). Блочными элементами называют те, которые отображаются как прямоугольник, они занимают всю доступную ширину и их высота определяется содержимым. Блочные теги по умолчанию начинаются и заканчиваются новой строкой — это <div>, <h1> и собратья, <p> и другие.

Если хотите, чтобы ваш HTML оставался валидным, следите за тем, чтобы блочные элементы не располагались внутри строчных элементов. Внутри строчных тегов может быть либо текст, либо другие строчные элементы.

Одна из самых часто встречаемых ошибок, это оборачивание заголовка в ссылку: <a href="#"><h1>Название статьи</h1></a>, не допускайте подобные промахи

Хотя если мы ориентируемся на HTML5 – то тег <a> теперь может быть блочным элементом, и приведённый пример будет валидным. Ага, вот такой я не последовательный.

По теме:

О размерах блочных элементов

Ещё хотел отдельно остановиться на вычислении ширины и высоты блочных элементов, ведь тут есть один нюанс, по умолчанию, высота и ширина элементов считается без учёта толщины границ и внутренних отступов, т.е. как-то так:



Эта блочная модель называется "content-box", и вот в CSS3 появилась возможность изменять блочную модель, указывая атрибут "box-sizing".

Отлично, теперь мы можем выбирать между двумя значениями "content-box" и "border-box", первый я уже описал, а вот второй вычисляет высоту и ширину включая внутренние отступы и толщину границ:



Такая блочная модель была свойственна IE6 в "quirks mode"

Полезные статьи по теме:

Плавающие элементы

Я бы хотел ещё рассказать о CSS свойстве "float", но боюсь, рассказ будет долгим и утомительным, но если кратко: если вы указываете элементу свойство "float", то:

Это поведение "по умолчанию", как это выглядит в живую можно посмотреть на примере

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Свойство float в CSS</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <style>
        article {
            border: #999 1px solid;
            margin: 0 4px 4px;
        }
        article h2 {
            border: #999 1px solid;
            background: #fff;
        }
        article div {
            background: #ccffcc;
            font-size: 16px;
            padding: 10px;
        }
        article p {
            background: #ccffcc;
        }
        article span {
            background: #ccffcc;
        }
    </style>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
            <a href="html.example.html" title="go prev" class="button alignleft" rel="prev">← Prev </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="css.priority.html" title="go next" class="button alignright" rel="next">Next →</a>
        </menu>
        <header>
            <h1>Пример работы свойства float</h1>
            <h2>Смотрим... →</h2>
        </header>
        <article>
            <h2 style="float:right;">Article Title</h2>
            <div style="height:100px;">
                <div style="height:100px;"></div>
            </div>
        </article>
        <article>
            <h2 style="float:right;">Article Title</h2>
            <div style="height:100px;clear:both;">
                <div style="height:100px;clear:both;"></div>
            </div>
        </article>
        <article>
            <h2 style="float:right">Article Title</h2>
            <p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse 
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <article>
            <h2 style="float:right">Article Title</h2>
            <span>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</span>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Тут главное надо понимать происходящее, и уметь управлять, если конечно вы хотите хоть чуть-чуть научиться верстать :)

Жизненно необходимая информация для верстальщиков:

"Раскладка в CSS: float" [http://softwaremaniacs.org/blog/2005/12/01/css-layout-float/]

Позиционирование

Дам лишь вводную по "position" – у него бывает лишь четыре значения:

Для самостоятельного изучения:

"Раскладка в CSS: позиционирование" [http://softwaremaniacs.org/blog/2005/08/03/css-layout-positioning/]

Разделяй и властвуй

Тут стоит запомнить несколько простых правил:

Теперь приведу код, за который следует что-нибудь ломать (это пример плохого кода, уточняю для тех, кто не понимает намёков):

<script>

function doSomething(){ /* … */ }
/* раздается хруст сломанных костей запястья, чтобы не печатал */
</script>
<style>
p { line-height:20px; }
/* крхххх… берцовая кость, и на работу уже не пойдет */
</style>
<div style="color:red;font-size:1.2em">
<p onclick="doSomething();">Lorem ipsum dolor sit amet...</p>
<!-- тыдыщь, головой об стол… насмерть. как жест милосердия -->
</div>

Неясно, почему же это плохо? Похоже, вам просто не приходилось менять дизайн для уже готового сайта :) Проясню суть проблемы: вам ставят задачу – "надо поменять цвет шрифта для всех страниц сайта", коих может быть три десятка. Это могут быть не только HTML-файлы, а страницы какого-то шаблонизатора, разбросанные по двум десяткам папок (и это еще не самый плохой вариант). И тут появляется он — красный абзац. Вероятность услышать "слова поддержки" в адрес автора сего кода будет стремиться к единице. Насчет inline-обработчиков событий ситуация похожа, вот представьте себе — пишите вы JavaScript код, всё отлично, всё получается, пока вы не пытаетесь кликнуть по красному абзацу, он оказывается вам не подвластен, и живёт своей собственной жизнью, игнорируя все ваши усилия. Вы смотрите код, и опять кто-то услышит эти слова...

Применив четыре правила "красного абзаца" у вас должен будет получиться чистый и предсказуемый HTML код:

<div id="abzac">
<p>Lorem ipsum dolor sit
amet...</p> </div>

Стили можно будет легко повесить на <div> с идентификатором, как собственно и обработчик событий для нашего параграфа.

Абзац не параграф, но для красного словца, и лёгкости усвоения сгодится

Немного о JavaScript

В данный раздел я вынес ту информацию о JavaScript, которую необходимо знать, чтобы у вас не возникало "детских" проблем с использованием jQuery. Если у вас есть опыт работы с JavaScript — то листайте далее.

Изучать хотите JavaScript и jQuery? Так силу познайте инструмента истинного:

Список не полон, но console там есть, применять её надо уметь

О форматировании

Хотел бы сразу обратить внимание на форматирование JavaScript кода. Мой опыт мне подсказывает – лучше всего спроецировать стандарты форматирования основного языка разработки на прикладной – JavaScript, а если вы хотите чего-нить глобального, то я для вас уже погуглил:

В довесок поделюсь небольшим советом: все переменные, содержащие объект jQuery, лучше всего именовать, начиная с символа "$". Поверьте, такая небольшая хитрость экономит много времени.

И ещё – в конце каждой строки я ставлю точку с запятой, сам JavaScript этого не требует, но и лишним не будет.

Основы JavaScript

Переменные

Первое с чем столкнёмся – это объявление переменных:

var name = "Ivan"; 
var age = 32;

Всё просто, объявляем переменную, используя ключевое слово "var". Можно, конечно же, и без него, но делать я вам это настоятельно не рекомендую, т.к. могут возникнуть непредвиденные проблемы (о чём чуть позже расскажу).

На имена переменных наложено два ограничение:

И ещё, регистр букв имеет значение:

var company = "Facebook";
// совсем другая "компания" 
var Company = "Google";
Константы

В JavaScript'е нет констант, но поскольку необходимость в них всё же есть, то появилась договорённость: переменные, набранные в верхнем регистре через подчёркивание, не изменять:

var USER_STATUS_ACTIVE = 1; 
var USER_STATUS_BANNED = 2;

Константы необходимы, чтобы избежать появления "magic numbers", сравните следующий код "if(status==2)" – о чём тут речь мало кому будет понятно, а вот код "if(status==USER_STATUS_BANNED)" уже более информативный

Типы данных

В JavaScript не так уж и много типов данных:

Во втором примере нас может ожидать сюрприз, если кто определит переменную "undefined", как обойти такую "неприятность" я ещё расскажу

Массивы

Массив – это коллекция данных с числовыми индексами. Данные могут быть любого типа, но я приведу самый простой массив со строками:

var users = ["Ivan", "Petr", "Serg"]

Нумерация массивов начинается с "0", так что для получения первого элемента вам потребуется следующий код:

alert(users[0]);  // выведет Ivan

Размер массива хранится в свойстве length:

alert(users.length);  // выведет 3
a[3] = "Danylo"; 
alert(users.length); // выведет 4

В действительности "length" возвращает индекс последнего элемента массива+1, так что не попадитесь:

var a = []; 
a[4] = 10;
alert(a.length); // выведет 5;

Для перебора массива лучше всего использовать цикл "for(;;)":

for (var i = 0; i < users.length; i++) {
alert(users[i]);  // последовательно выведет Ivan, Petr и Serg
}

Для работы с последними элементами массива следует использовать методы "push()" и "pop()":

users.push("Sidorov"); // добавляем элемент в конец массива var 
sidorov = users.pop(); // удаляем и возращаем последний элемент

Для работы с первыми элементами массива следует использовать методы "unshift()" и "shift()":

users.unshift("Sidorov"); // добавляем элемент в начало массива var 
sidorov = users.shift(); // удаляем и возращаем первый элемент

Последние два метода работают медленно, т.к. перестраивают весь массив

Функции

С функциями в JavaScript'е всё просто, вот вам элементарный пример:

function hello() {
alert("Hello world");
}

Просто, пока не заговорить об анонимных функциях…

Анонимные функции

В JavaScript можно создавать анонимную функцию (т.е. функцию без имени), для этого достаточно слегка изменить предыдущую конструкцию:

function() {
alert("Hello world");
}

Так как функция это вполне себе объект, то её можно присвоить переменной, и (или) передать в качестве параметра в другую функцию:

var myAlert = function(name) 
{ alert("Hello " + name);
}
function helloMike(myFunc) { // тут функция передаётся как параметр myFunc("Mike");
}
helloMike(myAlert);

Анонимную функцию можно создать и тут же вызвать с необходимыми параметрами:

(function(name) { alert("Hello " + name);
})("Mike");

Это не сложно, скоро вы к ним привыкните, и вам их будет недоставать в других языках.

Объекты

На объекты в JavaScript возложено две роли:

Первое предназначение можно описать следующим кодом:

var user = {
name: "Ivan", age: 32
};
alert(user.name); // Ivan
alert(user.age); // 32

Это фактически реализация key-value хранилища, или хэша, или ассоциативного массива, или …, ну вы поняли, названий много, но в JavaScript'е это объект, и запись выше – это JSON – JavaScript Object Notation (хоть и с небольшими оговорками).

Для перебора такого хранилища можно использовать цикл "for(.. in ..)":

for (var prop in user) {
alert(prop + "=" + user[prop]); // выведет name=Ivan 
								// затем age=32
}

С объектами, конструкторами и т.д. в JavaScript посложнее будет, хотя для понимания не так уж и много надо, запоминайте: любая функция вызванная с использованием ключевого слова "new" возвращает нам объект, а сама становится конструктором данного объекта:

function
 User(name)
 { this.name 
 = name;
this.status = USER_STATUS_ACTIVE;
}
var me = new User("Anton");

Поведение функции "User()" при использовании "new" слегка изменится:

  1. Данная конструкция создаст новый, пустой объект
  2. Ключевое слово "this" получит ссылку на этот объект
  3. Функция выполнится и возможно изменит объект через "this" (как в примере выше)
  4. Функция вернёт "this" (по умолчанию)

Результатом выполнения кода будет следующий объект:

me = { name: "Anton", status: 1 };
Область видимости и чудо this

Для тех, кто только начинает своё знакомство с JavaScript я расскажу следующие нюансы:

Всё что касается "window" относится лишь к браузерам, но поскольку книга о jQuery, то иное поведение я и не рассматриваю, но вот так прозрачно намекаю, что оно есть ;)

Замыкания

Изучив замыкания, можно понять много магии в JavaScript’e. Приведу пример кода с пояснениями:

var a = 1234;
var myFunc = function(){ var b = 4321;
var c = 1111; return function() {
return ((a+b)/c);
};
};
var anotherFunc = myFunc(); // myFunc возвращает анонимную функцию 
							// с "замкнутыми" значениями c и b
alert(anotherFunc()); // => 5

Что же тут происходит: функция, объявленная внутри другой функции, имеет доступ к переменным родительской функции. Повтыкайте в код, пока вас не осенит, о чём я тут толкую.

Рекомендуемые статьи по теме:

Вводная по JavaScript затянулась, лучше почитайте: http://learn.javascript.ru/

Лекция 2. Подключаем, находим, готовим

Базу подготовили, и теперь пора перейти к непосредственному изучению jQuery. Всё начинается с подключения библиотеки. И уже на этом этапе мы можем пойти несколькими путями:

  1. Скачиваем jQuery с домашней странице проекта (http://jquery.com/) и положим рядышком с нашей HTML страничкой (советую скачать development версию — всегда интересно покопаться в исходном коде:):
    <head>
    <script type="text/javascript" 
    src="js/jquery.js"></script> </head>
    
  2. Данный способ хорош для работы в offline, или при медленном соединении с интернетом. Отдельно стоит обратить внимание на путь — скрипты в отдельной папке, и это не случайно, нужно приучать себя к порядку.
  3. Используем CDN (предпочитаю сервис от Google, хотя есть и Microsoft и Яндекс, последний, кстати, размещает даже популярные плагины, за что команде Яндекса отдельное спасибо, и ещё универсальный http://cdnjs.com/):
    <head>
    <script type="text/javascript" 
    src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery. 
    min.js"></script>
    </head>
    
  4. Небольшие пояснения: CDN достаточно умная штука, при таком запросе библиотеки jQuery вам вернутся HTTP заголовки, в которых будет сказано, что "протухнет" этот файл лишь через год. Если же вы запросите файл по адресу "jquery/1.10/jquery.min.js", то вам вернётся последняя доступная версия библиотеки из ветки 1.10 — на момент написания сих строк это была версия 1.10.2, при этом в заголовках "expires" будет стоять текущая дата, и кэш будет жить лишь час. Есть CDN предоставляемый и самими разработчиками jQuery, но он отнюдь не такой продвинутый как у Google, и на моей памяти у него были проблемы со стабильностью, так что будьте аккуратней при работе с ним – http://code.jquery.com/
  5. С использованием менеджера пакетов bower.io устанавливаем искомую библиотеку. Данный менеджер пакетов позволяет устанавливать очень много разнообразных библиотек и пакетов, зацените список – http://sindresorhus.com/bower-components/

Зачем я упоминаю про данный менеджер пакетов? Ну может кто из вас окажется ну очень любопытным и осилит работу с ним самостоятельно :)

Будь готов

Теперь пора приступить к работе — возьмём какой-нибудь элемент на страничке и попробуем его изменить. Для этого в <head> вставим следующий код (пример странички ищите ранее):

<script>
//	мы пытаемся найти все элементы <h2> на странице 
//	и изменить цвет шрифта на красный 
jQuery("h2").css("color", "red"); </script>

Только подобный код ничего не сделает, так как, на момент выполнения, на странице ещё не будет тегов <h2>, слишком рано выполняется скрипт, до загрузки всего HTML документа. Для того, чтобы код сработал верно, мы должны либо поместить код в самый низ страницы (главное после искомого <h2>), либо использовать функцию "ready()" для отслеживания события "load" нашего "document":

<script>
//	ждём загрузки всего документа 
//	после этого будет выполнена анонимная функция 
//	которую мы передали в качестве параметра 
jQuery(document).ready(function(){ 	
jQuery("h2").css("color", "red"); 
}); 
</script>

Также можно использовать сокращённый вариант без явного вызова метода ready():

<script>
$(function(){
$("h2").css("color", "red");
});
</script>

Вы можете создать сколь угодно много подобных функций, не обязательно весь необходимый функционал помещать в одну.

$() — это синоним для jQuery(), чтобы у вас не возникало конфликтов с другими странами библиотеками за использование $, советую ваш код оборачивать в анонимную функцию следующего вида (best practice):

(function($, undefined){
//	тут тихо и уютно 
//	мы всегда будем уверены, что $ === jQuery 
//	a undefined не переопределен ;) 
})(jQuery); 

Наглядный код:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример работы с document.ready</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript">
        // nothing, tag h2 is not exists
        jQuery("article h2").css("font-size", "24px");
        // it's working
        jQuery(document).ready(function(){
            jQuery("article h2").css("background-color", "green");
        });
        // it's too
        $(function(){
            $("article h2").css("color", "#fff");
        });
        // anonymous function - best practices
        (function($, undefined){
            $(function(){
                $("p").css("color", "#966");
            });
        })(jQuery);
    </script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu>
            <a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <hr/>
<pre><code><em>// nothing, tag h2 is not exists</em>
jQuery(<span>"article h2"</span>).css(
    <span>"font-size"</span>,
    <span>"24px"</span>
);

<em>// it's working</em>
jQuery(document).ready(function(){
    jQuery(<span>"article h2"</span>).css(
        <span>"background-color"</span>,
        <span>"green"</span>
    );
});

<em>// it's too</em>
$(function(){
    $(<span>"article h2"</span>).css(<span>"color"</span>, <span>"#fff"</span>);
});

<em>// anonymous function - best practices</em>
(function($, undefined){
    $(function(){
        $(<span>"p"</span>).css(<span>"color"</span>, <span>"#966"</span>);
    });
})(jQuery);</code></pre>
        </menu>
        <header>
            <h1>$(document).ready()</h1>
            <h2>Простой пример использования</h2>
        </header>
        <article>
            <h2>Article Title</h2>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article Title</h2>
            <p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse 
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Селекторы

Как я уже говорил ранее, в поиске элементов на странице заключается практически половина успешной работы с jQuery. Так что приступим к поискам по документу (чтобы не листать, пусть пример HTML будет и тут):

<!DOCTYPE html>
<html dir="ltr" lang="en-US"> <head>
<meta charset="UTF-8"/> <title>Page Title</title>
</head>
<body>
<header>
<h1>Page Title</h1> <p>Page Description</p>
</header>
<div id="content" class="wrapper box"> 
<article>
<h2>Article Title</h2>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc urna metus, ultricies eu, congue vel, laoreet...</p>
</article>
<article>
<h2>Article Title</h2>
<p>Morbi malesuada, ante at feugiat tincidunt, enim massa gravida metus, commodo lacinia massa diam vel eros...</p>
</article>
</div>
<footer>©copyright 2014</footer> </body>
</html>

А теперь приступим к выборкам — выбор элементов по "id" либо "className" аналогично используемым в CSS:

$("#content")	//	выбираем элемент с id=content
$("div#content")	//	выбираем div с id=content (хотя и без div работает)
$(".wrapper")	//	выбираем элементы с class=wrapper
$("div.wrapper")	//	выбираем div'ы с class=wrapper
$(".wrapper.box") //	выбираем элементы с class=wrapper и box
$("h2")	//	выбираем все теги h2
$("h1, h2")	//	выбираем все теги h1 и h2

Используйте валидные имена классов и идентификаторов

Теперь вспомним, что мы в DOMе не одни, это таки иерархическая структура:

$("article h2") // выбираем все теги h2 внутри тега article 
$("div article h2") // выбираем все теги h2 внутри тега article
					// внутри тега div, в доме который построил Джек
$("article").find("h2")	// аналогично примерам выше
$("div").find("article").find("h2")  //

У нас есть соседи:

$("h1 + p") // выбор всех p элементов, перед которыми есть h1 // элементы (у нас только один такой)
$("#stick ~ article")  // выбор всех article элементов после элемента
						// c id=stick
$("#stick").prev() // выбор предыдущего элемента от найденного 
$("#stick").next() // выбор следующего элемента от найденного

Родственные связи:

$("*")	// выбор всех элементов
$("article > h2")	// выбираем все теги h2 которые являются
						// непосредственными потомками тега article
$("article > *")	// выбор всех потомков элементов article
$("article").children()
$("p").parent() // выбор всех прямых предков элементов p 
$("p").parents() // выбор всех предков элементов p (не понадобится) 
$("p").parents("div") // выбор всех предков элемента p которые есть div
					  // parents принимает в качестве параметра селектор

Если хотите поиграться с селекторами от души — то для этого я припас для вас соответствующую страничку

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>CSS селекторы, игровая площадка</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <style>
        #content {
            padding: 2px;
        }
        #content:before {
            color:#777;
            font-size: 14px;
            content: '<div id="content" class="wrapper box">'
        }
        #content:after {
            color:#777;
            font-size: 14px;
            content: '</div>'
        }
        header {
            background: #fff;
            padding: 2px;
            margin: 20px;
            border-left: 1px solid #ddd;
        }
        header:before {
            color:#777;
            font-size: 12px;
            content: "<header>"
        }
        header:after {
            color:#777;
            font-size: 12px;
            content: "</header>"
        }
        article {
            background: #fff;
            padding: 2px;
            margin: 20px;
            border-left: 1px solid #ddd;
        }
        article:before{
            color:#777;
            font-size: 12px;
            content: "<article>"
        }
        article#stick:before{
            content: '<article id="stick" class="box">'
        }
        article:after{
            color:#777;
            font-size: 12px;
            content: "</article>"
        }
        footer {
            background: #fff;
            margin: 12px 8px;
        }
        footer:before{
            color:#777;
            font-size: 12px;
            content: "<footer>"
        }
        footer:after{
            color:#777;
            font-size: 12px;
            content: "</footer>"
        }
        p {
            background: #fff;
            margin: 12px 8px;
        }
        p:before{
            color:#777;
            font-size: 12px;
            content: "<p>"
        }
        p:after{
            color:#777;
            font-size: 12px;
            content: "</p>"
        }
        h1 {
            background: #fff;
        }
        h1:before{
            color:#777;
            font-size: 12px;
            content: "<h1>"
        }
        h1:after{
            color:#777;
            font-size: 12px;
            content: "</h1>"
        }
        h2 {
            background: #fff;
            border: 0 !important;
        }
        h2:before{
            color:#777;
            font-size: 12px;
            content: "<h2>"
        }
        h2:after{
            color:#777;
            font-size: 12px;
            content: "</h2>"
        }
    </style>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/jquery.color.js"></script>
    <script type="text/javascript">
        $(function(){
            $('button').click(function(){
                var fnc = $(this).data('fnc');
                if (fnc) {
                    var els = eval(fnc);
                    els
                      .stop(true,true)
					  .animate({'background-color':'#ff5'}, 500)
					  .delay(100)
					  .animate({'background-color':'#fff'}, 500)
                }
            });
            $('button.code').click(function(){
                var code = $(this).prev().text();
                if (code) {
                    var els = eval(code);
                        els
                          .stop(true,true)
                          .animate({'background-color':'#ff5'}, 500)
                          .delay(100)
                          .animate({'background-color':'#fff'}, 500)
                }
            });
        });
    </script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">

            <a href="css.priority.html" title="go prev" class="button alignleft" rel="prev">← Prev </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="sizzle.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
            <h3><a href="css.selectors-test.html">Тест на смекалку</a></h3>
            <hr/>
<pre><code contenteditable="true">$(<span>'p'</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
            <hr/>
            <button type="button" data-fnc="$('#content')">$('#content')</button>
            <button type="button" data-fnc="$('div#content')">$('div#content')</button>
            <hr/>
            <button type="button" data-fnc="$('.wrapper')">$('.wrapper')</button>
            <button type="button" data-fnc="$('.box')">$('.box')</button>
            <button type="button" data-fnc="$('.wrapper.box')">$('.wrapper.box')</button>
            <button type="button" data-fnc="$('article.box')">$('article.box')</button>
            <hr/>
            <button type="button" data-fnc="$('h2')">$('h2')</button>
            <button type="button" data-fnc="$('h1, h2')">$('h1, h2')</button>

            <hr/>

            <button type="button" data-fnc="$('article h2')">$('article h2')</button>
            <button type="button" data-fnc="$('div article h2')">$('div article h2')</button>
            <button type="button" data-fnc="$('article').find('h2')">$('article').find('h2')</button>
            <button type="button" data-fnc="$('div').find('article').find('h2')">$('div').find('article').find('h2')</button>

            <hr/>

            <button type="button" data-fnc="$('h1+h2')">$('h1 + h2')</button>
            <button type="button" data-fnc="$('#stick ~ article')">$('#stick ~ article')</button>
            <button type="button" data-fnc="$('#stick').prev()">$('#stick').prev()</button>
            <button type="button" data-fnc="$('#stick').next()">$('#stick').next()</button>

            <hr/>

            <button type="button" data-fnc="$('*')">$('*')</button>
            <button type="button" data-fnc="$('article > h2')">$('article > h2')</button>
            <button type="button" data-fnc="$('article > *')">$('article > *')</button>
            <button type="button" data-fnc="$('article').children()">$('article').children()</button>

            <hr/>

            <button type="button" data-fnc="$('p').parent()">$('p').parent()</button>
            <button type="button" data-fnc="$('p').parents()">$('p').parents()</button>
            <button type="button" data-fnc="$('p').parents('div')">$('p').parents('div')</button>

        </menu>
        <header>
            <h1><a href="#">Page Title</a></h1>
            <h2>Page Description</h2>
        </header>
        <article id="stick" class="box">
            <h2>Article</h2>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Поиск по атрибутам

Ещё со времён CSS2 была возможность найти элемент с определёнными атрибутами, в CSS3 добавили ещё возможностей по поиску:

a[href] — все ссылки с атрибутом "href"

a[href=#] — все ссылки с "href=#"

a[href~=#] — все ссылки с "#" где-то в "href"

a[hreflang|=en] — все ссылки, для которых hreflang начинается с "en" и обрезается по символу "-" — "en", "en-US", "en-UK"

a[href^=http] — ссылки начинающиеся с "http"

a[href*="google.com"] — ссылки на погуглить

a[href$=.pdf] — ссылки на PDF файлы (по идее)

Заглянув внутрь jQuery вы скорей всего найдёте то место, где ваше выражение будет анализироваться с помощью регулярных выражений, по этой причине в селекторах необходимо экранировать специальные символы используя обратный слеш "\\":

$("a[href^=\\/]").addClass("internal");

Поиск по дочерним элементам

Хотелось бы еще обратить внимание на селекторы из спецификации CSS3[http://www.w3.org/TR/css3-selectors/] — много интересных:

:first-child — первый дочерний элемент

:last-child — последний дочерний элемент

:nth-child(2n+1) — выборка элементов по несложному уравнению подробнее можно прочитать в статье "Как работает nth-child" [http://web-standards.ru/articles/nth-child/]

:not(…) — выбрать те, что не подпадают под вложенную выборку

Но поскольку не все браузеры знакомы с CSS3-селекторами, то мы можем использовать jQuery для назначения стилей:

$("div:last-child").addClass("last-paragraph");

Sizzle

Пропустите это раздел, и вернитесь к нему тогда, когда вас заинтересует, как происходит поиск элементов внутри "$"

В качестве "поисковика" по элементам DOM'а jQuery использует библиотеку Sizzle. Данная библиотека одно время была неотъемлемой частью jQuery, затем "отпочковалась" в отдельный проект, который с радостью использует как сам jQuery, так и Dojo Toolkit. Но хватит лирики, давайте перейдем непосредственно к поиску, для оного в JavaScript'е предусмотрены следующие методы (не в jQuery, не в Sizzle, в JavaScript'е):

getElementById(id) — поиск по "id="…""

getElementsByName(name) — поиск по "name="name"" и

"id="name"" getElementsByClassName(class) — поиск по

"class="class"" getElementsByTagName(tag) — поиск по тегу

querySelectorAll(selector) — поиск по произвольному CSS селектору

Пробежавшись беглым взглядом по списку, можно заметить метод "querySelectorAll()" – он универсален и действительно удобен, да, именно этот метод библиотека пытается вызвать, когда вы скармливаете что-то в качестве селектора в jQuery, но данный метод иногда нас подводит, и тогда на сцену выходит Sizzle во всей красе, вооруженный всеми упомянутыми методами, да еще со своим уникальным арсеналом:

if (document.querySelectorAll) 
(function(){ var oldSelect = select
/* ... */
select = function( selector, context, results, seed, xml ) {
//	используем querySelectorAll когда нет фильтров в запросе, 
//	когда это запрос не по xml объекту, 
//	и когда не обнаружено проблем с запросом 
//	еще есть пару проверок, которые я опустил для наглядности 
try { 
push.apply( results, 
slice.call(context.querySelectorAll( selector ), 0) 
); 
return results; 
} catch(qsaError) { /* подвёл, опять, ну сколько можно */ } 
/* ... */ 
// выход Sizzle
return oldSelect( selector, context, results, seed, xml );
};
});

Но давайте уже рассмотрим, как Sizzle ищет в DOM'е, если таки метод "querySelectorAll()" споткнулся. Начнём с разбора алгоритма работы на следующем примере:

$("thead > .active, tbody > .active")
  1. Получить первое выражение до запятой: "thead > .active"
  2. Разбить на кусочки: "thead", ">", ".active"
  3. Найти исходное множество элементов по последнему кусочку (все ".active")
  4. Фильтровать справа налево (все ".active" которые находятся непосредственно в "thead")
  5. Искать следующий запрос после запятой (возвращаемся к первому пункту)
  6. Оставить только уникальные элементы в результате

Глядя на данный алгоритм вы должны заметить, что правила читаются справа на лево!

Копаем глубже. При анализе даже самого маленького выражения есть несколько нюансов на которые стоит обратить внимание – первый – это приоритет поиска, он различен для различных браузеров (в зависимости от их возможностей, или "невозможностей"):

order: new RegExp( "ID|TAG" + 
(assertUsableName ? "|NAME" : "") + 
(assertUsableClassName ? "|CLASS" : "")
)

Не обращайте внимание на RegExp – это внутренняя кухня Sizzle

Таким образом, рассматривая выражение "div#my", Sizzle найдёт вначале элемент с "id="my"", а потом уже проверит на соответствие с <div>. Хорошо, это вроде как фильтрация, и она тоже соблюдает очерёдность – это второй нюанс:

preFilter: {
"ATTR": function (match) { /* ... */ },
"CHILD": function (match) { /* ... */ },
"PSEUDO": function (match) { /* ... */ },
},
filter: {
"ID": function (id) { /* ... */ },
"TAG": function (nodeName) { /* ... */ },
"CLASS": function (className) { /* ... */ },
"ATTR": function (name, operator, check) { /* ... */ },
"CHILD": function (type, argument, first, last) { /* ... */ },
"PSEUDO": function (pseudo, argument, context, xml) { /* ... */ }
}

Третий нюанс – это относительные фильтры, которые работают сразу с двумя элементами (они вам знакомы по CSS селекторам):

relative: {
">": { dir: "parentNode", first: true },
" ": { dir: "parentNode" },
"+": { dir: "previousSibling", first: true },
"~": { dir: "previousSibling" }
},

Ой, зачем я вас всем этим гружу? Почитайте лучше об оптимизации запросов абзацем ниже.

Официальная документация по библиотеки Sizzle доступна на GitHub'е проекта:

Оптимизируем выборки

Ну перво-наперво вам следует запомнить, что

результаты поиска не кэшируются, каждый раз, запрашивая элементы по селектору, вы инициируете поиск элементов снова и снова

Взглянув на алгоритм работы Sizzle, сходу напрашиваются несколько советов об оптимизации по работе с выборками:

  1. Сохранять результаты поиска (исходя из постулата выше):
    // было
    $("a.button").addClass("active");
    /* ... */
    $("a.button").click(function(){ /* ... */ });
    // стало
    var $button = $("a.button");
    $button.addClass("active");
    /* ... .*/
    $button.click(function(){ /* ... */ });
    Правильная IDE о подобных вещах знает, и будет вам время от времени подсказывать ;)
  2. Или использовать цепочки вызовов (что по сути аналогично первому правилу):
    // было
    $("a.button").addClass("active");
    $("a.button").click(function(){ /* ... */ });
    // стало
    $("a.button").addClass("active")
    .click(function(){ /* ... */ });
  3. Использовать "context" (это такой второй параметр при выборе по селектору):
    // было
    $(".content a.button");
    // стало
    $("a.button", ".content");
    $(".content").find("a.button",); // чуток быстрее
  4. Разбивать запрос на более простые составные части используя "context", и сохранять промежуточные данные (как следствие из правил №1 и №3)
    // было
    $(".content a.button");
    $(".content h3.title");
    // стало
    var $content = $(".content")
    $content.find("a.button");
    $content.find("h3.title");
  5. Использовать более "съедобные" селекторы дабы помочь функции "querySelectorAll()", т.е. если у вас нет уверенности в правильности написания селектора, или сомневаетесь в том, что все браузеры поддерживают необходимый CSS селектор, то лучше разделить "сложный" селектор на несколько более простых:
    // было
    $(".content div input:disabled");
    // стало
    $(".content div").find("input:disabled");
  6. Не использовать jQuery, а работать с "native" функциями JavaScript'а

Есть ещё один пункт – выбирать самый быстрый селектор из возможных, но тут без хорошего багажа знаний не обойтись, так что дерзайте, пробуйте и присылайте ваши примеры.

Для наглядности лучше всего взглянуть на сравнительный тест sizzle.html(данный тест был изначально разработан Ильёй Кантором для мастер-класса по JavaScript и jQuery):

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <script>
        function query() {
            document.querySelectorAll('.my > div');
        }

        function jqueryQsa() {
            $('.my > div');
        }

        function slow() {
            // to disable native FF query
            document.querySelectorAll = null;
            slow = function() {
                $('.my > div');
            }
            slow()
        }

        function findMore() {
            $('.my').find('>div');
        }

        function context() {
            $('>div', $('.my'));
        }

        function context2() {
            $('>div', '.my');
        }

        function fast() {
            $('.my').children('div');
            //$.grep($('.my').children(), function(a) { return a.tagName=='DIV' }).length
        }

        function custom() {

            var arr = []

            var mys = document.getElementsByClassName('my'), i=mys.length

            while(i--) {
                // children[i] is very slow, sibling loop is fast
                var child = mys[i].firstChild;
                while(child) {
                    if(child.tagName == 'DIV') arr.push(child)
                    child = child.nextSibling
                }
            }
        }
        function runAllTests() {
            var f = {
                'query': "querySelectorAll('.my > div')",
                'jqueryQsa':"$('.my > div') [with QSA]",
                'slow':"$('.my > div') [without QSA]",
                'findMore':"$('.my').find('>div')",
                'context':"$('>div', $('.my'))",
                'context2':"$('>div', '.my')",
                'fast':"$('.my').children('div')",
                'custom':'no jQuery, no QSA'
            };
            var text = '';
            for(var key in f) {
                var d = new Date();
                for(var j=0;j<100;j++) {
                    window[key]()
                }
                text = text + f[key]+": "+(new Date-d)+" ms\n";
            }
            $('menu pre code').text('window.location.reload()');
            return text;
        }
    </script>
</head>
<body>

<div id="content" class="wrapper box">
        <menu label="Try...">
            <a href="css.selectors.html" title="go prev" class="button alignleft" rel="prev">← Prev </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
            <pre data-out="1"><code>runAllTests()</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>.my > div</h1>
            <h2>Тестирование производительности различных способов получения искомого набора элементов</h2>
        </header>
        <div id="output">
            <h3>Output</h3>
            <pre></pre>
        </div>
        <article>
            <p>Для надёжности каждая функция вызывается сотню раз, после запуска всех тестов необходимо перегрузить страницу</p>
<table>
<tr>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
<td class='my'>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
  <p>p</p><div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div><div>!</div></div>
</td>
</tr>
</table>

        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);

            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Маленькая хитрость от создателей jQuery – запросы по id элемента не доходят до Sizzle, а скармливаются "document.getElementById()" в качестве параметра:

$("#content") -> document.getElementById("content");

Примеры оптимизаций

Выбор по идентификатору элемента – самый быстрый из возможных, старайтесь использовать оный:

// внутри одна регулярочка + getElementById()
$("#content")
// а вот так ещё быстрее
$(document.getElementById("content"))
// но экономия незначительна
// а удобство использования стремится к нулю

Селектор "div#content" работает на порядок медленнее, нежели поиск лишь по идентификатору – "#content", но и он имеет право на существование в случае, если ваш скрипт используется на нескольких страницах, а логика требует лишь обрабатывать поведение для элемента <div>. Данный селектор можно представить в двух вариантах:

// getElementById() + фильтрация
$("#content").filter("div");
// оставляем как есть и надеемся на QuerySelectorAll()
$("div#content");

В результате тестирования получаем следующий расклад:

Выводы делаем сами.

Лекция 3. Атрибуты элементов и CSS

В предыдущих примерах мы уже изменяли CSS-свойства DOM-элементов, используя одноименный метод "css()", но это далеко не всё. Теперь копнём поглубже, чтобы не штурмовать форумы банальными вопросами ;)

Копать начнём с более досконального изучения метода "css()":

css(property) — получение значения CSS свойства

css(property, value) — установка значения CSS свойства

css({key: value, key:value}) — установка нескольких значений

css(property, function(index, value) { return value }) — тут для установки значения используется функция обратного вызова (в просторечии — callback-функция), index это порядковый номер элемента в выборке, value — старое значение свойства

Метод "css()" возвращает текущее значение, а не прописанное в CSS файле по указанному селектору

Примеры использования:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример манипуляций над CSS</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
            <a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="class.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
            <pre><code contenteditable="true">alert($(<span>'p'</span>).css(<span>'color'</span>))</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code contenteditable="true">$(<span>'p'</span>).css(<span>'color'</span>,<span>'red'</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code contenteditable="true">$(<span>'p'</span>).css({
    <span>'color'</span>:<span>'green'</span>,
    <span>'background-color'</span>:<span>'#000'</span>
})</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code contenteditable="true">$(<span>'p'</span>).css({
    color:<span>'#0F0F0F'</span>,
    backgroundColor:<span>'#fff'</span>,
    width:<span>'auto'</span>
})</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code contenteditable="true">$(<span>'p'</span>).css(<span>'width'</span>, function(i, v){
    return parseFloat(v) * 1/(i+1)
})</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример манипуляций над CSS</h1>
            <h2>Пробуем... →</h2>
        </header>
        <article>
            <h2>Article Title</h2>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article Title</h2>
            <p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse 
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
                var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
                ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
                var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>
$("#my").css('color') // получаем значение цвета шрифта
$("#my").css('color', 'red') // устанавливаем значение цвета шрифта
// установка нескольких значений
$("#my").css({
'color':'red',
'font-size':'14px',
'margin-left':'10px'
})
// альтернативный способ
$("#my").css({
color:'red',
fontSize:'14px',
marginLeft:'10px',
})
// используя функцию обратного вызова
$("#my").css('height', function(i, value){
return parseFloat(value) * 1.2;
})

Ну, вроде с CSS разобрались, хотя нет — стоит ещё описать манипуляции с классами, тоже из разряда первичных навыков:

addClass(className) — добавление класса элементу

addClass(function(index, currentClass){ return className }) — добавление класса используя функцию обратного вызова

hasClass(className) — проверка на причастность к определённому классу

removeClass(className) — удаление класса

removeClass(function(index, currentClass){ return className }) — удаление класса используя функцию обратного вызова

toggleClass(className) — переключение класса

toggleClass(className, switch) — переключение класса по флагу switch

toggleClass(function(index, currentClass, switch){ return className }, switch) — переключение класса используя функцию обратного вызова

В приведённых функциях в качестве className может быть строка содержащая список классов через пробел.

Мне ни разу не приходилось использовать данные функции с функциями обратного вызова, и лишь единожды пригодился флаг switch, так что не заморачивайтесь всё это запоминать, да и в дальнейшем, цитируя руководство по jQuery, я буду сознательно опускать некоторые "возможности"

Но хватит заниматься переводом официальной документации, перейдём к наглядным примерам (class.html):

// добавляем несколько классов за раз
$("#my").addClass('active notice')
// переключаем несколько классов
$("#my").toggleClass('active notice')
// работает вот так (похоже на классовый XOR):
<div id="my" class="active notice"> > <div id="my" class="">
<div id="my" class="active"> > <div id="my" class="notice">
<div id="my" class=""> > <div id="my" class="active notice">
// аналогично предыдущему примеру
$("#my").toggleClass('active')
$("#my").toggleClass('notice')
// проверяем наличие класса(-ов)
$("#my").hasClass('active')
// удаляем несколько классов за раз
$("#my").removeClass('active notice')

Также, стоит вспомнить, что у DOM элементов бывают атрибуты отличные от класса, и мы их тоже можем изменять, для этого нам потребуются следующие методы:

Атрибуты – это всё то, что мы видим внутри угловых скобочек, когда пишем HTML код:

<!-- В данном примере это href, title, class -->
<a href="#top" title="anchor" class="simple">To Top</a>

Атрибуты, с которыми вам чаще других придётся сталкиваться:

// получение альтернативного текста картинки
var altText = $('img').attr('alt')
// изменение адреса картинки
$('img').attr('src', '/images/default.png')
// работаем со ссылками
$('a#my').attr({
'href':'http://anton.shevchuk.name',
'title':'My Personal Blog',
});

Кроме атрибутов, также есть свойства элементов, к ним относится "selectedIndex", "tagName", "nodeName", "nodeType", "ownerDocument", "defaultChecked" и "defaultSelected". Ну вроде бы список невелик, можно и запомнить. Для работы со свойствами используем функции из семейства "prop()":

prop(propName) — получение значения свойства

prop(propName, propValue) — установка значения свойства (также можно использовать hash, либо функцию обратного вызова)

removeProp(propName) — удаление свойства (скорей всего никогда не понадобится)

А теперь выключите музыку, и запомните следующее – для отключения элементов формы, и для проверки/изменения состояния чекбоксов мы всегда используем функцию "prop()", пусть вас не смущает наличие одноименных атрибутов в HTML (это я про "disabled" и "checked"), используем "prop()" и точка:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример работы с атрибутами и свойствами элементов</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <script>
        $(function(){
            var status = function() {
                var $input = $("#check");
                out("$('#check').attr('checked'): " + $input.attr('checked') + "\n"
                          + "$('#check').prop('checked'): " + $input.prop('checked') + "\n"
                          + "$('#check').is(':checked'): " + $input.is(':checked'))
            };
            $("#check").change(status);
            $(".code").click(status);
        });
    </script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
            <a href="class.html" title="go prev" class="button alignleft" rel="prev">← Prev </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="height.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
            <h3>attr()</h3>
            <pre><code contenteditable="true"><em>// "check"</em>
$(<span>'#check'</span>).attr(<span>'checked'</span>, <span>true</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code contenteditable="true"><em>// "uncheck"</em>
$(<span>'#check'</span>).attr(<span>'checked'</span>, <span>false</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
            <h3>prop()</h3>
            <pre><code contenteditable="true"><em>// check</em>
$(<span>'#check'</span>).prop(<span>'checked'</span>, <span>true</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code contenteditable="true"><em>// uncheck</em>
$(<span>'#check'</span>).prop(<span>'checked'</span>, <span>false</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code contenteditable="true"><em>// disable checkbox</em>
$(<span>'#check'</span>).prop(<span>'disabled'</span>, <span>true</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code contenteditable="true"><em>// enable checkbox</em>
$(<span>'#check'</span>).prop(<span>'disabled'</span>, <span>false</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Свойства и атрибуты</h1>
            <h2>Почувствуй разницу между attr() и prop()</h2>
        </header>
        <div id="output">
            <h3>Output</h3>
            <pre></pre>
        </div>
        <article>
            <p>
                <input id="check" type="checkbox" checked="checked">
                <label for="check">Check me</label>
            </p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Лекция 4. События

Прежде чем приступить к прочтению данной главы, стоит определиться, что же из себя представляют события web-страницы. Так вот – события – это любые действия пользователя, будь то ввод данных с клавиатуры, проматывание страницы или передвижения мышки, и конечно же "клики".

А ещё существуют события создаваемые скриптами, и их обработчики – триггеры и хэндлеры, но о них чуть позже.

jQuery работает практически со всеми событиями в JavaScript'е, приведу список оных с небольшими пояснениями:

change — изменение значения элемента (значение, при потери фокуса, элемента отличается от изначального, при получении фокуса)

click — клик по элементу (порядок событий: "mousedown", "mouseup", "click")

dblclick — двойной щелчок мышки

resize — изменение размеров элементов

scroll — скроллинг элемента

select — выбор текста (только для "input[type=text]" и "textarea")

submit — отправка формы

focus — фокус на элементе - актуально для "input[type=text]", но в современных браузерах работает и с другими элементами

blur — фокус ушёл с элемента — актуально для "input[type=text]" — срабатывает при клике по другому элементу на странице или по событию клавиатуры (к примеру переключение по tab'у)

focusin — фокус на элементе, данное событие срабатывает на предке элемента, для которого произошло событие "focus"

focusout — фокус ушёл с элемента, данное событие срабатывает на предке элемента, для которого произошло событие "blur"

keydown — нажатие клавиши на клавиатуре

keypress — нажатие клавиши на клавиатуре (keydown → keypress → keyup)

keyup — отжатие клавиши на клавиатуре

load — загрузка элемента (например <img>)

unload — выгрузка элемента (например "window")

mousedown — нажатие клавиши мыши

mouseup — отжатие клавиши мыши

mousemove — движение курсора

mouseenter — наведение курсора на элемент, не срабатывает при переходе фокуса на дочерние элементы

mouseleave — вывод курсора из элемента, не срабатывает при переходе фокуса на дочерние элементы

mouseover — наведение курсора на элемент

mouseout — вывод курсора из элемента

Опробовать события можно на примере с событиями мышки и элементами формы. Для большинства событий существуют "shorthand" методы, так для отслеживания "click" можно использовать "click()" :)

Вызов большинства из перечисленных событий можно эмулировать непосредственно из самого скрипта:

<script>
$("#menu li a").click()
// или используя метод trigger $("#menu li a").trigger("click")
</script>

Теперь стоит рассказать немного и об обработчиках событий, для примера возьму код строкой выше, и слегка его модифицирую:

$("#menu li a").click(function(event){ 
alert("Hello!")
})

Теперь кликнув по ссылке вы увидите приветствие и после закрытия оного браузер перейдет по ссылке указанной в атрибуте "href". Но это не совсем то, что мне хотелось – надо было лишь вывести текст, и никуда не уходить. Ага, для этого стоит отменить действие по умолчанию:

$("#menu li a").click(function(event){
alert("Hello!"); 
event.preventDefault();
})

Теперь перехода нет, т.к. метод "preventDefault()" предотвращает данное действие. Но вот если кто-то повесит обработчик на само меню?

$("#menu").click(function(event){ 
alert("Menu!");
})

В результате мы получим два сообщения, но почему? Если у вас возникает подобный вопрос, значит вы еще не знакомы с тем, как обрабатываются события. Попробую кратенько дать вводную, когда вы кликаете на элементе в DOM дереве, то происходит "погружение" события – т.е. вначале все родительские элементы могут обработать "клик", и лишь потом он доберётся до элемента по которому был совершён, но и это еще не всё, затем событие начинает проделывать обратный путь – "всплывает", давая тем самым второй шанс родительским элементам обработать событие.

Но не так всё гладко, у нас же есть IE, который принципиально не работает с "погружением", поэтому все решили идти по пути наименьшего сопротивления и обрабатывают события лишь на этапе "всплытия".

Рекомендую к прочтению "Порядок срабатывание событий" из учебника Ильи Кантора

Хорошо, вроде бы понятно, теперь вернёмся к нашему примеру, и пытаемся понять что же у нас происходит – у нас есть обработчик клика для ссылки и непосредственно для самого меню, в котором эта ссылка находится. Теперь кликая по ссылке, срабатывает обработчик события на ссылке, и затем событие всплывает до меню, и срабатывает его обработчик события "click". Но это не совсем желаемый результат, и для борьбы с подобным вредительством, необходимо останавливать "всплытие" событий:

$("#menu li a").click(function(event){
alert("Hello!");
event.preventDefault();
event.stopPropagation();
})

Для ускорения разработки в jQuery есть быстрый способ вызова этих двух методов за раз:

$("#menu li a").click(function(event){
return false; // вот это он :)
})

Теперь у вас есть достаточный багаж знаний, чтобы легко манипулировать событиями на странице. Хотя я добавлю еще немного — для того, чтобы сработал лишь ваш обработчик события, можно использовать метод "stopImmediatePropagation()":

$("#menu li a").click(function(event){
alert("Hello!");
event.stopImmediatePropagation();
return false;
})
$("#menu li a").click(function(event){
alert("Hello again!");
return false;
})

В данном примере, при клике по ссылке будет выведено лишь одно сообщение. И да, порядок имеет значение.

Учимся рулить

Мы уже успели познакомиться с методом "click()", в действительности этот метод представляет из себя обёртку для вызова "on()" и "trigger()":

if (arguments.length > 0) {
this.on("click", null, data, fn ) :
} else {
this.trigger("click");
}

Ой, код я чуть-чуть изменил — для читаемости, если же любопытство восторжествует, то ищите в исходном коде по строке "dblclick"

Ну так давайте же попробуем без этих обёрток:

// вешаем обработчик
$('.class').on('click', function(){
// что-то делаем
});
// вызываем обработчик
$('.class').trigger('click');
// отключаем обработчик
$('.class').unbind('click');

Можно повесить обработчик событий практически на любой объект:

// проще некуда
var obj = {
test:function() {
console.log('obj.test');
}
}
// создаём обработчик произвольного события someEvent
$(obj).on('someEvent', function(){
console.log('obj.someEvent');
this.test();
});
// инициируем событие someEvent
$(obj).trigger('someEvent');
// полюбопытствуем
console.log(obj);

Скопируйте приведенный код в консоль и запустите, я думаю вам будет интересно ;)

Пространство имен

Как вы уже узнали, когда мы хотим создать/удалить свой обработчик событий, мы пишем следующий код:

// создаем свой обработчик
$('.class').on('click', function(){
// что-то делаем
});
// удаляем все обработчики
$('.class').unbind();

Но как всегда, есть ситуации когда нам необходимо отключить не все обработчики (как пример, надо отключить обработку какого-то контрола определенным плагином), в этом случае нам на помощь приходят пространства имен, использовать их достаточно легко:

// создаём обработчик
$('.class').on('click.namespace', function(){
// что-то делаем
});
// вызываем обработчик
$('.class').trigger('click.namespace');
// вызываем все обработчики без пространства имён
$('.class').trigger('click!');
// удаляем все обработчики click в данном пространстве имён
$('.class').unbind('click.namespace');

Еще примерчик, вешаем обработчик, который выводит текст в консоль:

$('.class').on('click.namespace', function(){
console.log('bang');
});
// вызываем событие, наш обработчик сработает
$('.class').trigger('click.namespace');
// тоже работает
$('.class').trigger('click');
// событие из другого пространства имён, наш обработчик не будет вызван
$('.class').trigger('click.other');

Также, есть поддержка нескольких пространств имён:

$('.class').on('click.a.b', function(){
// для пространства имён a и b
});
// вызываем обработчик из пространства a
$('.class').trigger('click.a');
// отменяем обработчик click для пространства b
$('.class').unbind('click.b');

Можно одним махом удалить все обработчики с определенного пространства имен:

// обработчик клика
$('.class').on('click.namespace', function(){});
// обработчик фокус
$('.class').on('blur.namespace', function(){});
// передумали, и все отменили
$('.class').unbind('.namespace');

Официальная документация скудна на этот счёт, и я надеюсь мой пример поможет лучше разобраться в данном вопросе.

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>События и пространства имён</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <script type="text/javascript">
        $(function(){
            $('#myButton').click(function(event){
                return false;
            });
        });
    </script>
	<style>
		article ul {
			margin: 20px;
			float:left;
			list-style: none
		}
		article a {
			display: block;
			padding: 4px 8px;
			border:1px solid #456;
			border-radius: 2px;
			-moz-border-radius: 2px;
			-webkit-border-radius: 2px;
		}
	</style>
</head>
<body>
    <div id="content" class="wrapper box">
        <div id="output">
            <h3>Output</h3>
            <pre></pre>
        </div>
        <menu label="Try...">

			<a href="events.form.html" title="go prev" class="button alignleft" rel="prev">← Prev </a>
			<a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
			<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
<pre><code>$(<span>'#myButton'</span>).on(<span>'click'</span>, function(){
    appendOut(<span>"click\n"</span>); })</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'#myButton'</span>).on(<span>'click.space'</span>, function(){
    appendOut(<span>"click.space\n"</span>); })</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'#myButton'</span>).on(<span>'click.my'</span>, function(){
    appendOut(<span>"click.my\n"</span>); })</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'#myButton'</span>).on(<span>'click.my.space'</span>, function(){
    appendOut(<span>"click.my.space\n"</span>); })</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'#myButton'</span>).trigger(<span>'click'</span>)</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'#myButton'</span>).trigger(<span>'click!'</span>)</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'#myButton'</span>).trigger(<span>'click.my!'</span>)</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'#myButton'</span>).off(<span>'.space'</span>)</code></pre>
<button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Работа с пространством имён</h1>
            <h2>Это уже круто знать и использовать</h2>
        </header>
        <article>
            <a href="#" id="myButton" class="button">#myButton</a>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

"Живые" события

тут немного забегу вперёд, так что если чего стало непонятно, отложите данный раздел "на потом".

Стоит обратить внимание на еще одну задачку, которая очень часто ставится перед разработчиком – это добавление обработчиков событий для элементов, которые добавляются на страницу динамически. Пожалуй, надо привести пример подобной задачи:

"У нас есть HTML страница, на которой все внутренние ссылки будут подгружаться AJAX’ом, данное утверждение справедливо и для подгружаемого HTML’а тоже"

Первое условие решается просто:

$('a[href^=\\/]').on('click', function() {
var url = $(this).attr('href');
$('body').load(url + ' body > *');
return false;
});

Для наглядности, условимся, что внутренние ссылки содержат относительные пути от корня сайта.

Со вторым условием чуть-чуть посложнее ситуация, но тоже вполне решаема:

$('body').on('click', 'a[href^=\\/]', function() {
var url = $(this).attr('href');
$('body').load(url + ' body > *');
return false;
});

Отличий не так уж и много, проясню происходящее:

Работа данной схемы базируется на "всплытии" событий, так что используя метод "event.stopPropagation()" вы сможете предотвратить выполнение "живых" обработчиков

Лирическое отступление к истории: жил да был когда-то плагин для jQuery, назывался "live()", позволял он вешать обработчики на элементы DOM дерева которых ещё нет (подгружаемые AJAX’ом или ещё как), а потом он умер его внесли в само ядро. Метод "live()" к тому времени работал лишь с "document". Затем появился метод "delegate()" который научился вешать обработчик на произвольный элемент, а затем и он был поглощён методом "on()". Так что не пугайтесь сильно, если встретите старый метод "live()", адаптировать под новые версии jQuery его будет не так уж и сложно (ну я на это надеюсь)

Неявным бонусом от использования "живых" событий можно считать возможность оптимизации, о "подходящих" случаях я и расскажу.

Случай первый, банальный – представьте себе таблицу на тысячу строк да десяток столбцов, а теперь попытайтесь подсчитать, сколько памяти скушают обработчики события "click" для каждой ячейки? Вот-вот, стоит это переписать в один обработчик:

$('table').on('click', 'td', function() { /* ... */ }

Случай второй, надуманный – необходимо записывать действия пользователя на странице, т.е. отслеживать клики по бессчётному количеству объектов:

$('body').on('click', '*', function() {
console.info("Click on "+this.tagName);
});

Пример работы данного "надуманного" варианта

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Оптимизация в обработке событий</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <style>
        #content {
            padding: 2px;
        }
        #content:before {
            color:#777;
            font-size: 14px;
            content: '<div id="content" class="wrapper box">'
        }
        #content:after {
            color:#777;
            font-size: 14px;
            content: '</div>'
        }
        header {
            background: #fff;
            padding: 2px;
            margin: 20px;
            border-left: 1px solid #ddd;
        }
        header:before {
            color:#777;
            font-size: 12px;
            content: "<header>"
        }
        header:after {
            color:#777;
            font-size: 12px;
            content: "</header>"
        }
        article {
            background: #fff;
            padding: 2px;
            margin: 20px;
            border-left: 1px solid #ddd;
        }
        article:before{
            color:#777;
            font-size: 12px;
            content: "<article>"
        }
        article#stick:before{
            content: '<article id="stick" class="box">'
        }
        article:after{
            color:#777;
            font-size: 12px;
            content: "</article>"
        }
        footer {
            background: #fff;
            margin: 12px 8px;
        }
        footer:before{
            color:#777;
            font-size: 12px;
            content: "<footer>"
        }
        footer:after{
            color:#777;
            font-size: 12px;
            content: "</footer>"
        }
        p {
            background: #fff;
            margin: 12px 8px;
        }
        p:before{
            color:#777;
            font-size: 12px;
            content: "<p>"
        }
        p:after{
            color:#777;
            font-size: 12px;
            content: "</p>"
        }
        h1 {
            background: #fff;
        }
        h1:before{
            color:#777;
            font-size: 12px;
            content: "<h1>"
        }
        h1:after{
            color:#777;
            font-size: 12px;
            content: "</h1>"
        }
        h2 {
            background: #fff;
            border: 0 !important;
        }
        h2:before{
            color:#777;
            font-size: 12px;
            content: "<h2>"
        }
        h2:after{
            color:#777;
            font-size: 12px;
            content: "</h2>"
        }
    </style>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <script type="text/javascript">
        $(function(){
            $('body').on('click', '*', function(e) {
                var targ;
                if (e.target) targ = e.target;
                else if (e.srcElement) targ = e.srcElement;
                if (targ.nodeType == 3) // defeat Safari bug
                    targ = targ.parentNode;
                if (targ == this) {
                    appendOut("Click on "+this.tagName + "\n");
                } else {
                    appendOut(" > "+this.tagName + "\n");
                }
            });
        });
    </script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
            <a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
            <h3>Покликайте по элементам</h3>
            <h3>Объясните, что за дополнительная информация выводится на экран</h3>
        </menu>
        <div id="output">
            <h3>Output</h3>
            <pre></pre>
        </div>
        <header>
            <h1><a href="#">Events optimization</a></h1>
            <h2>Page Description</h2>
        </header>
        <article id="stick" class="box">
            <h2>Article</h2>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Touch события

Смартфоны с большим сенсорным экраном — это уже норма жизни, и любому web-разработчику рано или поздно потребуется разрабатывать интерфейсы с поддержкой "touch" событий. На этот случай в JavaScript'е предусмотрены следующие события:

touchstart — событие схоже с "mousedown", происходит при касании пальцем экрана

touchend — убираем палец с экрана, ака "mouseup"

touchmove — водим пальцем по экрану — "mousemove"

touchcancel — странное событие, отмена "touch" до того, как палец был убран

О том как с ними работать, можно подчерпнуть из статьи Touching and Gesturing on iPhone, Android, and More [http://www.sitepen.com/blog/?p=3425] (хоть рассказ там и о Dojo Toolkit).

В jQuery Mobile работа с touch событиями идёт "из коробки". Чтобы jQuery UI заставить работать с touch событиями следует использовать библиотеку jQuery UI Touch Punch [http://touchpunch.furf.com/] Пробуйте, но учтите, без touch устройства разработка интерфейсов для подобных устройств – нонсенс (англ. nonsense).

Лекция 5. Анимация

Библиотека jQuery позволяет очень легко анимировать DOM элементы, для этого предусмотрено несколько функций, но обо всём по порядку, начнём с простого "hide()" и "show()", эти два метода соответственно скрывают либо отображают элементы:

// скроем все картинки
$('img').hide();
// теперь вернём их на место
$('img').show();

Данные вызовы оперируют лишь CSS атрибутом "display" и переключают его из текущего состояния в none и обратно. В качестве первого параметра можно задать скорость анимации, для этого можно использовать одно из зарезервированных слов "slow" или "fast", либо же указывать скорость в миллисекундах (1000 мс = 1 сек):

// медленно спускаемся с горы и… скрываем все картинки
// slow == 600
// fast == 200
$('img').hide('slow');
// теперь вернём их на место, чуть быстрее
$('img').show(400);

В таком случае, исчезновение элементов будет сопровождаться анимацией атрибутов "width", "height", "opacity" и прочих. Смотрите пример:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример анимации с использованием методов Hide/Show/Toggle</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">

            <a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="slide.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
<pre><code contenteditable="true">$(<span>'article img'</span>).hide()</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true">$(<span>'article img'</span>).show()</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true">$(<span>'article img'</span>).hide(<span>'slow'</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true">$(<span>'article img'</span>).show(<span>'slow'</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true">$(<span>'article img'</span>).toggle()</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true">$(<span>'article img'</span>).toggle(<span>'slow'</span>, function(){
    $(this).toggle(<span>'slow'</span>)
})</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Анимация</h1>
            <h2>Hide/Show/Toggle</h2>
        </header>
        <article id="stick" class="box">
            <h2>Article</h2>
            <p>
                <img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200"/>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-chamomile-tumb.jpg" alt="Chamomile" class="left" width="200"/>
            Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-maple-leaf-tumb.jpg" alt="Maple Leaf" class="left" width="200"/>
            Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
            laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
            non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
            ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
            sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
            vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
            magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
            cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>
</example>
<p>В довесок к этим двум методам есть еще метод "toggle()", он работает как
переключатель "hide → show" или "show → hide".</p>
<p>Теперь идём немножко дальше – вторым параметром в приведенных методах
может быть callback-функция – она будет выполнена по окончанию анимации
элементов:</p>
<example type="listing">// скрываем все картинки
$('img').hide('slow', function(){
// опосля отображаем alert
alert("Images was hidden");
});

Приведу иллюстрацию для наглядности процесса анимации:

анимация show()


Рис. 5.1.  анимация show()

Анимацию атрибутов height, width и opacity видно невооружённым взглядом, в действительности же это далеко не всё, заглянув внутрь jQuery можно увидеть, что так же изменяются внутренние и внешние отступы – "padding" и "margin" – так что не стоит об этом забывать.

Идём дальше – у нас на очереди набор методов из семейства slide – "slideUp()", "slideDown()" и "slideToggle()". Их поведение схоже с предыдущими функциями, но анимация будет затрагивать лишь высоту блоков – смотрим пример (ну и иллюстрации так же есть):

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример анимации с использованием методов семейства Slide</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>s
    <script src="js/jquery.js"></script>
    <script src="js/code.js"></script>
    <script>
        function changeClass() {
            $(this).prev().toggleClass('active')
        }
        $(function(){
            $('article h2').click(function(){
                $(this).next().slideToggle();
                $(this).toggleClass('active');
            });
        });
    </script>
</head>
<body>
    <div id="content" class="wrapper box slides">
        <menu label="Try...">

            <a href="hide.html" title="go prev" class="button alignleft" rel="prev">← Prev </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="fade.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
<pre><code><em>// callback function</em>
function changeClass() {
    $(this).prev().toggleClass(<span>'active'</span>)
}</code></pre>
<pre><code contenteditable="true">$(<span>'article p'</span>).slideUp(<span>'slow'</span>, changeClass)</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true">$(<span>'article p'</span>).slideDown(<span>'slow'</span>, changeClass)</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true">$(<span>'article p'</span>).slideToggle(<span>'slow'</span>, changeClass)</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Анимация</h1>
            <h2>SlideUp/SlideDown/SlideToggle</h2>
        </header>
        <article id="stick" class="box">
            <h2>Article</h2>
            <p>
                <img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200"/>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-chamomile-tumb.jpg" alt="Chamomile" class="left" width="200"/>
            Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-maple-leaf-tumb.jpg" alt="Maple Leaf" class="left" width="200"/>
            Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam, 
            laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
            non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
            ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
            sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
            vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
            magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
            cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

анимация slideDown()


Рис. 5.2.  анимация slideDown()

Прежде чем перейти к десерту упомяну семейство функций fade – они манипулируют лишь "opacity":

fadeIn(duration, callback) – изменяет "opacity" от 0 до предыдущего

fadeOut(duration, callback) – изменяет "opacity" от текущего до 0

fadeToggle(duration, callback) – переключатель между "In" и "Out"

fadeTo(duration, opacity, callback) – изменяет значение "opacity" до требуемого значения

А теперь самое сладкое – все эффекты анимации в jQuery крутятся вокруг метода "animate()". Данная функция берет один или несколько CSS-свойств элемента и изменяет их от исходного до заданного за N-ое количество итераций (количество итераций зависит от указанного времени, но не реже одной итерации в 13мс, если я правильно накопал это значение). Ну что-же, от слов к делу, попробуем реализовать функции "fadeIn()" и "fadeout()" с помощью "animate()" (см. пример):

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример анимации с методом animate()</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">

            <a href="fade.html" title="go prev" class="button alignleft" rel="prev">← Prev </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="animate.queue.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
<pre><code contenteditable="true"><em>// fadeOut()</em>
$(<span>'article img'</span>).animate({
    <span>'opacity'</span>:<span>'hide'</span>
})</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true"><em>// fadeIn()</em>
$(<span>'article img'</span>).animate({
    <span>'opacity'</span>:<span>'show'</span>
})</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true"><em>// resize to</em>
$(<span>'article img'</span>).animate({
    <span>'opacity'</span>:0.5,
    <span>'height'</span>:<span>'50px'</span>,
    <span>'width'</span>:<span>'250px'</span>
})</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true"><em>// change current</em>
$(<span>'article img'</span>).animate({
    <span>'opacity'</span>:<span>'-=0.1'</span>,
    <span>'height'</span>:<span>'+=10px'</span>
})</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true"><em>// change current</em>
$(<span>'article img'</span>).animate({
    <span>'width'</span>:<span>'+=50%'</span>
})</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Анимация</h1>
            <h2>Без обёрток, лишь используя animate()</h2>
        </header>
        <article id="stick" class="box">
            <h2>Article</h2>
            <p>
                <img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200"/>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-chamomile-tumb.jpg" alt="Chamomile" class="left" width="200"/>
            Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-maple-leaf-tumb.jpg" alt="Maple Leaf" class="left" width="200"/>
            Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
            laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
            non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
            ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
            sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
            vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
            magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
            cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>
// fadeOut()
$('article img').animate({
'opacity':'hide'
})
// fadeIn()
$('article img').animate({
'opacity':'show'
})

Всё просто, давайте-ка теперь усложним задачу – изменим размер блоков и прозрачность:

// значения указанных свойств будут плавно изменяться
// от текущих до заданных
$('article img').animate({
'opacity':0.5,
'height':'50px',
'width':'250px'
})

Как видите – тоже несложно, теперь попробуем отталкиваться от текущих значений, а не задавать необходимые:

// изменяем, шаг за шагом
$('article img').animate({
'opacity':'-=0.1',
'height':'+=10px'
})

Поигрались и хватит, пора усложнить вам жизнь – у функции "animate()" может быть более одного параметра, и пора приступить к их разбору. Набор параметров может быть разным, приведу первый, тот, что попроще:

params – CSS свойства – с этим мы уже познакомились

duration – скорость анимации – тоже упоминалась ранее, указывается в миллисекундах, или используя ключевые слова "fast" или "slow"

easing – указываем какую функцию будем использовать для изменения значений

callback – функция, которая будет вызвана после окончания анимации

Из приведённых параметров нам только "easing" не встречался ранее – я его берёг на сейчас – этот параметр указывает, какая функция будет использоваться для процесса анимации значений. Это могут быть линейные, квадратичные, кубически и любые другие функции. "Из коробки" мы можем выбрать лишь между "linear" и "swing":

easing "linear"


Рис. 5.3.  easing "linear"

easing "swing"


Рис. 5.4.  easing "swing"

Заглянув в код jQuery мы легко найдём соответствующий код:

linear: function(p) {
return p;
},
swing: function(p) {
return 0.5 - Math.cos( p*Math.PI ) / 2;
}

p – коэффициент прохождения анимации, изменяется от 0 до 1

Сложно? Хотите больше и сразу? Тогда ищите easing plugin на странице http://gsgd.co.uk/sandbox/jquery/easing/, он действительно из разряда "must have".

Подключайте и используйте одну из трёх десятков функций easing (наглядно, с иллюстрациями – animate.easing.html, а так же http://easings.net/)

Но давайте вернёмся к функции "animate()", которая в качестве параметров может принимать ещё один набор параметров, который уже не будет казаться таким простым:

params – CSS свойства (уже было)

options – тут целый набор возможностей, часть уже описывалась ранее:

duration – скорость анимации

easing – функция ("linear" или "swing")

complete – функция, которая будет вызвана после окончания анимации

step – функция, которая будет вызвана на каждом шаге анимации, о ней расскажу чуть ниже

queue – флаг/параметр очереди, чуть позже опишу подробнее

specialEasing – хэш в котором можно описать какую именно easing функцию следует использовать для изменения определённых параметров

Step-by-step

Хотелось бы отдельно остановиться на функции "step", и для наглядности приведу пример реализации подобной функции, которая отображает текущее значение анимированного параметра:

var customStep = function(now, obj) {
obj.elem; // объект анимации
obj.prop; // параметр, который анимируется
obj.start; // начальное значение
obj.end; // конечное значение
obj.pos; // коэффициент, изменяется от 0 до 1
obj.options; // опции настроек анимации
now; // текущее значение анимированного параметра, вычисляется как
// now = (obj.end - obj.start) * obj.pos
$(this).html(obj.prop +': '+now+obj.unit); // вывод текста
}
$("#box").animate({height: "+=10px"}, {step: customStep});

Мне ни разу не приходилось использовать step-функции, лишь только для примера

В очередь…©

Немного об очередности работы функции "animate()" – большинство читателей, наверное, уже знакомо с организацией последовательной анимации – для этого мы можем использовать цепочку вызовов:

$('#box ')
// говорим что меняем
.animate({left:'+=100'})
// следующий вызов добавляется в очередь на выполнение
.animate({top:'+=100'})

Для параллельного запуска анимации, необходимо будет внести следующие изменения:

$('#box')
// говорим что меняем
.animate({left:'+=100'})
// следующий вызов будет игнорировать очередь
.animate({top:'+=100'}, {queue:false})

Есть ещё чудесная функция "stop()", которая позволяет остановить текущую анимацию на полпути, а так же почистить очередь при необходимости. Для обеспечения различного поведения функции, она принимает три параметра:

queue – имя очереди; для работы с очередью анимации "fx" данный параметр опускаем ("fx" – очередь по умолчанию)

clearQueue – флаг очистки очереди

jumpToEnd – применить результат анимации али нет

// останавливаем выполнение текущей анимации
$('#box').stop();
// останавливаем выполнение текущей анимации
// и всех последующих (чистим очередь)
$('#box').stop(true);
// останавливаем выполнение текущей анимации и всех последующих
// но применяем результат текущей
$('#box').stop(true, true);
// останавливаем выполнение только текущей анимации
// и применяем её результат
$('#box').stop(false, true);

Пример есть, и требует ваших проб и ошибок:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример работы очередей анимации</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <style>
        article {
            position: relative;
            height: 600px;
        }
        #box {
            background: #55f;
            position: relative;
            margin: 10px;
            width: 80px;
            height: 80px;
        }
        #box span {
            display: inline-block;
            background: #fff;
            font-size: 22px;
            line-height: 22px;
            text-align: center;
            width: 16px;
            margin: 6px 8px;
            padding: 2px;
            border: 1px solid #000;
            border-radius: 2px;
        }
        .red {
            color:red;
        }
    </style>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">

            <a href="animate.html" title="go prev" class="button alignleft" rel="prev">← Prev </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="animate.game.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
<pre><code contenteditable="true"><em>// with queue</em>
$(<span>'#box'</span>).animate({
    <span>'left'</span>:<span>'+=100'</span>
}, 2000).animate({
    <span>'top'</span>:<span>'+=100'</span>
}, 2000)</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true"><em>// without queue</em>
$(<span>'#box'</span>).animate({
    <span>'left'</span>:<span>'-=100'</span>
}, 2000).animate({
    <span>'top'</span>:<span>'-=100'</span>
}, {queue:false, duration:2000})</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true"><em>// stop current</em>
$(<span>'#box'</span>).stop()</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true"><em>// stop all animation</em>
$(<span>'#box'</span>).stop(true)</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true"><em>// stop current animation, goto end, clear queue</em>
$(<span>'#box'</span>).stop(true, true)</code></pre>
            <button type="button" class="code">Run Code</button>
<pre><code contenteditable="true"><em>// stop current animation</em>
$(<span>'#box'</span>).stop(false, true)</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Очередь</h1>
            <h2>Пример работы с очередью и без анимировании элементов</h2>
        </header>
        <article>
            <div id="box">
                <span>♠</span>
                <span>♣</span>
                <span class="red">♥</span>
                <span class="red">♦</span>
            </div>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Заметка на будущее: если вы сделали выпадающее меню, и поигравшись с мышкой оно продолжает выпадать и исчезать, то значит надо вставить "stop()" в обработчик события

По умолчанию вся анимация над объектом складывается в очередь "fx", но с версии 1.7 можно указывать произвольную очередь:

$('#box')
.animate({'left':'-=100'}, {queue:'x'}) // составляем очередь X
.dequeue('x') // запускаем очередь X
$('#box').stop('x') // останавливаем анимацию в очереди X

Для чего нам может понадобиться произвольная очередь? Да для распараллеливания анимации, чтобы мы могли запустить одну очередь анимации, и в любой другой момент запустить другую очередь, возможно, это заклад под игры? Но чего гадать, давайте пример посмотрим

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Работа с произвольными очередями</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <script>
        $(function(){
            var logX = $("#log-x");
            var logY = $("#log-y");
            var $box = $("#box");
            logX.text($box.css('left'));
            logY.text($box.css('top'));
            var stepX = function(now, obj) {
                logX.text(Math.floor(now)+obj.unit)
            };
            var stepY = function(now, obj) {
                logY.text(Math.floor(now)+obj.unit)
            };
            $(document).keydown(function(e){
                // 37 - left
                // 38 - up
                // 39 - right
                // 40 - down
                switch (e.keyCode) {
                    case 37:
                        $box.stop('x', true);
                        $box.animate({'left':'-=100'}, {queue:'x', step:stepX}).dequeue('x');
                        break;
                    case 38:
                        $box.stop('y', true);
                        $box.animate({'top':'-=100'}, {queue:'y', step:stepY}).dequeue('y');
                        break;
                    case 39:
                        $box.stop('x', true);
                        $box.animate({'left':'+=100'}, {queue:'x', step:stepX}).dequeue('x');
                        break;
                    case 40:
                        $box.stop('y', true);
                        $box.animate({'top':'+=100'}, {queue:'y', step:stepY}).dequeue('y');
                        break;
                }
            })
        });
    </script>
    <style>
        article {
            position: relative;
        }
        article.withbox {
            height: 600px;
        }
        #box {
            background: #55f;
            position: relative;
            margin: 10px;
            width: 80px;
            height: 80px;
            left:270px;
            top:0;
        }
        #box span {
            display: inline-block;
            background: #fff;
            font-size: 22px;
            line-height: 22px;
            text-align: center;
            width: 16px;
            margin: 6px 8px;
            padding: 2px;
            border: 1px solid #000;
            border-radius: 2px;
            -moz-border-radius: 2px;
            -webkit-border-radius: 2px;
        }
        .red {
            color:red;
        }
    </style>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">

            <a href="animate.queue.html" title="go prev" class="button alignleft" rel="prev">← Prev </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="menu.drop-down.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
<pre><code contenteditable="true">$(<span>'#box'</span>).animate({
    <span>'left'</span>:<span>'-=100'</span>
}, {queue:'x', duration:2000}).dequeue('x')</code></pre>
            <button type="button" class="code">Left</button>
<pre><code contenteditable="true">$(<span>'#box'</span>).animate({
    <span>'left'</span>:<span>'+=100'</span>
}, {queue:'x', duration:2000}).dequeue('x')</code></pre>
            <button type="button" class="code">Right</button>
<pre><code contenteditable="true">$(<span>'#box'</span>).animate({
    <span>'top'</span>:<span>'-=250'</span>
}, {queue:'y', duration:2000}).dequeue('y')</code></pre>
            <button type="button" class="code">Up</button>
<pre><code contenteditable="true">$(<span>'#box'</span>).animate({
    <span>'top'</span>:<span>'+=250'</span>
}, {queue:'y', duration:2000}).dequeue('y')</code></pre>
            <button type="button" class="code">Down</button>
<pre><code contenteditable="true">$(<span>'#box'</span>).stop(<span>'x'</span>, true);
$(<span>'#box'</span>).stop(<span>'y'</span>, true);</code></pre>
            <button type="button" class="code">Stop</button>
        </menu>
        <header>
            <h1>Произвольная очередь анимации</h1>
            <h2>Своя очередь, попробуйте использовать стрелочки на клавиатуре (← и →, ↑ и ↓)</h2>
        </header>
        <article>
            <p>
                X: <span id="log-x">0px</span>
                Y: <span id="log-y">0px</span>
            </p>
        </article>
        <article class="withbox">
            <div id="box">
                <span>♠</span>
                <span>♣</span>
                <span class="red">♥</span>
                <span class="red">♦</span>
            </div>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Отключение

Иногда требуется отключить всю анимацию (к примеру, для отладки) воспользуйтесь следующей конструкцией:

jQuery.fx.off = true;

Лекция 6. Манипуляции с DOM

А теперь я буду долго и нудно рассказывать о том, как с помощью jQuery можно изменять DOM дерево на странице, т.е. добавлять и удалять элементы, но чего это я, глава в действительности не будет объёмной :)

Начнём с создания элементов для последующей работы с ними, документация нам заботливо сообщает, что тут всё просто:

var $myDiv = $('<div id="my" class="some"></div>')

Этот пример вполне рабочий, да вот только производительностью он блистать не будет, ведь внутри будет всё это разбираться с помощью метода "jQuery.parseHTML()", который совсем не быстрый. Но мы можем помочь парсеру если атрибуты элемента будем передавать вторым параметром:

var $myDiv = $('<div>', {'id':'my', 'class':'some'})

Можем сделать ещё проще:

var $myDiv = $('<div>').attr({'id':'my', 'class':'some'});

И этот способ будет работать даже быстрее (ну совсем капельку), но почему? Для того, чтобы ответить на данный вопрос – загляните в код jQuery, в самую главную функцию "init()", в её коде можно найти алгоритм разбора предыдущего примера:

  1. Парсим строку, и создаём DOM элемент в jQuery обёртке
  2. Заходим в цикл обработки переданных параметров:
    • Проверяем, а нет ли функции у нашего элемента с таким названием
    • Если нет, то устанавливаем атрибут элемента используя метод attr()

Выводы делайте сами, гдe мы тут время потеряли :)

Ну и на последок опишу самый быстрый способ, который я часто использую:

var myDiv = document.createElement('div'); 
myDiv.id = 'my';
myDiv.className = 'some';

Да, это и есть "чистый" JavaScript, но как по мне – в данном случае он не менее удобен любых фреймворков. И вот вам домашнее задание – оптимизируйте такой скрипт:

$('<div id="my"><div 
id="precious">Ring</div></div>')

Выполняйте тут, бумага стерпит:

Все необходимые нам методы собраны в одном разделе документации – Manipulation, с некоторыми из них мы уже познакомились, и осталось совсем чуть-чуть:

after(content) — вставляет контент после каждого элемента из выборки, т.е. если вы встречаете строку "$("p").after("<hr/>")", читайте её как "после каждого параграфа будет вставлена линия"

insertAfter(element) — вставляет элементы из выборки после каждого элемента переданного в качестве аргумента, т.е. если вы встречаете строку "$("<hr/>").insertAfter("p")" – читайте её как "линия будет вставлена после каждого параграфа"

— Хм, а я разницы не увидел! — тут всё легко, присмотритесь:

$("после чего добавляем").after("что добавляем")
$("что добавляем").insertAfter("после чего добавляем")

before(content) — вставляет контент перед каждым выбранным элементом

insertBefore(element) — вставляет элементы из выборки перед каждым элементом переданным в качестве аргумента

append(content) — вставляет контент в конец каждого элемента из выборки, т.е. строку кода "$("p").append("<hr/>")", следует читать как "в конец каждого параграфа будет добавлена линия"

appendTo(element) — вставляет выбранный контент в конец каждого элемента переданного в качестве аргумента: "$("<hr/>").appendTo("p")" — "линия будет добавлена в конец каждого параграфа"

Опять про разницу:

$("куда добавляем").append("что добавляем")
$("что добавляем").appendTo("куда добавляем")

prepend(content) — вставляет контент в начало каждого элемента из выборки

prependTo(element) — вставляет выбранный контент в начало каждого элемента переданного в качестве аргумента

Так, с этим кусочком документации вроде как разобрались, опять же – почувствуйте разницу перечисленных методов, ведь дальше будут ещё:

replaceWith(content) – заменяет найденные элементы новым

replaceAll(target) – вставляет контент в замен найденному

$("что-то находим").replaceWith("на что меняем")
$("что вставляем").replaceAll("вместо чего")

wrap(element) – оборачиваем каждый найденный элемент новым элементом, т.е. мы конфеты из коробки заворачиваем в фантики

wrapAll(element) – оборачивает найденные элементы новым элементом, мы берём все конфеты, и заворачиваем в один большой фантик

wrapInner(element) – оборачивает контент каждого найденного элемента новым элементом, берём конфеты, убираем фантики, заворачиваем в свой фантик, и сверху заворачиваем в родной фантик

unwrap() – удаляет родительский элемент у найденных элементов, фантики вон

clone(withDataAndEvents) – клонирует выбранные элементы, для дальнейшей вставки копий назад в DOM, позволяет так же копировать и обработчики событий

detach() – удаляет элемент из DOM, но при этом сохраняет все данные о нём в jQuery, следует использовать, если надо удалить элемент, а потом вернуть его обратно

empty() – удаляет текст и дочерние DOM элементы

remove() – удаляет элемент из DOM, насовсем

html() – вернёт HTML заданного элемента

html(newHtml) – заменит HTML в заданном элементе

text() – вернёт текст заданного элемента, если внутри элемента будут другие HTML тэги, то вернётся сборная солянка из текста всех элементов

text(newText) – заменит текст внутри выбранных элементов, при попытке вставить таким образом HTML, будет получен текст, где тэги будут приведены к HTML entities:

$("div").text("Some <strong>text</strong>") >> Some <strong>text</strong>

Переварили? Хорошо, теперь настал черёд методов, которые работают с размерами, и знают координаты элементов:

Но прежде чем продолжить, хотелось бы освежить в памяти информацию о вычислении высоты и ширины блочных элементов ;)

offset() – вернёт позицию DOM элемента относительно document'а, данные будут получены в виде объекта: "{ top: 10, left: 30 }"

offset({ top: 10, left: 30 }) – устанавливаем расположение DOM элемента по указанным координатам

position() – вернёт позицию DOM элемента относительно родительского элемента

height() – возвращает высоту элемента за вычетом отступов и границ; если у нас несколько элементов в выборке, вернётся первый; значение, в отличии от метода "css('height')", возвращается без указания единиц измерения

height(height) — устанавливает высоту всех элементов в выборке, если значение высоты передано без указания единиц измерения, то это будут "px"

// в качестве памятки, взято из мануала
$(window).height(); // высота окна
$(document).height(); // высота HTML документа

width() и width(width) – ведут себя аналогично методу "height()", но работают с шириной элемента

Методы "height()" и "width()" не изменяют своего поведения в зависимости от выбранной блочной модели, т.е. они всегда возвращают параметры области внутри margin, padding и border'а элемента.

innerHeight() и innerWidth() – вернут соответственно высоту и ширину элемента, включая "padding"

outerHeight() и outerWidth() – вернут высоту и ширину элемента, включая "padding" и "border"

outerHeight(true) и outerWidth(true) – высота и ширина, включая "padding", "border" и "margin"

Для наглядности различий между методами "height()", "innerHeight()" и "outerHeight()" я создал страничку, а ещё переделал несколько картинок из официальной документации в одну полноценную иллюстрацию:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Разбор высот</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
	<script type="text/javascript" src="js/jquery.js"></script>
	<script type="text/javascript" src="js/code.js"></script>
    <style>
        article {
            border: 1px solid #000;
        }
        #block {
            width:400px;
            height:40px;
            margin:40px;
            padding:40px;
            border:40px solid #777;
            background: #ddd;
        }
        #text {
            background: #fff;
            font-size: 20px;
            line-height: 40px;
            text-indent: 8px;
        }
        .contentBox {
            box-sizing: content-box;
            -moz-box-sizing: content-box;
            -webkit-box-sizing: content-box;
        }
        .borderBox {
            box-sizing: content-box;
            -moz-box-sizing: content-box;
            -webkit-box-sizing: content-box;
        }
        @media screen and (max-width: 480px) {
            article {
                border: 0;
            }
            #block {
                width:auto;
            }
            #text {
                font-size: 16px;
            }
        }
    </style>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu>
            <a href="property.html" title="go prev" class="button alignleft" rel="prev">← Prev </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="form.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
            <h3>Style</h3>
            <pre><code>#block {
    height:40px;
    margin:40px;
    padding:40px;
    border:40px solid #777;
}</code></pre>
            <!--<h3>Switch <em>box-sizing</em></h3>-->
            <!--<pre><code contenteditable="true">$(<span>'#block'</span>).addClass(<span>'borderBox'</span>)</code></pre>-->
            <!--<button type="button" class="code">Run Code</button>-->
            <!--<pre><code contenteditable="true">$(<span>'#block'</span>).addClass(<span>'contentBox'</span>)</code></pre>-->
            <!--<button type="button" class="code">Run Code</button>-->
            <h3>Получаем высоту</h3>
            <p>Лишь значение height</p>
            <pre data-out="1"><code>$(<span>'#block'</span>).height()</code></pre>
            <button type="button" class="code">Run Code</button>
            <p>Значение height + padding</p>
            <pre data-out="1"><code>$(<span>'#block'</span>).innerHeight()</code></pre>
            <button type="button" class="code">Run Code</button>
            <p>Значение height + padding + border</p>
            <pre data-out="1"><code>$(<span>'#block'</span>).outerHeight()</code></pre>
            <button type="button" class="code">Run Code</button>
            <p>Значение height + padding + border + margin</p>
            <pre data-out="1"><code>$(<span>'#block'</span>).outerHeight(true)</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример вычисление «высот»</h1>
        </header>
        <div id="output">
            <h3>Output</h3>
            <pre></pre>
        </div>
        <article>
            <div id="block">
                <div id="text">Content</div>
            </div>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

блочная модель


Рис. 6.1.  блочная модель

Ну и последняя пара методов:

scrollLeft() – возвращает значение "проскроленности" по горизонтали первого элемента из выборки

scrollLeft(value) – устанавливает значение горизонтального скрола для каждого элемента из выборки

scrollTop() – возвращает значение "проскроленности" по вертикали первого элемента из выборки

scrollTop(value) – устанавливает значение вертикального скрола для каждого элемента из выборки

Значение "scrollTop" и "scrollLeft" поддаются анимации и не работают для спрятанных элементов DOM

Методов реально много, я и сам не всегда помню что и для чего (особенно это касается wrap-семейства), так что не утруждайте себя запоминанием всего перечисленного, главное помнить что таковые имеются и держать под рукой документацию

Лекция 7. Работа с формами

Формы – это, пожалуй, один из самых нелюбимых элементов на странице – пока настроишь внешний вид, потом еще проверь, что ввели нерадивые пользователи да выведи им информацию о допущенных ошибках, и в конце концов отправляешь на сервер данные с чувством облегчения от проделанной кропотливой работы. Так вот – о том, что поможет в этой самой работе я и буду рассказывать.

Для начала, стоит напомнить события с которыми чаще всего придётся работать:

change — изменение значения элемента

submit — отправка формы

В каких же случаях они нам помогут? Да всё просто – отслеживание change позволяет обрабатывать такие события как изменение selectbox'а, или radiobutton'а, что потребуется для динамического изменения формы. И самый простой пример тому – это на странице регистрации выбор страны, затем по выбранной стране должен быть подгружен список регионов, по региону – список городов и так далее. Отслеживание submit потребуется для проверки правильности заполнения формы, а так же для отправки формы посредством AJAX. Форму возьмём попроще:

<form action="/save/">
<input type="text" name="name" value="Ivan"/> <select name="role">
<option>User</option> <option>Admin</option>
</select>
<input type="submit"/> </form>

А примеры будут идти в обратном порядке, вот отправка формы AJAX'ом по ссылке из "action":

$('form').submit(function(){
// чуть позже расскажу подробнее о AJAX
$.post(
$(this).attr('action'), // ссылка куда отправляем данные
$(this).serialize() // данные формы
);
// отключаем действие по умолчанию
return false;
});

Вот и первый метод – "serialize()" – он в ответе за "сбор" данных с формы в удобном для передачи данных формате:

name=Ivan&role=Admin

Так же есть метод "serializeArray()" – он собранные данные представляет в виде объекта:

[
{
name:"name",
value:"Ivan"
},
{
name:"role",
value:"Admin"
},
]

Теперь стоит добавить в данный код немного проверки данных:

$('form').submit(function(){
if ($(this).find('input[name=name]').val() == '') {
alert('Введите имя пользователя');
return false;
}
// кусок кода с отправкой
// ...
});

Вот еще один метод, который нам будет частенько нужен:

val() – получение значения первого элемента формы из выборки

val(value) – установка значение всем элементам формы из выборки

Данный метод отлично работает практически со всеми элементами формы, вот только с radiobutton'ами установить значение таким образом не получится, тут потребуется небольшой workaround:

$('input[type=radio][name=choose][value=2]').prop('checked', true)

Можно конечно же использовать и метод "click()" дабы эмулировать выбор необходимо пункта, но это вызовет все обработчики события "click", что не желательно

С checbox'ами чуть-чуть попроще:

$('input[name=check] ').prop('checked', true)

Проверяем "чекнутость" простым скриптом:

$('input[name=check] ').prop('checked')
// или чуть более наглядным способом
$('input[name=check] ').is(':checked')

Проверять и отправлять форму AJAX'ом теперь умеем, теперь осталось решить вопрос с динамическим изменением формы, и для этого у нас уже есть все необходимые знания, вот, к примеру, добавление выпадающего списка:

$('form').append('<select name="some"></select>');

А если потребуется изменить список? Есть на все случаи жизни:

// возьмём список заранее, поберегу чернила
var $select = $('form select[name=Role]');
// добавить новый элемент в выпадающий список
$select.append('<option>Manager</option>');
// выбрать необходимый элемент
$select.val('Value 1');
// или по порядковому номеру, начиная с 0
$select.find('option:eq(2)').prop('selected', true);
// очищаем список
$select.remove('option');
// преобразуем в multiple
// не забываем, что имя такого селекта, должно быть с [], т.е.
// myselect[], иначе сервер получит, лишь одно значение
$('select').attr('size',
$('select option').length

Хорошо, работать с формой теперь можем, осталось прикрутить более вменяемый вывод ошибок (да-да, за "alert()" да по рукам):

if ($(this).find('input[name=user]').val() == '') {
$(this).find('input[name=user]')
.before('<div class="error">Введите имя</div>');
return false;
}

При повторной отправки формы не забудьте убрать сообщения оставшиеся от предыдущей проверки:

$(this).find('.error').remove()

Теперь можно объединить кусочки кода и получить следующий вариант:

$('form').submit(function(){
// чистим ошибки
$(this).find('.error').remove();
// проверяем поля формы
if ($(this).find('input[type=name]').val() == '') {
$(this).find('input[name=user]')
.before('<div class="error">Введите имя</div>');
return false;
}
// всё хорошо – отправляем запрос на сервер
$.post(
$(this).attr('action'), // ссылка куда отправляем данные
$(this).serialize() // данные формы
);
return false;
});

Теперь стоит вернуться к списку событий формы, и перечислить недостающие:

focus — фокус на элементе, для работы с данным событием так же есть "shorthand" метод "focus()"; может потребоваться, если надо вывести подсказку к элементу формы при наведении

blur — фокус ушёл с элемента + метод "blur()", для работы с ним; пригодится при валидации формы по мере заполнения полей

select — выбор текста в "textarea" и "input[type=text]" + метод "select()"; если соберётесь разрабатывать свой WYSIWYG, то познакомитесь очень плотно

submit — отправка формы + метод "submit()"; будете использовать частенько

Примеры работы данных методов:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Работа с формой</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <script type="text/javascript">
        $(function(){
            $('form').submit(function(){
                $(this).find('.error').remove();
                if ($(this).find('[name=text]').val() == '') {
                    $(this).find('[name=text]').before('<div class="error">Введите имя</div>');
                    return false;
                }
                out($(this).serialize()+'\n'+$(this).serializeArray());
                return false;
            })
        });
    </script>
    <style>
        form {
            padding: 10px;
        }
    </style>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
            <a href="height.html" title="go prev" class="button alignleft" rel="prev">← Prev </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
            <h3>Text input</h3>
            <pre><code contenteditable="true">$(<span>'input[type=text]'</span>).val(<span>'new text'</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre data-out="1"><code>$(<span>'input[type=text]'</span>).val()</code></pre>
            <button type="button" class="code">Run Code</button>

            <h3>Text area</h3>
            <pre><code contenteditable="true">$(<span>'textarea'</span>).val('Some text')</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre data-out="1"><code>$(<span>'textarea'</span>).val()</code></pre>
            <button type="button" class="code">Run Code</button>
            <h3>Selectbox</h3>
            <pre><code contenteditable="true">$(<span>'select'</span>).val('Option 2')</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre data-out="1"><code>$(<span>'select'</span>).val()</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code>$(<span>'select option:eq(2)'</span>).prop(<span>'selected'</span>, true)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code>$(<span>'select option:eq(2)'</span>).prop(<span>'selected'</span>, false)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code contenteditable="true">$(<span>'select'</span>).append(<span>'<option>New Option</option>'</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code>$(<span>'select'</span>).attr(<span>'size'</span>,
    $(<span>'select option'</span>).length
)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code>$(<span>'select option'</span>).remove()</code></pre>
            <button type="button" class="code">Run Code</button>

            <h3>Checboxes</h3>
            <pre><code>$(<span>'input[type=checkbox]'</span>).prop(<span>'checked'</span>, true)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre data-out="1"><code>$(<span>'input[type=checkbox]'</span>).is(<span>':checked'</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
            <h3>Radiobuttons</h3>
            <pre><code>$(<span>'input[type=radio][name=choose][value=2]'</span>)
    .prop(<span>'checked'</span>, true)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code>$(<span>'input[type=radio][name=choose][value=2]'</span>)
    .prop(<span>'checked'</span>, false)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre data-out="1"><code>$(<span>'input[type=radio][name=choose]:checked'</span>).val()</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Работа с формой</h1>
            <h2>Попробуйте изменить данные, и получить данные</h2>
        </header>
        <div id="output">
            <h3>Output</h3>
            <pre></pre>
        </div>
        <article>
			<form action="">
				<fieldset>
					<input type="text" name="text"/>
					<textarea name="editor" cols="64" rows="16">Lorem ipsum dolor sit amet, consectetur adipiscing lit. Phasellus rutrum,
lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</textarea>

					<select name="select">
					  <option>Option 1</option>
					  <option>Option 2</option>
					  <option>Option 3</option>
					</select>
					<hr/>
					<label><input type="checkbox" name="check" value="1"/> checkbox</label><br/>
					<hr/>
					<label><input type="radio" name="choose" value="1"/> choose 1</label><br/>
					<label><input type="radio" name="choose" value="2"/> choose 2</label><br/>
					<label><input type="radio" name="choose" value="3"/> choose 3</label><br/>
					<hr/>
					<input type="submit" name="submit" class="button"/>

				</fieldset>
			</form>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Вот так мы и расправились с "ужасными" формами, возможно я ещё приведу несколько примеров из реальной жизни, но это будет уже в последующих версиях данного учебника :)

Лекция 8. AJAX

Что такое AJAX я думаю рассказывать не стоит, ибо с приходом веб-два-нуля большинство пользователей уже воротят носом от перезагрузок страниц целиком, а с появлением jQuery реализация упростилась в разы…

Начнем с самого простого – загрузка HTML кода в необходимый нам DOM элемент на странице. Для этой цели нам подойдет метод "load()". Данный метод может принимать следующие параметры:

url – запрашиваемой страницы

data – передаваемые данные (необязательный параметр)

callback – функция которая будет вызвана при завершении запроса к серверу (необязательный параметр)

Теперь на примерах:

// в элемент с id=content будет вставлен весь HTML с указанной страницы
$("#content").load("/get-my-page.html");
// в элемент с id=content будет вставлен HTML с указанной страницы
// выбранный по указанному селектору #wrapper
$("#content").load("/get-my-page.html #wrapper");
// передаем данные на сервер
$("#content").load("/get-my-page.html", {id:18});
// обрабатываем полученные данные
$("#content").load("/get-my-page.html", function(){
alert("Ничего оригинальней не придумал");
});

Из моего опыта работы – вам очень часто придётся пользоваться методом "load()" как описано в первом примере, а еще советую запомнить второй пример, он может выручить, когда надо реализовать загрузку AJAX’ом, а доступа к сервер-сайду у вас нет или он ограничен.

Живой пример:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример загрузки HTML посредством AJAX и метода load()</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
	<script type="text/javascript" src="js/code.js"></script>
    <style>
        article {
            border:1px dotted #ccc
        }
    </style>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu>
            <a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back</a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="ajax.datatype.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
			<pre><code contenteditable="true"><em>// AJAX is easy</em>
$(<span>'article'</span>).load(<span>'index.html'</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
			<pre><code contenteditable="true"><em>// easy to load</em>
$(<span>'article'</span>).load(<span>'index.html header'</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
			<pre><code contenteditable="true"><em>// easy to handle</em>
$(<span>'article'</span>).load(<span>'index.html article'</span>, function(){
	$(<span>'article'</span>).show().fadeOut(2000, function(){
        $(this).html('<strong>→</strong>').fadeIn();
    });
})</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>load()</h1>
            <h2>Простой пример использования метода load()</h2>
        </header>
        <article>
			<h2>Пробуем загрузить контент →</h2>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Следующий метод с которым я вас познакомлю будет "ajax()" – собственно, он тут главный, и все остальные AJAX методы являются лишь обёрткой (и "load()" в том же числе). Метод "ajax()" принимает в качестве параметра пачку настроек и URL куда стучаться, приведу пример аналогичный вызову "load()":

$.ajax({
url: "/get-my-page.html", // указываем URL и
dataType: "html", // тип загружаемых данных
success: function (data) {
// вешаем свой обработчик события success
$("#content").html(data)
}
});

Тут мы обрабатывали HTML ответ от сервера – это хорошо когда нам полстраницы обновить надо, но данные лучше передавать в "правильном" формате – это XML – понятно, структурировано, и избыточно, и как-то не совсем JavaScript-way, и поэтому наш выбор – это JSON (wikipedia в помощь):

{
"note": {
"time":"2012.09.21 13:11:15",
"text":"Рассказать про JSONP"
}
}

Фактически это и есть JavaScript код как есть (JavaScript Object Notation если быть придирчиво точным), при этом формат уже распространён настолько, что работа с данными в другом формате уже не комильфо.

Жизнь не стоит на месте, есть и более удобные форматы, но не в JavaScript’е :)

Для загрузки JSON существует быстрая функция-синоним – "jQuery.getJSON(url [,data] [,success(data, textStatus, jqXHR)])" – в качестве обязательного параметра лишь ссылка, куда стучимся, опционально можно указать данные, для передачи на сервер и функцию обратного вызова

Нельзя просто так взять и описать все возможные параметры для вызова "ajax()", таки стоит держать официальный мануал под рукой – http://api.jquery.com/jQuery.ajax/

Я неспроста оставляю так много места под ваши заметки:..

Обработчики AJAX событий

Для удобства разработки, AJAX запросы бросают несколько событий, и их естественно можно и нужно обрабатывать. jQuery позволяет обрабатывать эти события для каждого AJAX запроса в отдельности, либо глобально. Приведу схемку на которой наглядно видно порядок возникновения событий в jQuery:



увеличить изображение

Вот и полный список событий с небольшими ремарками:

ajaxStart — данное событие возникает в случае когда побежал первый AJAX запрос, и при этом других активных AJAX запросов в данный момент нет

beforeSend — возникает до отправки запроса, позволяет редактировать XMLHttpRequest, локальное событие

ajaxSend — возникает до отправки запроса, аналогично "beforeSend"

success — возникает по возвращению ответа, когда нет ошибок ни сервера, ни вернувшихся данных, локальное событие

ajaxSuccess — возникает по возвращению ответа, аналогично success

error — возникает в случае ошибки, локальное событие

ajaxError — возникает в случае ошибки

complete — возникает по завершению текущего AJAX запроса (с ошибкой или без — срабатывает всегда), локальное событие

ajaxComplete — глобальное событие, аналогичное complete

ajaxStop — данное событие возникает в случае, когда больше нету активных запросов

Пример для отображения элемента с "id="loading"" во время выполнения любого AJAX запроса (т.е. мы обрабатываем глобальное событие):

$("#loading").bind("ajaxSend", function(){
$(this).show(); // показываем элемент
}).bind("ajaxComplete", function(){
$(this).hide(); // скрываем элемент
});

Это задачка по юзабилити – мы всегда должны держать пользователя сайта в курсе дела о происходящем на странице, и отправка AJAX запроса тоже попадает под разряд "must know". Подобное решение у вас будет практически на любом сайте, где ходит AJAX

Для локальных событий – вносим изменения в опции метода "ajax()":

$.ajax({
beforeSend: function(){
// данный обработчик будет вызван
// перед отправкой данного AJAX запроса
},
success: function(){
// а этот при удачном завершении
},
error: function(){
// этот при возникновении ошибки
},
complete: function(){
// и по завершению запроса (удачном или нет)
}
});

Можно глобальные обработчики отключить принудительно используя флаг "global", для этого выставляем его в "false", и пишем функционал в обход событий "ajaxStart" и "ajaxStop":

$.ajax({
global: false,
beforeSend: function(){
// ...
},
success: function(){
// ...
},
error: function(){
// ...
},
complete: function(){
// ...
}
});

Данная опция частенько помогает избежать конфликтов при работе с AJAX запросами, но не стоит ей злоупотреблять.

JSONP

JSONP – это наш старый знакомый JSON с прослойкой в виде callback функции О_о. Да ладно, давайте на примерах, вот как у нас выглядит ответ сервера в формате JSON:

{
"note": {
"time":"2012.09.21 13:12:42",
"text":"Рассказать зачем нужен JSONP"
}
}

Хорошо, когда у нас эти данные приходят с нашего сервера – обработаем, и всё будет чики-пики, но а если нам потребуется заполучить данные с другого сервера, то политика безопасности в браузерах не позволит отправить XMLHTTPRequest на другой сервер, и надо уже будет что-то придумывать. Можно чуть-чуть напрячься и вспомнить, что подключать JavaScript с другого сервера то мы можем, и он будет выполнен. Вот она – зацепка-то, а если подключаемый скрипт будет содержать вызов нашей функции с подготовленными данными – то это уже что-то:

alertMe({
"note": {
"time":"2012.09.21 13:13:13",
"text":"Каков же профит от использования JSONP?"
}
})

Таким образом, описав в своём коде функцию "alertMe()" мы сможем обработать данные с удаленного сервера. Зачастую, сервера ловят параметр "callback" или "jsonp", и используют его как имя функции обёртки:

<script type="text/javascript" src="http://domain.com/getUsers/?callback=alertMe">

</script>

Ну это было предыстория, теперь вернёмся к jQuery и методу "ajax()":

$.ajax({
url: "http://domain.com/getUsers/?callback=?", // указываем URL dataType: "jsonp",
success: function (data) {
// обрабатываем данные
}
});

В запрашиваемом URL наблюдательный читатель заметит незаконченную структуру "callback=?", так вот вместо "?" будет подставлено имя ново сгенерированной функции, внутри которой будет осуществляться вызов функции "success()". Вместо этой прокси-функции можно использовать и свою функцию, достаточно указать её имя в качестве параметра "jsonpCallback" при вызове "ajax()". Оба этих подхода есть в примере:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример использования JSONP</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
	<script type="text/javascript" src="js/code.js"></script>
    <script>
        function toJsonContainer(data, status, xhr) {
            $('#json').text(data);
        }
        function buildImages(data) {
            $.each(data.items, function(i, item){
                $("<img/>").attr("src", item.media.m).appendTo("#images");
                if ( i == 3 ) return false;
            });
        }
    </script>
    <style>
        article pre {
            font-size: 14px;
            padding: 20px;
        }
    </style>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu>
            <a href="ajax.script.html" title="go prev" class="button alignleft" rel="prev">← Prev</a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="ajax.google.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
<pre><code>$.ajax({
    url: <span>"http://anton.shevchuk.name/book/code/ajax/example.php?callback=?"</span>,
    dataType: <span>"jsonp"</span>,
    success: toJsonContainer
});</code></pre>
             <button type="button" class="code">Run Code</button>
<pre><code>$.ajax({
    url: <span>"http://anton.shevchuk.name/book/code/ajax/example.php?callback=?"</span>,
    dataType: <span>"jsonp"</span>,
    jsonpCallback: <span>"toJsonContainer"</span>
});</code></pre>
             <button type="button" class="code">Run Code</button>
            <h3>Flickr API</h3>
<pre><code contenteditable="true">$.getJSON(
    <span>"http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?"</span>,
    {
        tags: <span>"orange"</span>,
        tagmode: <span>"any"</span>,
        format: <span>"json"</span>
    },
    buildImages
);</code></pre>
             <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример использования JSONP с простым сервером</h1>
            <h2>Код сервера <a href="http://anton.shevchuk.name/book/code/ajax/example.code">code/ajax/example.code</a></h2>
        </header>
        <article>
            <pre id="json">

            </pre>
        </article>
        <article>
            <div id="images">

            </div>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

А еще стоит упомянуть, что можно указать как обзывается callback- параметр используя параметр "jsonp", таким образом указав "jsonp:"my"" в URL будет добавлена структура "my=?"

На данный момент достаточно много сервисов предоставляют API с поддержкой JSONP:

Использование подобного подхода позволяет обходить ограничения накладываемые сервисами на количество запросов с одного IP, плюс не грузит сервер дополнительной работой по проксированию запросов от пользователя к сервисам.

Лечим JavaScript зависимость

Любовь к AJAX бывает чрезмерной, и в погоне к Web2.0 (3.0, 4.0, … — желаемое подчерк-нуть) мы создаём сайты в которых все наши действия бегут через XMLHTTPRequest. Нет, это конечно не плохо — снижаем нагрузку на сервер, канал и т.д. и т.п., но есть одно "но" — у нас есть поисковые машины, которые не озадачивают себя выполнением JavaScript кода, а контент, спрятанный за AJAX запросом, им отдать всё таки нужно. Следовательно, у нас возникает необходимость дублирования навигации (это как минимум) для клиентов без JavaScript.

Стоит помнить, что есть ещё пользователи, у которых отключён JavaScript в браузере (или даже не поддерживается, привет тебе, рысь), но эти знают, что делают. А есть ещё скрипты, которые ломаются, и не дают обычным пользователям воспользоваться навигацией по сайту, а пользователей это очень сильно расстраивает, так что эта глава не "просто так".

Как же всё это обойти и на грабли не наступить? Да всё очень просто — создавайте обычную навигацию, которую вы бы делали не слышав ни разу о AJAX и компании:

<ul class="navigation">
<li><a href="/">Home</a></li>
<li><a href="/about.html">About Us</a>
</li> <li><a href="/contact.html">Contact Us</a></li>
</ul>
<section id="content"><!-- Content --></section>

Данный пример работает у нас без JavaScript’a, все страницы в нашем меню используют один и тот же шаблон для вывода информации, и по факту у нас изменяется лишь содержимое <div> с "id="content"". Теперь приступим к загрузке контента посредством AJAX – для этого добавим следующий код:

$(function() {
// вешаем обработчик на все ссылки в нашем меню navigation
$("ul.navigation a").click(function(){
var url = $(this).attr("href"); // возьмем ссылку
url =+ "?ajax=true"; // добавим к ней параметр ajax=true
$("#content").load(url); // загружаем обновлённое содержимое
return false; // возвращаем false
// - дабы не сработал переход по ссылке
});
});

В данном примере мы предполагаем, что сервер, видя параметр "ajax=true" вернет нам не полностью всю страницу, а лишь обновление для искомого элемента <div id="content">.

Конечно, сервер должен быть умнее и не требовать явного указания для использования AJAX’а, а должен вполне удовлетвориться, словив header "X_REQUESTED_WITH" со значением "XMLHttpRequest". Большинство современных фреймворков для web-разработки с этим справляются "из коробки".

Если же управлять поведением сервера проблематично, и он упёрто отправляет нам всю страницу целиком, то можно написать следующий код:

$(function() {
// вешаем обработчик на все ссылки в нашем меню navigation
$("ul.navigation a").click(function(){
var url = $(this).attr("href"); // возьмем ссылку
// загружаем страницу целиком, но в наш контейнер вставляем
// лишь содержимое #content загружаемой страницы
$("#content").load(url + " #content > *");
return false; // возвращаем false
});
});

Если в подгружаемом содержимом так же есть ссылки – то вы уже должны знать как "оживить" события.

Прокачиваем AJAX

У нас есть три способа для "прокачки" AJAX'а в jQuery: это создание префильтров, добавление новых конверторов и транспортов.

Префильтры

Префильтр – это функция, которая будет вызвана до "ajaxStart", в ней вы сможете изменить как объект "jqXHR", так и любые сопутствующие настройки:

// регистрация AJAX префильтра
$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
// наши манипуляции над настройками и jqXHR
});

Для чего всё это? Да вот простая задачка – не ждать "старый" AJAX ответ, если мы запрашиваем URL заново:

// коллекция текущих запросов
var currentRequests = {};
$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
// наша произвольная настройка
if ( options.abortOnRetry ) {
if ( currentRequests[ options.url ] ) {
// отменяем старый запрос
currentRequests[ options.url ].abort();
}
currentRequests[ options.url ] = jqXHR;
}
});
// вызов с использованием фильтра
$.ajax({
/* ... */
abortOnRetry: true
})

Ещё можно изменить опции вызова, вот пример который по флагу "crossDomain" пересылает запрос на заранее подготовленную проксирующую страницу на нашем сервере:

$.ajaxPrefilter(function( options ) {
if ( options.crossDomain ) {
options.url = "/proxy/" + encodeURIComponent( options.url );
options.crossDomain = false;
}
});

Префильтры можно "вешать" на определенный тип "dataType" (т.е. в зависимости от ожидаемого типа данных от сервера будут срабатывать различные фильтры):

$.ajaxPrefilter("json script", function(options, original, jqXHR) {
/* ... */
});

Ну и последнее, для переключение "dataType" на какой-нить другой нам достаточно будет вернуть необходимое значение:

$.ajaxPrefilter(function( options ) {
// это наша функция-детектор необходимых URL
if ( isActuallyScript( options.url ) ) {
// теперь "ждём" script
return "script";
}
});

Будьте очень осторожны когда оперируете глобальными настройками, да ещё через такую неявную фичу как фильтры – задокументируйте подобные подходы в сопроводительной документации, иначе разработчики которые будут в дальнейшем сопровождать ваш код будут сильно ругаться (в качестве оных можете оказаться и вы сами, ну через пару месяцев)

Конверторы

Конвертор – функция обратного вызова, которая вызывается в том случае, когда полученный типа данных не совпадает с ожидаемым (т.е. "dataType" указан неверно). Всё конверторы хранятся в глобальных настройках "ajaxSettings":

// формат ключа "из_формата в_формат"
// в качестве входного формата можно использовать "*"
converters: {
"* text": window.String, // что угодно приводим к тексту
"text html": true, // текст к html (флаг true == без изменений)
"text json": jQuery.parseJSON, // текст к JSON
"text xml": jQuery.parseXML // разбираем текст как xml
}

Для расширения набора конверторов потребуется функция "$.ajaxSetup()":

$.ajaxSetup({
converters: {
"text mydatatype": function( textValue ) {
if ( valid( textValue ) ) {
// разбор пришедших данных
return mydatatypeValue;
} else {
// возникла ошибка
throw exceptionObject;
}
}
}
});

Имена "dataType" должны всегда быть в нижнем регистре

Конверторы следует использовать, если требуется внедрить произвольные форматы "dataType", или для конвертации данных в нужный формат. Необходимый "dataType" указываем при вызове метода "$.ajax()":

$.ajax( url, { dataType: "mydatatype" });

Конверторы можно задавать так же непосредственно при вызове "$.ajax()", дабы не засорять общие настройки:

Конверторы можно задавать так же непосредственно при вызове "$.ajax()",
дабы не засорять общие настройки:
$.ajax( url, {
dataType: "xml text mydatatype",
converters: {
"xml text": function( xmlValue ) {
// получаем необходимые данные из XML
return textValue;
}
}
});

Чуть-чуть пояснений – мы запрашиваем "XML", который конвертируем в текст, который будет передан в наш конвертор из "text" в "mydatatype"

Транспорт

Использование своего транспорта – это крайняя мера, прибегайте к ней, только в том случае если с поставленной задачей нельзя справиться с использованием префильтров и конверторов

Транспорт – это объект, который предоставляет два метода – "send()" и "abort()" – они будут использоваться внутри метода "$.ajax()". Для регистрации своего метода транспортировки следует использовать метод "$.ajaxTransport()", будет это выглядеть как-то так:

$.ajaxTransport( function( options, originalOptions, jqXHR ) {
if ( /* transportCanHandleRequest */ ) {
return {
send: function( headers, completeCallback ) {
/* отправляем запрос */
},
abort: function() {
/* отменяем запрос */
}
};
}
});

Проясню чуток параметры с которыми будем работать:

Функция "completeCallback" имеет следующую сигнатуру:

function ( status, statusText, responses, headers ) {
/* какой-то код */
}

где:

Как и префильтры, транспорт можно привязывать к определенному типу запрашиваемых данных:

$.ajaxTransport( "script", function( options, originalOptions, jqXHR ) {
/* привязываемся лишь к script*/
});

А теперь мега-напряг – пример транспорта "image":

$.ajaxTransport( "image", function( options ) {
if (options.type === "GET" && options.async ) {
var image;
return {
send: function( _ , callback ) {
image = new Image();
function done( status ) { // подготовим
if ( image ) {
var statusText =
( status == 200 ) ? "success" : "error",
tmp = image;
image = image.onreadystatechange = image.onerror
= image.onload = null;
callback( status, statusText, { image: tmp } );
}
}
image.onreadystatechange = image.onload = function() {
done( 200 );
};
image.onerror = function() {
done( 404 );
};
image.src = options.url;
},
abort: function() {
if ( image ) {
image = image.onreadystatechange = image.onerror
= image.onload = null;
}
}
}; // /return
} // /if
}); // /ajaxTransport

Рабочий пример вы увидите ниже, но я хотел бы ещё раз напомнить, что это "advanced level", и данный раздел лишний в учебнике "для начинающих".

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример создания транспорта для AJAX</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
	<script type="text/javascript" src="js/code.js"></script>
    <script>
        $.ajaxTransport("image", function (options) {
            if (options.type === "GET" && options.async) {
                var image;
                return {
                    send:function (_, callback) {
                        image = new Image();
                        // подготовим функцию done
                        function done(status) {
                            if (image) {
                                var statusText = ( status == 200 ) ? "success" : "error", tmp = image;
                                image = image.onreadystatechange = image.onerror = image.onload = null;
                                callback(status, statusText, { image:tmp });
                            }
                        }
                        image.onreadystatechange = image.onload = function () {
                            done(200);
                        };
                        image.onerror = function () {
                            done(404);
                        };
                        image.src = options.url;
                    },
                    abort:function () {
                        if (image) {
                            image = image.onreadystatechange = image.onerror = image.onload = null;
                        }
                    }
                };
            }
        });
    </script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu>
            <a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
			<pre><code contenteditable="true">$.ajax(<span>'images/events.png'</span>, {
    dataType:<span>'image'</span>,
    success: function(data) {
        $(<span>'article'</span>).html(data);
    }
});</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Своя реализация AJAX-транспорта</h1>
            <h2>Пример надуманный</h2>
        </header>
        <article>
			<p>
                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
                lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
                in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
                euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
                eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
                at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
                nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
                tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
                fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
                lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.
			</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

По следам официальной документации:

Лекция 9. Объект Deferred и побратимы

Работа с объектом "Deferred" это уже высший пилотаж, это "mad skills" заставлять асинхронный JavaScript работать синхронно. Давайте посмотрим как он работает (данный код можно скопировать в консоль и выполнить на любой странице, где подключен jQuery):

// инициализация Deferred объекта
// статус "ожидает выполнение"
var D = $.Deferred();
// подключаем обработчики
D.done(function() { console.log("first") });
D.done(function() { console.log("second") });
// изменяем статус на "выполнен успешно"
// для этого вызываем resolve()
// наши обработчики будут вызваны в порядке очереди,
// но они не ждут друг-друга
D.resolve();
// данный обработчик подключён слишком поздно, и будет вызван сразу
D.done(function() { console.log("third") });

Кроме сценариев с "happy end", есть ещё и грустные истории, когда всё пошло не так как нам бы хотелось:

// инициализация Deferred объекта
var D = $.Deferred();
// подключаем обработчики
D.done(function() { console.log("done") });
D.fail(function() { console.log("fail") });
// изменяем статус на "выполнен с ошибкой"
D.reject();
// в консоли нас будет ожидать лишь "fail" :(

Для ускорения написания кода существует ещё метод "then()", который позволяет вешать одновременно обработчики всех типов:

D.then(function() { console.log("done") },
function() { console.log("fail") });

Становится ли от этого код читаемым – сомневаюсь, но метод есть, и может пригодится.

Ещё упомяну метод "always()" – он добавляет обработчики, которые будут выполнены вне зависимости от выбранного сценария. Чтобы не путаться в перечисленных методах приведу блок-схему:



увеличить изображение

В качестве аргументов методов "done()" и "fail()"может быть произвольное множество callback-функций. При вызове "resolve()" и "reject()" можно передать произвольные данные в зарегистрированные callback-функции для дальнейшей работы. Кроме того, существуют еще методы "resolveWith()" и "rejectWith()", они позволяют изменять контекст вызываемых callback-функции (т.е. внутри них "this" будет смотреть на указанный контекст)

Отдельно хотел отметить, что если вы собираетесь передать Deferred объект "на сторону", чтобы "там" могли повесить свои обработчики событий, но не хотите потерять контроль, то возвращайте не сам объект, а результат выполнения метода "promise()" – это фактически будет искомый объект в режиме "read only".

Теперь покажу хитрый метод "$.when()":

$.when(
$.ajax("/ajax/example.json"),
$("article").slideUp(200)
).then(function(){
alert("All done");
}, function(){
alert("Something wrong");
})

Поясню происходящее – AJAX запрос и анимация стартуют одновременно, когда и тот и другой завершат свою работу, будет вызвана функция, которую мы передаём в качестве аргумента в метод "then()" (одна из двух, в зависимости от исхода происходящего). Для обеспечения работы этой "магии" методы "when()", "ajax()" и "animate()" реализуют интерфейс Deferred. Пример работы

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример работы с $.when</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">

            <a href="deferred.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="deferred.pipe.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
<pre><code contenteditable="true">$.when(
    $(<span>'article img'</span>).slideUp(1000),
    $(<span>'article p'</span>).hide(3000)
).done(function(){
     $(<span>'article'</span>).slideUp(500)
})</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример работы метода $.when()</h1>
            <h2>Поддержка анимации</h2>
        </header>
        <article id="stick" class="box">
            <h2>Article</h2>
            <p>
                <img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200"/>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-chamomile-tumb.jpg" alt="Chamomile" class="left" width="200"/>
            Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-maple-leaf-tumb.jpg" alt="Maple Leaf" class="left" width="200"/>
            Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
            laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
            non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
            ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
            sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
            vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
            magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
            cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
        </script>
	</div>
</body>
</html>

Теперь можно и заумно – метод "when()" возвращает проекцию Deferred объекта, принимает в качестве параметров произвольное множество Deferred объектов, когда все из них отработают, объект "when" изменит своё состояние в "выполнено", с последующим вызовом всех подписавшихся.

А ещё, кроме поведения "ждём всех", с помощью Deferred можно выстраивать цепочки вызовов – "живые очереди":

$.ajax('ajax/example.json')
.then(function(){
// подождём окончания AJAX запроса
return $('article img').slideUp(2000)
})
.then(function(){
// подождём пока спрячутся картинки
return $('article p').slideUp(2000)
})
.then(function(){
// подождём пока спрячутся параграфы
return $('article').hide(2000);
})
.done(function(){
// всё сделано шеф
});

Подобное поведение можно воспроизвести используя лишь animate, но нам же хочется заглянуть чуть-чуть поглубже (до версии 1.8 тут шла речь о методе "pipe()", а теперь о "then()")

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример цепочек вызовов $.Deferred</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
            <a href="when.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="callbacks.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
<pre><code contenteditable="true">$.ajax(<span>'ajax/example.json'</span>)
    .then(function(){
        appendOut(<span>'Ajax\n'</span>);
        return $(<span>'article img'</span>).slideUp(2000)
    })
    .then(function(){
        appendOut(<span>'Images\n'</span>);
        return $(<span>'article p'</span>).slideUp(2000)
    })
    .then(function(){
        appendOut(<span>'Paragraph\n'</span>);
        return $(<span>'article'</span>).hide(2000);
    })
    .done(function(){
        appendOut(<span>"All Ok"</span>);
    });
</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример построение цепочек $.Deferred</h1>
            <h2>C animate(), метод pipe() не подружился :(</h2>
        </header>
        <div id="output">
            <h3>Output</h3>
            <pre></pre>
        </div>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200"/>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-chamomile-tumb.jpg" alt="Chamomile" class="left" width="200"/>
            Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-maple-leaf-tumb.jpg" alt="Maple Leaf" class="left" width="200"/>
            Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
            laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
            non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
            ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
            sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
            vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
            magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
            cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
        </script>
	</div>
</body>
</html>

В данном примере мы вызываем метод "then()", которому скормлена callback-функция, которая должна возвращать объект Deferred, это необходимо для соблюдения порядка в очереди – попробуйте убрать в примере один "return", и вы заметите, что следующая анимация наступит не дождавшись завершения предыдущей.

На этом возможности Deferred ещё не завершились, есть ещё связка методов "notify()" и "progress()" – первый шлёт послания в callback-функции, которые зарегистрированы с помощью второго. Приведу наглядный код для демонстрации (копи-паст в консоль, и смотрите что получается):

var D = $.Deferred();
var money = 100; // наш бюджет
// съём денежки
D.progress(function($){
console.log(money + " - " + $ + " = " + (money-$));
money -= $;
if (money < 0) { // деньги закончились
this.reject();
}
});

// тратим деньги
setTimeout(function(){ D.notify(40); }, 500); // покупка 1
setTimeout(function(){ D.notify(50); }, 1000); // покупка 2
setTimeout(function(){ D.notify(30); }, 1500); // покупка 3
D.done(function(){ console.info("All Ok") });
D.fail(function(){ console.error("Insufficient Funds") });

Испытайте всю мощь Deferred в примере

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Работаем с $.Deferred</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <style>
        .loading {
            background: url('images/ajax-loader.gif') no-repeat right;
        }
        #images div {
            height: 216px;
            width: 310px;
            float:left;
            overflow: hidden;
            margin: 4px;
            border: 1px solid #ccc;
            border-radius: 4px;
            -moz-border-radius: 4px;
            -webkit-border-radius: 4px;
        }
        article div img {
            min-height: 216px;
            min-width: 310px;
            border:0;
            padding:0;
            margin:0;
        }
    </style>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <script>
        var Flickr = {
            search:function(query) {
                showLoader();
                var def = $.Deferred();
                $.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?", {
                            tags: query,
                            tagmode: "any",
                            format: "json"
                        },
                        def.resolve
                );
                return def.promise();
            }
        };
        function buildImages(data) {
            var def = $.Deferred();
            var limit = 4;
            var loaded = 0;
            $.each(data.items, function(i, item){
                if ( i == limit ) return false;
                var img         = new Image();
                    img.onload  = img.onerror = function() {
                        // изменяем прогресс загрузки
                        def.notify(1)
                    };
                    img.src     = item.media.m;
                var div = $('<div></div>').append(img);
                $(div).prependTo("#images");
            });
            def.progress(function() {
                loaded ++;
                if (limit == loaded) {
                    def.resolve();
                }
            });
            def.done(hideLoader);
        }
        function showLoader() {
            $('h1').addClass('loading');
            $('#images').slideUp(function(){
                $(this).html('')
            });
        }
        function hideLoader() {
            $('h1').removeClass('loading');
            $('#images').slideDown();
        }
    </script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
			<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="when.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
            <pre><code contenteditable="true">Flickr
    .search(<span>'orange'</span>)
    .then(buildImages)</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример работы с $.Deferred</h1>
            <h2>С использованием AJAX и ожиданием подгрузки картинок. Код функции buildImages вы найдёте в исходном коде данной страницы</h2>
        </header>
        <article>
            <h2>Flickr</h2>
            <p>Попробуем загрузить картинки используя AJAX+JSONP. По получению ответа с сервера создаём объекты «Image» и ждём их загрузки,
            и лишь после этого отображаем готовый результат</p>
            <div id="images">
            </div>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
        </script>
	</div>
</body>
</html>

Callbacks

Callbacks – это крутой объект – он позволяет составлять списки функций обратного вызова, а также даёт бразды правления над ними. Работать с ним проще нежели с Deferred, тут нет разделения на позитивный и негативный сценарии, лишь стек функций, который будет выполнен по команде "fire()":

var C = $.Callbacks();
C.add(function(msg) {
console.log(msg+" first")
});
C.add(function(msg) {
console.log(msg+" second")
});
C.fire("Go");
>>>
Go first
Go second

— А в чём сила, брат?

— В аргументах

По умолчанию, вы можете прямо из консоли вызвать метод "fire()" снова и снова, и будете получать один и тот же результат раз за разом. А можно задать поведение Callbacks через флаги:

Наверное, будет лучше с примерами, вот "once":

var C = $.Callbacks("once");
C.add(function(msg) {
console.log(msg+" first")
});
C.add(function(msg) {
console.log(msg+" second")
});
C.fire("Go");
C.fire("Again"); // не даст результата, только Go
>>>
Go first
Go second

C "memory" посложнее, будьте внимательней:

var C = $.Callbacks("memory");
C.add(function(msg) {
console.log(msg+" first")
});
C.fire("Go");
C.add(function(msg) {
console.log(msg+" second")
});
C.fire("Again");
>>>
Go first
Go second // без флага, этой строчки не было бы
Again first
Again second

Пример с уникальностью прост до безобразия:

var C = $.Callbacks("unique");
var func = function(msg) {
console.log(msg+" first")
};
C.add(func);
C.add(func); // эта строка не повлияет на результат
C.fire("Go"); // только Go first
>>>
Go first

Флаг "stopOnFalse":

var C = $.Callbacks("stopOnFalse");
C.add(function(msg) {
console.log(msg+" first");
return false; // вот он – роковой false
});
C.add(function(msg) { console.log(msg+" second") });
C.fire("Go"); // только Go first
>>>
Go first

Перечисленные флаги можно комбинировать и получать интересные результаты, а можно не получать, а лишь посмотреть на пример:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример использования с $.Callbacks</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <style>
        article {
            overflow: visible;
            min-height: 120px;
            margin: 10px;
            position: relative;
        }
        #car {
            width: 100px;
            height: 100px;
            position: absolute;
            background: url(images/car.png) 50% 50% no-repeat;
            -webkit-transition: all 0.2s ease-in-out;
            -moz-transition: all 0.2s ease-in-out;
            -o-transition: all 0.2s ease-in-out;
            -ms-transition: all 0.2s ease-in-out;
            transition: all 0.2s ease-in-out;
        }
        .up {
            -webkit-transform: rotate(0deg);
            -moz-transform: rotate(0deg);
            -o-transform: rotate(0deg);
            -ms-transform: rotate(0deg);
            transform: rotate(0deg);
        }
        .down {
            -webkit-transform: rotate(180deg);
            -moz-transform: rotate(180deg);
            -o-transform: rotate(180deg);
            -ms-transform: rotate(180deg);
            transform: rotate(180deg);
        }
        .right {
            -webkit-transform: rotate(90deg);
            -moz-transform: rotate(90deg);
            -o-transform: rotate(90deg);
            -ms-transform: rotate(90deg);
            transform: rotate(90deg);
        }
        .left {
            -webkit-transform: rotate(270deg);
            -moz-transform: rotate(270deg);
            -o-transform: rotate(270deg);
            -ms-transform: rotate(270deg);
            transform: rotate(270deg);
        }
    </style>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <script>
        var C = $.Callbacks();
        var car;
        $(function(){
            car = $('#car');
            C.add(function(speed){
                appendOut('\nSpeed 200px per '+speed+' ms: ')
            });
            $(document).keydown(function(e){
                // 37 - left
                // 38 - up
                // 39 - right
                // 40 - down
                switch (e.keyCode) {
                    case 37:
                        C.add(function(speed){
                            car.queue(function(){
                                $(this).attr('class', 'left')
                                       .dequeue();
                            });
                            car.animate({left:'-=200'}, speed);
                            appendOut('← ');
                        });
                        appendOut('\nAdded ←');
                        break;
                    case 38:
                        C.add(function(speed){
                            car.queue(function(){
                                $(this).attr('class', 'up')
                                       .dequeue();
                            }).animate({top:'-=200'}, speed);
                            appendOut('↑ ');
                        });
                        appendOut('\nAdded ↑');
                        break;
                    case 39:
                        C.add(function(speed){
                            car.queue(function(){
                                $(this).attr('class', 'right')
                                       .dequeue();
                            });
                            car.animate({left:'+=200'}, speed);
                            appendOut('→ ');
                        });
                        appendOut('\nAdded →');
                        break;
                    case 40:
                        C.add(function(speed){
                            car.queue(function(){
                                $(this).attr('class', 'down')
                                       .dequeue();
                            });
                            car.animate({top:'+=200'}, speed);
                            appendOut('↓ ');
                        });
                        appendOut('\nAdded ↓');
                        break;
                }
            })
        });
    </script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
            <a href="deferred.pipe.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
            <pre><code><em>// use button up arrow: ↑</em>
C.add(function(speed){
    car.animate({top:'-=200px'}, speed)
})</code></pre>
            <pre><code><em>// use button down arrow: ↓</em>
C.add(function(speed){
    car.animate({top:'+=200px'}, speed)
})</code></pre>
            <pre><code><em>// use button right arrow: →</em>
C.add(function(speed){
    car.animate({left:'+=200px'}, speed)
})</code></pre>
            <pre><code><em>// use button left arrow: ←</em>
C.add(function(speed){
    car.animate({left:'-=200px'}, speed)
})</code></pre>
<pre><code contenteditable="true">C.fire(200)</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример работы $.Callbacks</h1>
            <h2>На практике мне ещё не приходилось использовать, поэтому пример немного надуманный - запрограммируйте поездку
                машинки используя стрелки на клавиатуре, а затем запустите программу, указав скорость</h2>
        </header>
        <div id="output">
            <h3>Output</h3>
            <pre></pre>
        </div>
        <article>
            <div id="car"></div>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Из истории: объект "Deferred" отпочковался от метода "ajax()" в результате рефакторинга версии 1.5. Шло время, появлялись новые версии jQuery, и вот новый виток рефакторинга – результатом стало отделение "Callbacks" от "Deferred" в версии 1.7, таким образом в текущей версии библиотеки метод "ajax()" работает с объектом "Deferred", который является надстройкой над "Callbacks". Дабы не вносить путаницу в терминологию, я использую определение "Deferred Callbacks" и при работе с "Callbacks", ибо колбэков много, и каждый раз уточнять, что я говорю именно "о том самом" - дело достаточно утомительное.

Статьи по данной теме:

Лекция 10. Пишем свой плагин

jQuery плагин

Для начала вспомним, для чего нам нужны плагины? Мой ответ — создание повторно используемого кода, и да — с удобным интерфейсом. Давайте напишем такой код, вот простая задачка: "По клику на параграф, текст должен измениться на красный"

JavaScript и даже не jQuery

Дабы не забывать истоков — начнем с реализации на нативном JavaScript'е:

var loader = function () {
// находим все параграфы
var para = document.getElementsByTagName('P');
// перебираем все, и вешаем обработчик
for (var i=0,size=para.length;i<size;i++) {
// обработчик
para[i].onclick = function() {
this.style.color = "#FF0000";
}
}
}
// естественно, весь код должен работать после загрузки всей страницы
document.addEventListener("DOMContentLoaded", loader, false);

Данный код не является кроссбраузерным, и написан с целью лишний раз подчеркнуть удобство использования библиотеки ;)

jQuery, но еще не плагин

Теперь можно этот код упростить, подключаем jQuery и получаем следующий вариант:

$(function(){
$('p').click(function(){
$(this).css('color', '#ff0000');
})
});

Таки jQuery плагин

С поставленной задачей мы справились, но где тут повторное использование кода? Или если нам надо не в красный, а в зеленый перекрасить? Вот тут начинается самое интересное, чтобы написать простой плагин достаточно расширить объект $.fn:

$.fn.mySimplePlugin = function() {
$(this).click(function(){
$(this).css('color', '#ff0000');
})
}

Если же писать более грамотно, то нам необходимо ограничить переменную $ только нашим плагином, а так же возвращать "this", чтобы можно было использовать цепочки вызовов (т.н. "chaining"), делается это следующим образом:

(function($) {
$.fn.mySimplePlugin = function(){
// код плагина
return this;
};
})(jQuery);

Внесу небольшое пояснение о происходящей тут "магии", код "(function($){…})(jQuery)" создает анонимную функцию, и тут же вызывает ее, передавая в качестве параметра объект jQuery, таким образом внутри анонимной функции мы можем использовать алиас $ не боясь за конфликты с другими библиотеками — так как теперь $ находится лишь в области видимости нашей функции, и мы имеем полный контроль над ней. Если у вас возникло ощущение дежавю – то всё верно, я об этом уже рассказывал

Добавим опцию по выбору цвета и получим рабочий плагин:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример плагина с лобальными настройками</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
	<script type="text/javascript" src="js/jquery.js"></script>
	<script type="text/javascript" src="js/code.js"></script>
    <script type="text/javascript">
        (function($) {
            // значени по умолчанию
            var defaults = {
                color:'green'
            };
            // актуальные настройки, глобальные
            var options;
            $.fn.mySimplePlugin = function(params){
                // при многократном вызове функции настройки будут сохранятся, и замещаться при необходимости
                options = $.extend({}, defaults, options, params);
                $(this).click(function(){
                    $(this).css('color', options.color);
                });
                return this;
            };
        })(jQuery);
    </script>

</head>
<body>
    <div id="content" class="wrapper box">
        <menu>
            <a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
            <pre><code>$(<span>'p:eq(0)'</span>).mySimplePlugin()</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code>$(<span>'p:eq(1)'</span>).mySimplePlugin({'color':'red'})</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code>$(<span>'p:eq(2)'</span>).mySimplePlugin({'color':'blue'})</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример плагина с глобальными настройками</h1>
            <h2>Запустите плагин с новыми настройками и проклацайте все параграфы</h2>
        </header>
        <article>
            <h2>Article Title</h2>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article Title</h2>
            <p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
            laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
            non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
            ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
            sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
            vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
            magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
            cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>
(function($) {
// значение по умолчанию - ЗЕЛЁНЫЙ
var defaults = { color:'green' };
// актуальные настройки, глобальные
var options;
$.fn.mySimplePlugin = function(params){
// при многократном вызове настройки будут сохранятся
// и замещаться при необходимости
options = $.extend({}, defaults, options, params);
$(this).click(function(){
$(this).css('color', options.color);
});
return this;
};
})(jQuery);

Вызов:

// первый вызов
$('p:first,p:last').mySimplePlugin();
// второй вызов
$('p:eq(1)').mySimplePlugin({ color: 'red' });

В результате работы данного плагина, каждый клик будет изменять цвет параграфа на красный, т.к. мы используем глобальную переменную для хранения настроек, то второй вызов плагина изменят значение для всех элементов.

Можно внести небольшие изменения, и разделить настройки для каждого вызова:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример плагина с индивидуальными настройками</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
	<script type="text/javascript" src="js/jquery.js"></script>
	<script type="text/javascript" src="js/code.js"></script>
    <script type="text/javascript">
        (function($) {
            // значени по умолчанию
            var defaults = {
                color:'green'
            };
            $.fn.mySimplePlugin = function(params){
                // актуальные настройки, будут индивидуальными при каждом запуске
                var options = $.extend({}, defaults, options, params);
                $(this).click(function(){
                    $(this).css('color', options.color);
                });
                return this;
            };
        })(jQuery);
    </script>

</head>
<body>
    <div id="content" class="wrapper box">
        <menu>
            <a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
            <pre><code>$(<span>'p:eq(0)'</span>).mySimplePlugin()</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code>$(<span>'p:eq(1)'</span>).mySimplePlugin({'color':'red'})</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code>$(<span>'p:eq(2)'</span>).mySimplePlugin({'color':'blue'})</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример плагина с индивидуальными настройками</h1>
            <h2>Разница лишь в одном объявлении var</h2>
        </header>
        <article>
            <h2>Article Title</h2>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article Title</h2>
            <p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
            laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
            non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
            ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
            sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
            vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
            magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
            cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>
// актуальные настройки, будут индивидуальными при каждом запуске
var options = $.extend({}, defaults, params);

А разница то в одном "var". Мне даже сложно себе представить как много часов убито в поисках потерянного "var" в JavaScript'е, будьте внимательны

Работаем с коллекциями объектов

Тут все просто, достаточно запомнить — "this" содержит jQuery объект с коллекцией всех элементов, т.е.:

$.fn.mySimplePlugin = function(){
console.log(this); // это jQuery объект
console.log(this.length); // число элементов в выборке
};

Если мы хотим обрабатывать каждый элемент то соорудим следующую конструкцию внутри нашего плагина:

// необходимо обработать каждый элемент в коллекции
return this.each(function(){
$(this).click(function(){
$(this).css('color', options.color);
});
});
// предыдущий вариант немного избыточен,
// т.к. внутри функции click и так есть перебор элементов
return this.click(function(){
$(this).css('color', options.color);
});

Опять же напомню, если ваш плагин не должен что-то возвращать по вашей задумке — возвращайте "this" — цепочки вызовов в jQuery это часть магии, не стоит её разрушать. Методы "each()" и "click()" возвращают объект jQuery.

Публичные методы

Так, у нас написан крутой плагин, надо бы ему еще докрутить функционала, пусть цвет регулируется несколькими кнопками на сайте. Для этого нам понадобится некий метод "color", который и будет в ответе за всё. Сейчас приведу пример кода готового плагина — будем курить вместе (обращайте внимание на комментарии):

// настройки со значением по умолчанию
var defaults = { color:'green' };
// наши будущие публичные методы
var methods = {
// инициализация плагина
init: function(params) {
// настройки, будут индивидуальными при каждом запуске
var options = $.extend({}, defaults, params);
// инициализируем лишь единожды
if (!this.data('mySimplePlugin')) {
// закинем настройки в реестр data
this.data('mySimplePlugin', options);
// добавим событий
this.bind('click.mySimplePlugin', function(){
$(this).css('color', options.color);
});
}
return this;
},
// изменяем цвет в реестре
color: function(color) {
var options = $(this).data('mySimplePlugin');
options.color = color;
$(this).data('mySimplePlugin', options);
},
// сброс цвета элементов
reset: function() {
$(this).css('color', 'black');
}
};
$.fn.mySimplePlugin = function(method){
// немного магии
if ( methods[method] ) {
// если запрашиваемый метод существует, мы его вызываем
// все параметры, кроме имени метода прийдут в метод
// this так же перекочует в метод
return methods[ method ].apply( this, Array.prototype.slice.call(
arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
// если первым параметром идет объект, либо совсем пусто
// выполняем метод init
return methods.init.apply( this, arguments );
} else {
// если ничего не получилось
$.error('Метод "' + method + '" в плагине не найден');
}
};

Теперь еще небольшой пример использование данных методов:

// вызов без параметров - будет вызван init
$('p').mySimplePlugin();
// вызов метода color и передача цвета в качестве параметров
$('p').mySimplePlugin('color', '#FFFF00');
// вызов метода reset
$('p').mySimplePlugin('reset');

Для понимания данного кусочка кода, вы должны разобраться лишь с переменной "arguments", и с методом "apply()". Тут им целые статьи посвятили, дерзайте:

О обработчиках событий

Если ваш плагин вешает какой-либо обработчик, то лучше всего (читай всегда) данный обработчик повесить в своём собственном namespace:

return this.bind("click.mySimplePlugin", function(){
$(this).css('color', options.color);
});

Данный финт позволит в любой момент убрать все ваши обработчики, или вызвать только ваш, что очень удобно:

// вызовем лишь наш обработчик
$('p').trigger("click.mySimplePlugin");
// убираем все наши обработчики
$('p').unbind(".mySimplePlugin");

Дежавю? Ок!

На этом об обычных плагинах всё, хотя дам ещё чуток информации к размышлению, но на английском:

Data

Если по какой-то причине вы еще не знакомы с "data()" — то советую прочитать документацию и усвоить незамедлительно. Если же в двух словах — это реестр данных, и все данные привязанные к какому-либо элементу лучше хранить в нём, это же правило касается и плагинов. Если вам надо сохранить состояние плагина — используйте "data()", если необходим кеш — используйте "data()", если вам необходимо сохранить … ну думаю понятно. Приведу еще примерчик связанный с инициализацией:

function() { // функция init
var init = $(this).data('mySimplePlugin');
if (init) {
return this;
} else {
$(this).data('mySimplePlugin', true);
return this.bind('click.mySimplePlugin', function(){
$(this).css('color', options.color);
});
}
}

По стечению обстоятельств, в HTML5 появились data-атрибуты, и для доступа к ним jQuery использует тот же метод "data()", но вот дела, "jQuery.data()" – не манипулирует атрибутами HTML, а работает со своим реестром, и лишь при отсутствии там данных пытается заполучить атрибут "data-*", не попадитесь:

<div id="my" data-foo="bar"></div>
$("#my").data("foo"); // >>> bar
$("#my").attr("data-foo"); // >>> bar
$("#my").data("foo", "xyz");
$("#my").data("foo"); // >>> xyz
$("#my").attr("data-foo"); // >>> bar
$("#my").attr("data-foo", "def");
$("#my").data("foo"); // >>> xyz
$("#my").attr("data-foo"); // >>> def
<div id="my" data-foo="def"></div>

Animate

Информация в данном разделе актуальна для jQuery версии 1.8 и выше, если вас заинтересуют возможности расширения для более старых версий, то читайте мою статью "Пишем плагины анимации"

Для начала затравка – метод "animate()" манипулирует объектом "jQuery.Animation", который предусматривает следующие точки для расширения функционала:

Начну рассказ с "jQuery.Tween.propHooks", т.к. уже есть плагины, в код которых можно заглянуть :) Для пущей наглядности я возьму достаточно тривиальную задачу – заставим плавно изменить цвет шрифта для заданного набора элементов:

$('p').animate({color:'#ff0000'});

Приведенный выше код не даст никакого эффекта, т.к. свойство "color" библиотека из коробки не анимирует, но это можно исправить – надо лишь прокачать "jQuery.Tween.propHooks":

$.Tween.propHooks.color = {
get: function(tween) {
return tween.elem.style.color;
}
set: function(tween) {
tween.easing; // текущий easing
tween.elem; // испытуемый элемент
tween.options; // настройки анимация
tween.pos; // текущий прогресс
tween.prop; // свойство которое изменяем
tween.start; // начальные значения
tween.now; // текущее значение
tween.end; // желаемое результирующие значения
tween.unit; // еденицы измерения
}
}

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

console.log(tween);
>>>
easing: "swing"
elem: HTMLParagraphElement
end: "#ff0000"
now: "NaNrgb(0, 0, 0)"
options: Object
complete: function (){}
duration: 1000
old: false
queue: "fx"
specialEasing: Object
pos: 1
prop: "color"
start: "rgb(0, 0, 0)"
unit: "px"

В консоле у нас будет очень много данных, т.к. приведённый метод вызывается N кол-во раз, в зависимости от продолжительности анимации, при этом "tween.pos" постепенно наращивает своё значение с 0 до 1. По умолчанию, наращивание происходит линейно, если надо как-то иначе — то стоит посмотретьли дочитать раздел до конца (об этом я уже упоминал в лекции Анимация)

Даже при таком раскладе мы уже можем изменять выбранный элемент (путём манипуляций над "tween.elem"), но есть более удобный способ – можно установить свойство "run" объекта "tween":

$.Tween.propHooks.color = {
set: function(tween) {
// тут будет инициализация
tween.run = function(progress) {
// тут код отвечающий за измение свойств элемента
}
}
}

Получившийся код будет работать следующим образом:

  1. единожды будет вызвана функция "set"
  2. функция run будет вызвана N-раз, при этом "progress" будет себя вести аналогично "tween.pos"

Теперь, возвращаясь к изначальной задачи по изменению цвета можно наворотить следующий код:

$.Tween.propHooks.color = {
set: function(tween) {
// приводим начальное и конечное значения к единому формату
// #FF0000 == [255,0,0]
tween.start = parseColor(tween.start);
tween.end = parseColor(tween.end);
tween.run = function(progress) {
tween.elem.style['color'] =
// вычисляем промежуточное значение
buildColor(tween.start, tween.end, progress);
}
}
}

Код функций "parseColor()" и "buildColor()" вы найдёте в листинге на странице color.html

Результатом станет плавное перетекание исходного цвета к красному (#F00 == #FF0000 == 255,0,0), вживую можно посмотреть на странице со следующим кодом

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример расширения анимации</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
	<script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript">
    /**
     * @author Anton Shevchuk (AntonShevchuk@gmail.com)
     * @uri    http://anton.shevchuk.name
     */
    (function($) {
        /**
         * parse HEX color to DEC
         * e.g. #f00    => [255,0,0]
         *      #ff00ff => [255,0,255]
         * @param {Array} color
         */
        function parseColor(color) {
            if (color.length != 4 &&
                    color.length != 7) {
                return [0,0,0];
            }
            color = color.substr(1);
            if (color.length == 3) {
                color = color[0]+color[0]+
                        color[1]+color[1]+
                        color[2]+color[2];
            }
            var R = parseInt(color.substr(0,2),16);
            var G = parseInt(color.substr(2,2),16);
            var B = parseInt(color.substr(4,2),16);
            return [R,G,B];
        };
        /**
         * Build color for apply as style
         */
        function buildColor(start, end, progress) {
            // нехитрое вычисление
            var R = Math.round(((end[0] - start[0]) * progress) + start[0]);
            var G = Math.round(((end[1] - start[1]) * progress) + start[1]);
            var B = Math.round(((end[2] - start[2]) * progress) + start[2]);
            return 'rgb('+R+','+G+','+B+')';
        }
        $.Tween.propHooks.color = {
            set: function(tween) {
                // console.log(tween);
                // испытуемый элемент
                tween.elem;
                // текущий easing
                tween.easing
                // настройки анимация
                tween.options;
                // текущий прогресс
                tween.pos;
                // свойство которое изменяем
                tween.prop;
                // начальные значения
                tween.start = parseColor(tween.start);
                // текущее значение
                tween.now;
                // результирующие значения
                tween.end = parseColor(tween.end);
                // еденицы измерения
                tween.unit;
                tween.run = function(progress) {
                    // непосредственно измение свойств элемента
                    tween.elem.style['color'] = buildColor(tween.start, tween.end, progress);
                }
            }
        }
    })(jQuery);
    $(function(){
        $('p:eq(0)').animate({color:'#ff0000'},1000);
        $('p:eq(1)').animate({color:'#ff0000'},3000);
        $('p:eq(2)').animate({color:'#ff0000'},5000);
    });
</script>

</head>
<body>
    <div id="content" class="wrapper box">
        <menu>
            <a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
        </menu>
        <header>
            <h1>Пример расширения анимации</h1>
            <h2>Данный пример работает лишь с jQuery 1.8+</h2>
        </header>
        <article>
            <h2>Article Title</h2>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article Title</h2>
            <p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
            laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
            non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
            ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
            sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
            vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
            magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
            cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

В плагине jQuery Color для решения поставленной задачи использовали jQuery.cssHooks, но мы же не ищем лёгких путей.

Ещё хотел было рассказать про префильтры анимации, но – документации нет, а как использовать "в жизни" – я не догадался, но чуть-чуть информации таки накопал (код можно найти в функции "Animation"):

jQuery.Animation.prefilter(function(element, props, opts) {
// deffered объект animate
element; // искомый элемент
props; // настройки анимации из animate()
opts; // опции анимации
// отключаем анимацию при попытке анимировать высоту элемента
if (props['height'] != undefined) {
return this;
}
});

Пример:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример расширения animate через префильтры</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <script>
        // jQuery.Animation.preFilter( function( element, props, opts ) {})
		jQuery.Animation.prefilter(function(element, props, opts) {
            //console.log(this, element, props, opts);
            // disable "height" animation
            if (props['height'] != undefined) {
                return this;
            }
        });
    </script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
			<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
            <pre><code contenteditable="true">$(<span>'img'</span>).animate({
    <span>'width'</span>:<span>'+=20px'</span>
}, 2000)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code contenteditable="true"><em>// disabled</em>
$(<span>'img'</span>).animate({
    <span>'height'</span>:<span>'+=20px'</span>
}, 2000)</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Анимация</h1>
            <h2>Пример расширения через префильтры</h2>
        </header>
        <article id="stick" class="box">
            <h2>Article</h2>
            <p>
                <img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200"/>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Про "jQuery.Animation.tweener" так же много не расскажешь, но пример получилось сделать чуток интересней – приведённый код позволяет анимировать ширину и высоту объекта по заданной диагонали:

Осторожно, для понимания происходящего потребуются знания геометрии за 8-ой класс

// создаём поддержку нового свойства для анимации – diagonal
jQuery.Animation.tweener( "diagonal", function( property, value ) {
// создаём tween объект
var tween = this.createTween( property, value );
// промежуточные вычисления и данные
var a = jQuery.css(tween.elem, 'width', true );
var b = jQuery.css(tween.elem, 'height', true );
var c = Math.sqrt(a*a + b*b), sinA = a/c, sinB = b/c;
tween.start = c;
tween.end = value;
tween.run = function(progress) {
// вычисление искомого значения – новое значение гипотенузы
var hyp = this.start + ((this.end - this.start) * progress);
// непосредственно измение свойств элемента
tween.elem.style.width = sinA*hyp + tween.unit; // ширина
tween.elem.style.height = sinB*hyp + tween.unit; // высота
};
return tween;
});

Пример работы:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример расширения animate через tweener</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <script>
        // jQuery.Animation.tweener( propsList, function( property, value ) {})
        jQuery.Animation.tweener( "diagonal", function( property, value ) {
            var tween = this.createTween( property, value );
            var target = tween.cur();
            var elem = tween.elem;
/*
            A
            |\
          b | \ c
            |  \
            |___\
          C   a   B
*/
            var a = jQuery.css( elem, 'width', true );
            var b = jQuery.css( elem, 'height', true );
            var c =  Math.sqrt(a*a + b*b);
            var sinA = a/c;
            var sinB = b/c;
            appendOut('Hypotenuse (start): '+c+'\n');
            tween.start = c;
            tween.end = value;
            tween.run = function(progress) {
                // вычисление искомого значения
                var newc = this.start + ((this.end - this.start) * progress);
                var width = sinA*newc;
                var height = sinB*newc;
                // непосредственно измение свойств элемента
                if (progress == 1) {
                    width = Math.ceil(width);
                    height = Math.ceil(height);
                    appendOut('Hypotenuse (finish): '+Math.sqrt(width*width + height*height)+'\n');
                }
                tween.elem.style.width = width + tween.unit;
                tween.elem.style.height = height + tween.unit;
            };
            return tween;
        });
    </script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
			<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
            <pre><code contenteditable="true">$(<span>'img'</span>).animate({
    <span>'diagonal'</span>:400
}, 2000)</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <div id="output">
            <h3>Output</h3>
            <pre></pre>
        </div>
        <header>
            <h1><a href="index.html">Анимация</a></h1>
            <h2>Пример расширения анимации через tweener</h2>
        </header>
        <article id="stick" class="box">
            <h2>Article</h2>
            <p>
                <img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200" />
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Easing

Теперь опять обратимся к easing’у – приведу пример произвольной функции, которой будет следовать анимация. Дабы особо не фантазировать – я взял пример из статьи на вездесущем хабре o анимации в MooTools фреймворке [http://habrahabr.ru/post/43379/] – наглядный пример с сердцебиением, которое описывается следующими функциями:



В расширении функционала easing нет ничего военного:

$.extend($.easing, {
/**
* Heart Beat
*
* @param x progress
* @param t current time
* @param b = 0
* @param c = 1
* @param d duration
* @link http://habrahabr.ru/blogs/mootools/43379/
*/
heart:function(x, t, b, c, d) {
if (x < 0.3) return Math.pow(x, 4) * 49.4;
if (x < 0.4) return 9 * x - 2.3;
if (x < 0.5) return -13 * x + 6.5;
if (x < 0.6) return 4 * x - 2;
if (x < 0.7) return 0.4;
if (x < 0.75) return 4 * x - 2.4;
if (x < 0.8) return -4 * x + 3.6;
if (x >= 0.8) return 1 - Math.sin(Math.acos(x));
}
});

Чуть-чуть пояснений, конструкция "$.extend({}, {})" "смешивает" объекты:

$.extend({name:"Anton"}, {location:"Kharkiv"});
>>>
{
name:"Anton",
location:"Kharkiv"
}
$.extend({name:"Anton", location:"Kharkiv"}, {location:"Kyiv"});
>>>
{
name:"Anton",
location:"Kyiv"
}

Таким образом мы "вмешиваем" новый метод к существующему объекту "$.easing"; согласно коду, наш метод принимает в качестве параметров следующие значения:

Результат конечно интересен, но его можно ещё чуть-чуть расширить дополнительными функциями (развернем и скомбинируем):

heartIn: function (x, t, b, c, d) {
return $.easing.heart(x, t, b, c, d);
},
heartOut: function (x, t, b, c, d) {
return c - $.easing.heart(1 - x, t, b, c, d) + b;
},
heartInOut: function (x, t, b, c, d) {
if (t < d/2) return $.easing.heartIn(x, t, b, c, d);
return $.easing.heartOut(x, t, b, c, d);
}

Получим следующие производные функции:



Работать с данным творением надо следующим образом:

$("#my").animate({height:"+200px"}, 2000, "heartIn"); // вот оно

Пример работы данной функции можно пощупать на примере

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример расширения объекта easing</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <style type="text/css">
        .heart{
            width:200px;
            height:200px;
            background:#fff;
            margin:0 auto;
            overflow:hidden;
            position: relative;
            border-radius: 4px;
            -moz-border-radius: 4px;
            -webkit-border-radius: 4px;
            margin-bottom: 4px;;
        }
        .heart img{
            position:absolute;
            margin:50px;
            width: 100px;
            height: 100px;
            border: 0
        }
        .heart p{
            position:absolute;
            width:200px;
            padding: 0;
            text-align: center;
            margin-top: 8px;
            font-weight: 700;
        }
        .block {
            height: 100px;
        }
        .target {
            height: 10px;
            background: #999999;
        }
    </style>
	<script type="text/javascript" src="js/jquery.js"></script>
	<script type="text/javascript" src="js/code.js"></script>
    <script type="text/javascript">
        $.extend($.easing, {
            /**
             * Heart Beat
             *
             * @param x
             * @param t current time
             * @param b begining
             * @param c change in value
             * @param d duration
             *
             * @link http://habrahabr.ru/blogs/mootools/43379/
             */
            heart: function (x, t, b, c, d) {
                if (x < 0.3)  return Math.pow(x, 4) * 49.4;
                if (x < 0.4)  return 9 * x - 2.3;
                if (x < 0.5)  return -13 * x + 6.5;
                if (x < 0.6)  return 4 * x - 2;
                if (x < 0.7)  return 0.4;
                if (x < 0.75) return 4 * x - 2.4;
                if (x < 0.8)  return -4 * x + 3.6;
                if (x >= 0.8) return 1 - Math.sin(Math.acos(x));
            },
            heartIn: function (x, t, b, c, d) {
                return $.easing.heart(x, t, b, c, d);
            },
            heartOut: function (x, t, b, c, d) {
                return c - $.easing.heart(1 - x, t, b, c, d) + b;
            },
            heartInOut: function (x, t, b, c, d) {
                if (t < d/2) return $.easing.heartIn(x, t, b, c, d);
                return $.easing.heartOut(x, t, b, c, d);
            }
        });
        $(function(){
            $('.heart').click(function(){
                var easing = $(this).data('easing');
                $(this).find('img')
                        .animate({width:"-=100px",height:"-=100px",left:"+=50px",top:"+=50px"},3000, easing)
                        .animate({width:"+=100px",height:"+=100px",left:"-=50px",top:"-=50px"},200);
            });
        });
    </script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu>
            <a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
            <pre><code>$(<span>'.target'</span>).animate({height:<span>'+=100px'</span>}, 2000, <span>'heartIn'</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code>$(<span>'.target'</span>).animate({height:<span>'+=100px'</span>}, 2000, <span>'heartOut'</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code>$(<span>'.target'</span>).animate({height:<span>'+=100px'</span>}, 2000, <span>'heartInOut'</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример расширять объект easing</h1>
            <h2>Пробуем, играемся, и мотаем на ус (покликайте по сердцам)</h2>
        </header>
        <article>
            <div class="block">
                <div class="target"></div>
            </div>
            <div class="heart" data-easing="heartIn">
                <p>heartIn</p>
                <img src="images/heart.png" alt="Heart" />
            </div>
            <div class="heart" data-easing="heartOut">
                <p>heartOut</p>
                <img src="images/heart.png" alt="Heart" />
            </div>
            <div class="heart" data-easing="heartInOut">
                <p>heartInOut</p>
                <img src="images/heart.png" alt="Heart" />
            </div>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Sizzle

Когда я рассказывал о Sizzle я решил вас не грузить возможностями по расширению библиотеки, но вот время настало… В Sizzle можно расширять много чего:

Но браться мы будем лишь за расширение псевдо-селекторов, наподобие:

$("div:animated"); // поиск анимированных элементов
$("div:hidden"); // поиск скрытых элементов div
$("div:visible"); // поиск видимых элементов div

Почему я привел только эти фильтры? Всё просто — только они не входят в Sizzle, и относятся лишь к jQuery, именно такие плагины мы будем тренироваться разрабатывать. Начнем с кода фильтра ":hidden":

// пример для расширения Sizzle внутри jQuery
// для расширения самого Sizzle нужен чуть-чуть другой код
jQuery.expr.filters.hidden = function( elem ) {
// проверяем ширину и высоту каждого элемента в выборке
return elem.offsetWidth === 0 || elem.offsetHeight === 0;
};

Выглядит данный код несложно, но, пожалуй, я таки дам каркас для нового фильтра и добавлю чуть-чуть пояснений:

$.extend($.expr[':'], {
/**
* @param element DOM элемент
* @param i порядковый номер элемента
* @param match объект матчинга регулярного выражения
* @param elements массив всех найденных DOM элементов
*/
test: function(element, i, match, elements) {
/* тут будет наш код, и будет решать кто виноват */
return true || false; // выносим вердикт
}
})

Ну теперь попробуем решить следующую задачку:

Необходимо выделить ссылки в тексте в зависимости от её типа: внешняя, внутренняя или якорь

Для решения лучше всего подошли бы фильтры для селекторов следующего вида:

$("a:internal");
$("a:anchor");
$("a:external");

Поскольку "из коробки" данный функционал не доступен, мы напишем его сами, для этого нам понадобится не так уж и много :

$.extend($.expr[':'], {
// определения внешней ссылки
// нам понадобится лишь DOM Element
external: function(element) {
// а у нас ссылка?
if (element.tagName.toUpperCase() != 'A') return false;
// есть ли атрибут href
if (element.getAttribute('href')) {
var href = element.getAttribute('href');
// отсекаем ненужное
if ((href.indexOf('/') === 0) // внутренняя ссылка
|| (href.indexOf('#') === 0) // якорь
// наш домен по http:// или https://
|| (href.indexOf(window.location.hostname) === 7)
|| (href.indexOf(window.location.hostname) === 8)
) {
return false; // мимо
} else {
return true; // да, мы нашли внешние ссылки
}
} else {
return false;
}
}
})

Пример лишь для последнего ":external", рабочий код:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример создания фильтра для Sizzle</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
	<script type="text/javascript" src="js/code.js"></script>
    <script>
        (function($, window, undefined){
            $.extend($.expr[':'], {
                /**
                 * Get internal elements
                 *
                 * @param element DOM Element
                 * @param i Integer Index
                 * @param match Object All matched data
                 * @param elements Array of Dom Elements
                 */
                internal: function(element, i, match, elements) {
                    // internal links
                    if (element.tagName.toUpperCase() != 'A') return false;
                    if (element.getAttribute('href')) {
                        if ((element.getAttribute('href').indexOf('/') === 0)
                            || (element.getAttribute('href').indexOf(window.location.hostname) === 7) // http://...
                            || (element.getAttribute('href').indexOf(window.location.hostname) === 8) // https://...
                            ) {
                            return true;
                        } else {
                            return false;
                        }
                    } else {
                        return false;
                    }
                },
                anchor: function(element) {
                    // anchor only
                    if (element.tagName.toUpperCase() != 'A') return false;
                    if (element.getAttribute('href')) {
                        return (element.getAttribute('href').indexOf('#') === 0);
                    } else {
                        return false;
                    }
                },
                external: function(element) {
                    // external link
                    if (element.tagName.toUpperCase() != 'A') return false;
                    if (element.getAttribute('href')) {
                        if ((element.getAttribute('href').indexOf('/') === 0)
                            || (element.getAttribute('href').indexOf('#') === 0)                      // #anchor
                            || (element.getAttribute('href').indexOf(window.location.hostname) === 7) // http://...
                            || (element.getAttribute('href').indexOf(window.location.hostname) === 8) // https://...
                                ) {
                            return false;
                        } else {
                            return true;
                        }
                    } else {
                        return false;
                    }
                }
            })
        })(jQuery, window);
    </script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu>
            <a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
			<pre><code>$(<span>'a:internal'</span>).css({
    color:<span>'#ff5555'</span>,
    fontSize:<span>'24px'</span>
});</code></pre>
            <button type="button" class="code">Run Code</button>
			<pre><code>$(<span>'a:anchor'</span>).css({
    color:<span>'#33aa33'</span>,
    fontSize:<span>'24px'</span>
});</code></pre>
            <button type="button" class="code">Run Code</button>
			<pre><code>$(<span>'a:external'</span>).css({
    color:<span>'#5555ff'</span>,
    fontSize:<span>'24px'</span>
});</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример создания фильтра для Sizzle</h1>
            <h2>Пробуем найти элементы по своему фильтру</h2>
        </header>
        <article>
            <p>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut lacinia quam nec enim scelerisque porta.
            In ut lorem ipsum. <a href="#">Proin iaculis viverra rutrum</a>. Maecenas quis ante enim. Vestibulum ante ipsum primis
            in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum luctus tristique feugiat. Morbi
            dictum est dolor, at condimentum nisl. Ut id nunc augue, at luctus enim. Phasellus urna nunc, aliquam
            sit amet rutrum ac, imperdiet in nunc. Cras mattis massa et est sodales ac auctor mi sagittis. Fusce
            elementum ultrices nunc, eu scelerisque massa sodales quis. Aliquam bibendum accumsan nibh ut blandit.
            Vestibulum suscipit leo vitae magna tincidunt pharetra. Sed consequat, ligula non pretium ultrices,
            ligula enim venenatis urna, in scelerisque urna turpis vel neque. Praesent nibh urna, eleifend et
            fringilla eu, pellentesque sit amet nisl.
            </p>
            <p>
            <a href="http://google.com">Praesent venenatis dictum ante</a>, et viverra diam ultrices et. Maecenas egestas augue eget nibh
            consequat tempus. Quisque facilisis ipsum non mi molestie faucibus. Integer sapien est, mattis
            eu suscipit nec, auctor id dolor. In hac habitasse platea dictumst. Duis adipiscing tristique
            ipsum, ut tempus eros porta ut. Nulla enim quam, auctor ut venenatis at, pharetra eget enim.
            Nam suscipit quam eu erat adipiscing aliquam. Etiam aliquet sagittis viverra. <a href="http://anton.shevchuk.name/jquery/">Ut ultricies</a>,
            neque nec bibendum commodo, sem arcu lobortis nunc, ut luctus tellus erat at sem.
            </p>
            <p>
            Aliquam erat volutpat. <a href="/index.html">Nunc vel augue sagittis lacus aliquet consequat</a>. Integer eros risus,
            posuere non volutpat a, mattis et elit. Curabitur congue enim in purus sagittis eu ultrices sem
            sollicitudin. Morbi sit amet nibh sapien. Nulla facilisi. Integer neque purus, commodo eu tempor eu,
            interdum sit amet quam. <a href="http://hohli.com/">Vestibulum mollis ullamcorper ipsum sit amet imperdiet</a>. Nullam lorem nunc,
            scelerisque ac volutpat ut, elementum ut risus. Integer placerat turpis purus. Vestibulum quis turpis
            ut justo sodales vulputate. Duis lectus tellus, gravida non commodo eu, dictum a tellus.
            Praesent a nibh vel nisl sodales tincidunt at ac leo.
            </p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

ВСЕГДА используйте фильтр вместе с HTML тэгом который ищите:

$("tag:filter")

Это один из пунктов оптимизации работы с фильтрами jQuery, иначе ваш фильтр будет обрабатывать все DOM элементы на странице, а это может очень сильно сказаться на производительности. Если же у вас несколько тегов, то пишите уж лучше так — "$("tag1:filter, tag2:filter, tag3:filter")", или ещё лучше через вызов метода "filter()".

— "Sizzle Documentation" — скудненькая официальная документация [https://github.com/jquery/sizzle/wiki/Sizzle-Documentation]

Лекция 11. Дополнение

Дополнение

Еще чуть-чуть о полезном инструментарии: есть такой классный плагин – jQuery-inlog [http://prinzhorn.github.com/jquery-inlog/] – основное его назначение — дать нам чуть-чуть больше понимания о происходящем внутри самого jQuery, вот кусочек HTML:

<body>
<div class="bar">
<div class="bar">
<div id="foo"></div> 
</div>
</div>
<div id="bacon"></div> </body>

А вот и код, который его обслуживает:

$l(true);
$("#foo").parents(".bar").next().prev().parent().fadeOut();
$l(false);

Какие-то странные манипуляции, для какого же элемента будет применён метод "fadeout()"? Для выяснения оного наш код обёрнут в вызов метода "$l()". "$l()" — это и есть собственно вызов плагина, результат его работы можно найти в консоли:



У данного плагина есть ещё настройки, которые регулируют объём информации выводимой в консоль.

Пример и скриншот взять с официальной документации по плагину

jQuery UI

jQuery UI представляет из себя набор виджетов и плагинов от самих разработчиков jQuery. По моему мнению, данный инструмент необходимо изучить настолько, насколько это требуется чтобы не писать свои "велосипеды". Скачать-почитать о данной надстройке над jQuery можно на домашней страницы проекта – http://jqueryui.com/

Что нам необходимо знать о виджетах и плагинах? Первое – это какие они есть, и второе – как работают. На этих двух моментах я и постараюсь остановиться.

Интерактивность

Начну с полезных плагинов, которые могут упростить жизнь при создании интерактивных интерфейсов:

Виджеты

Виджеты – это уже комплексное решение содержащие не только JavaScript код, но и некую HTML и CSS реализацию:

На этом обзор виджетов можно считать законченным, вернёмся к плагинам.

Все виджеты и плагины завязаны на ядро jQuery UI, но есть так же зависимости между самими плагинами и стоит о них помнить. Но не переживайте – при сборке jQuery UI пакета все зависимости проверяются автоматически, т.е. когда вам потребуется доселе неподключенный виджет, лучше скачать сборку заново.

Утилиты

Утилит у нас не много – вот полезный плагин – position, который позволяет контролировать положение DOM элементов – http://jqueryui.com/position/, а ещё есть фабрика по созданию виджетов, но о ней я расскажу чуть попозже.

Эффекты

Среди эффектов предоставляемых jQuery UI я выделяю четыре пункта:

За анимацию цвета отвечает компонент "Effects Core", который позволяет анимировать изменения цвета посредством использования функции "animate()":

$("#my").animate({ backgroundColor: "black" }, 1000);

Да-да, jQuery из коробки не умеет этого делать, а вот jQuery UI позволяет анимировать следующие параметры:

Ещё одной возможностью заключенной в "Effects Core" является анимация изменений класса DOM элемента, т.е. когда вы будете присваивать новый класс элементу, то вместо обычного моментального применения новых CSS свойств вы будете наблюдать анимацию этих свойств от текущих до заданных в присваиваемом классе. Для использования данного функционала нам потребуются старые знакомые – методы "addClass()", "toggleClass()" и "removeClass()", с одной лишь разницей – при вызове метода вторым параметром должна быть указана скорость анимации:

$("#my").addClass("active", 1000);
$("#my").toggleClass("active", 1000);
$("#my").removeClass("active", 1000);

Если из предыдущего абзаца у вас не возникло понимания сути происходящего, то этот код для вас:

<style>
#my {
font-size:14px;
}
#my.active {
font-size:20px;
}
</style>
<script>
$(function (){
$("#my").addClass("active", 1000);
// тут получается аналогично следующему вызову
$("#my").animate({"font-size":"20px"}, 1000);
});
</script>

А ещё появляется метод switchClass(removeClass, addClass, duration), который заменяет один класс другим, но мне он ни разу не пригодился.

О наборе эффектов я не буду долго рассказывать, их лучше посмотреть в действии на странице http://jqueryui.com/effect/. Для работы с эффектами появляется метод "effect()", но сам по себе его лучше не использовать, ведь UI расширил функционал встроенных методов "show()", "hide()" и "toggle()", теперь, передав в качестве параметра скорости анимации названия эффекта вы получите необходимый результат:

$("#my").hide("puff");
$("#my").show("transfer");
$("#my").toggle("explode");

Приведу список эффектов, может кто запомнит: blind, bounce, clip, drop, explode, fold, highlight, puff, pulsate, scale, shake, size, slide, transfer.

Помните, я в главе о анимации рассказывал о easing и одноименном плагине для jQuery? Так вот UI тоже расширяет easing, так что подключив его можно отключать плагин. И да, этот функционал завязан лишь на "Effects Core".

Темы

Одной из самых замечательных особенностей jQuery UI является возможность менять "шкурки" всех виджетов разом, и для этого даже предусмотрена специальная утилита – ThemeRoller [http://jqueryui.com/themeroller/]:



Если в какой-то момент времени потребуется внести изменения в тему, то откройте файл jquery-ui-#.#.##-custom.css и найдёте строчку начинающуюся с текста "To view and modify this theme, visit http://..." и таки пройдите по указанной ссылке, и уже используя ThemeRoller внесите необходимые изменения.

Пишем свой виджет

Отправной точкой при написания виджета для jQuery UI для вас будет официальная документация, но поскольку со знанием английского не у всех сложилось, то я постараюсь перевести и адаптировать информацию изложенную в ней.

Первое, о чём стоит рассказать, это то, что правила написания плагинов для jQuery слишком вальяжны, что не способствует их качеству. При создании jQuery UI, походу, решили пойти путём стандартизации процесса написания плагинов и виджетов, я не могу сказать насколько задумка удалась, но стало явно лучше чем было. Начну с описания каркаса для вашего виджета:

$.widget("book.expose", {
// настройки по умолчанию
options: {
color: "red"
},
// инициализация widget
// вносим изменения в DOM и вешаем обработчики
_create: function() {
this.element; // искомый объект в jQuery обёртке
this.name; // имя - expose
this.namespace; // пространство – book
this.element.on("click."+this.eventNamespace, function(){
console.log("click");
})
},
// метод отвечает за применение настроек
_setOption: function( key, value ) {
// применяем изменения настроек
this._super("_setOption", key, value );
},
// метод _destroy должен быть антиподом к _create
// он должен убрать все изменения внесенные изменения в DOM
// и убрать все обработчики, если таковые были
_destroy: function() {
this.element.unbind('.'+this.eventNamespace);
}
});

Поясню для тех кто не прочёл комментарии:

$("#my").expose({key:value})

Наблюдательный глаз заметит, что все перечисленные методы начинаются со знака подчёркивания – это такой способ выделить "приватные" методы, которые недоступны для запуска, и если мы попытаемся запустить "$('#my').expose('_destroy')", то получим ошибку. Но учтите – это лишь договорённость, соблюдайте, её!

Для обхода договорённости о приватности можно использовать метод "data()":

$("#my").data("expose")._destroy() // место для смайла "(evil)"

В данном примере, я постарался задать хороший тон написания виджетов – я "повесил" обработчики событий в namespace, это даст в дальнейшем возможность контролировать происходящее без необходимости залазить в код виджета, это "true story".

Код описанный в методе "_destroy()" – избыточен, т.к. он и так выполняется в публичном "destroy()", приведён тут для наглядности.

А для ленивых, чтобы не прописывать каждый раз "eventNamespace" в обработчиках событий, разработчики добавили в версии 1.9.0 два метода: "_on()" и "_off()", первый принимает два параметра:

Все перечисленные события будут "висеть" в пространстве "eventNamespace", т.е. результат будет предположительно одинаковым:

this._on(this.element, {
mouseover:function(event) {
console.log("Hello mouse");
},
mouseout:function(event) {
console.log("Bye mouse");
}
});

Второй метод – "_off()" – позволяет выборочно отключать обработчики:

this._off(this.element, "mouseout click");

Ну каркас баркасом, пора переходить к функционалу – добавим произвольную функцию с произвольным функционалом:

callMe:function(){
console.log("Allo?");
}

К данной функции мы легко сможем обращаться как из других методов виджета так и извне:

// изнутри
this.callMe()
// извне
$("#my").expose("callMe")

Если ваша функция принимает параметры, то передача оных осуществляется следующим способом:

$("#my").expose("callMe", "Hello!")

Если вы хотите достучаться в обработчике событий до метода виджета, то не забудьте про область видимости переменных, и сделайте следующий манёвр:

_create: function() {
var self = this; // вот он!
this.element.on("click."+this.eventNamespace, function(){
// тут используем self, т.к. this уже указывает на
// элемент по которому кликаем
self.callMe();
})
},

Хорошо идём, теперь поговорим о событиях – для более гибкой разработки и внедрения виджетов предусмотрен функционал по созданию произвольных событий и их "прослушиванию":

// инициируем событие
this._trigger("incomingCall");
// подписываемся на событие при инициализации виджета
$("#my").expose({
incommingCall: function(ev) {
console.log("din-don");
}
})
// или после, используя в качестве имени события
// имя виджета + имя события
$("#my").bind("exposeincomingCall", function(){
console.log("tru-lya-lya")
});

Материала много, я понимаю, но ещё добавлю описание нескольких методов которые можно вызвать из самого виджета:

_delay() – данная функция работает как "setTimeout()", вот только контекст переданной функции будет указывать на сам виджет (это чтобы не заморачиваться с областью видимости)

_hoverable() и _focusable() – данным методам необходимо скармливать элементы для которых необходимо отслеживать события "hover" и "focus", чтобы автоматически добавит к ним классы "ui-state-hover" и "ui-state-focus" при наступлении оных

_hide() и _show() – эти два метода появились в версии 1.9.0, они созданы дабы стандартизировать поведение виджетов при использовании методов анимации, настройки принято прятать в опциях под ключами "hide" и "show" соответственно. Использовать методы следует следующим образом:

options: {
hide: {
effect: "slideDown", // настройки эквиваленты вызову
duration: 500 // .slideDown( 500)
}
}
// внутри виджета следует использовать вызовы _hide() и _show()
this._hide( this.element, this.options.hide, function() {
// это наша функция обратного вызова
console.log('спрятали');
});

Существует ещё пару методов, которые реализованы за нас:

enable: function() {
return this._setOption( "disabled", false );
},
disable: function() {
return this._setOption( "disabled", true );
},

Фактически, данный функции создают синоним для вызова:

$("#my").expose({ "disabled": true }) // или false

Наша задача сводится лишь к отслеживанию данного флага в методе "_setOption()".

Примеру быть, возможно этот виджет и не будет популярен, зато он наглядно демонстрирует как создавать виджеты для jQuery UI.

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример widget'а для jQuery UI</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <style>
        .expose-widget {
            position: absolute;
            z-index: 999;
            background-color: rgba(0, 0, 0, 0.42);
            display: none;
        }
    </style>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/jquery-ui.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
	<script>
        (function( $, undefined ) {
            $.widget( "book.expose", {
                version: "1.0.0",
                // наши настройки
                options: {
                    color: null, // цвет фона
                    speed: 500,  // скорость анимации
                    show: 500,   // настройки для метода _show()
                    hide: {      // настройки для метода _hide()
                        effect: "fade",
                        duration: 500
                    }
                },
                // инициализация
                _create: function() {
                    var self = this;
                    // выбранному элементу добавляем наш класс
                    // и вешаем обработчик события "клик"
                    this.element
                        .addClass( "book-widget" )
                        .on('click.expose-widget.'+this.eventNamespace, function() {
                            // вызываем метод виджета
                            // виджет доступен в переменной self
                            if (self.element.data('expose-it')) {
                                self.unmask();
                            } else {
                                self.mask();
                            }
                            return false;
                        });
                    // нам необходимо инициировать окружение лишь один раз для всех элементов
                    // это наш флаг
                    var totalInited = $.data(document.body, 'expose');
                    if (!totalInited) {
                        // изменяем флаг - у нас уже запущена инициализация
                        $.data(document.body, 'expose', 1);
                        // инициализация верхнего "бокса"
                        // и добавление в DOM
                        var $topBox = $('<div></div>');
                            $topBox.addClass("expose-widget");
                            $topBox.appendTo(document.body);
                        // сохраняем на него ссылку в реестре
                        $.data(document.body, 'topBox',    $topBox);
                        // клонируем верхний бокс
                        // добавляем и сохраняем
                        var $bottomBox = $topBox.clone();
                            $bottomBox.appendTo(document.body);
                        $.data(document.body, 'bottomBox', $bottomBox);
                        // повторяем для левого
                        var $leftBox = $topBox.clone();
                            $leftBox.appendTo(document.body);
                        $.data(document.body, 'leftBox',   $leftBox);
                        // и для правого
                        var $rightBox = $topBox.clone();
                            $rightBox.appendTo(document.body);
                        $.data(document.body, 'rightBox',  $rightBox);
                        // все боксы разом
                        $allBox = $topBox.add($bottomBox).add($leftBox).add($rightBox);
                        $.data(document.body, 'allBox',  $allBox);
                        // вешаем единый обработчик события для всех боксов
                        $(document.body).on('click.expose-widget', '.expose-widget', function() {
                            // вызов метода виджета
                            self.unmask();
                            return false;
                        });
                        // кидаем событие, на него можно подписаться лишь при инициализации виджета
                        // иначе толку не будет - будет поздно
                        this._trigger('build');
                    } else {
                        // отслеживаем кол-во виджетов
                        totalInited++;
                        $.data(document.body, 'expose', totalInited);
                    }
                    // вызываем метод виджета для применения цвета
                    this._setCurrentColor();
                },
                // ломаем всё что строили
                _destroy: function() {
                    // подчищаем наш элемент
                    this.element
                        .removeClass( "book-widget" )
                        // данный метод избыточен, т.к. это делает за нас публичный метод destroy()
                        .off('.'+this.eventNamespace);
                    // декрементим кол-во инициализированных виджетов
                    var totalInited = $.data(document.body, 'expose');
                    totalInited--;
                    $.data(document.body, 'expose', totalInited);
                    // если больше нет виджетов
                    // удаляем вспомогательные боксы
                    if (totalInited==0) {
                        $.data(document.body, 'allBox').remove();
                        $(document.body).off('.expose-widget');
                        this._trigger('destroy');
                    }
                },
                // применяем настройки
                _setOption: function( key, value ) {
                    if (key == 'disabled') {
                        // тут должен быть код по отключению виджета
                    }
                    if (key == 'speed') {
                        this.options.show = value;
                        this.options.hide.duration = value;
                    }
                    this._super( key, value );
                },
                // применяем настройки цвета
                _setCurrentColor: function() {
                    this.options.color = $.data(document.body, 'topBox').css('background-color');
                },
                // изменяем цвет
                color: function(value) {
                    $.data(document.body, 'allBox').css('background-color', value);
                    this._setCurrentColor();
                },
                // прячем боксы
                unmask: function() {
                    var self = this;
                    // получаем набор
                    var $allBox = $.data(document.body, 'allBox');
                    // вызываем функцию анимации
                    this._hide($allBox, this.options.hide, function() {
                        // бросаем событие по завершению
                        self._trigger('unmask')
                    });
                    this._resetFlag();
                    // изменяем флаг
                    this.element.data('expose-it', false);
                },
                // показываем боксы
                mask: function() {
                    var self = this;
                    // берём все "боксы"
                    var $allBox = $.data(document.body, 'allBox');
                    var $topBox = $.data(document.body, 'topBox');
                    var $bottomBox = $.data(document.body, 'bottomBox');
                    var $leftBox = $.data(document.body, 'leftBox');
                    var $rightBox = $.data(document.body, 'rightBox');
                    // вычисляем текущие координаты элемента и размеры окна
                    var offset = this.element.offset();
                    var height = this.element.outerHeight();
                    var width  = this.element.outerWidth();
                    var winWidth = $(document).width();
                    var winHeight = $(document).height();
                    var topSettings = {
                        top:0,
                        left:0,
                        width:winWidth,
                        height:offset.top,
                        backgroundColor:this.options.color
                    };
                    var leftSettings = {
                        top:offset.top,
                        left:0,
                        width:offset.left,
                        height:height,
                        backgroundColor:this.options.color
                    };
                    var rightSettings = {
                        top:offset.top,
                        left:offset.left+width,
                        width:winWidth - (offset.left+width),
                        height:height,
                        backgroundColor:this.options.color
                    };
                    var bottomSettings = {
                        top:offset.top+height,
                        left:0,
                        width:winWidth,
                        height:winHeight - (offset.top+height),
                        backgroundColor:this.options.color
                    };
                    // проверяем - отображается у нас боксы в данный момент времени или нет
                    if ($topBox.is(':hidden')) {
                        // применяем настройки
                        $topBox.css(topSettings);
                        $leftBox.css(leftSettings);
                        $rightBox.css(rightSettings);
                        $bottomBox.css(bottomSettings);
                        // показываем все наши боксы
                        this._show( $allBox, this.options.show, function() {
                            // событие mask
                            self._trigger('mask');
                        });
                    } else {
                        this._resetFlag();
                        // применяем настройки через анимацию
                        $topBox.animate(topSettings, this.options.speed);
                        $leftBox.animate(leftSettings, this.options.speed);
                        $rightBox.animate(rightSettings, this.options.speed);
                        $bottomBox.animate(bottomSettings, this.options.speed, function(){
                            // событие mask
                            self._trigger('mask');
                        });
                    }
                    // изменяем флаг
                    this.element.data('expose-it', true);
                },
                _resetFlag:function() {
                    // workaround - надо "сбросить" флаг для всех других элементов
                    // надо будет придумать более красивое решение
                    $.each($.cache, function(i, el) {
                        if (el.data != undefined && el.data.exposeIt != undefined) {
                            el.data.exposeIt = false;
                        }
                    });
                }
            });
        })( jQuery );
	</script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
			<a href="tabs.html" title="go prev" class="button alignleft" rel="prev">← Prev </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
            <pre><code><em>// run widget and try to click on article</em>
$(<span>'article'</span>).expose()</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code contenteditable="true"><em>// configuration was changed</em>
$(<span>'article'</span>).expose({speed:2000})</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code contenteditable="true"><em>// run widget and try to click on image</em>
$(<span>'article img'</span>).expose({speed:100})</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code><em>// try widget API - trigger</em>
$(<span>'article:eq(0)'</span>).bind(<span>'exposemask'</span>,
    function(){
        $(this).css(<span>'color'</span>, <span>'red'</span>);
    });
$(<span>'article:eq(0)'</span>).bind(<span>'exposeunmask'</span>,
    function(){
        $(this).css(<span>'color'</span>, <span>'black'</span>);
    })</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code><em>// try widget API - call method</em>
$(<span>'article:eq(1)'</span>).expose(<span>'mask'</span>)</code></pre>
            <button type="button" class="code">Run Code</button>
            <pre><code contenteditable="true"><em>// try widget API - call method+args</em>
$(<span>'article:eq(1)'</span>).expose(
    <span>'color'</span>,
    <span>'rgba(255, 0, 0, 0.5)'</span>
)</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>widget</h1>
            <h2>Пишем своё виджет для jQuery UI</h2>
        </header>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200"/>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-chamomile-tumb.jpg" alt="Chamomile" class="left" width="200"/>
            Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-maple-leaf-tumb.jpg" alt="Maple Leaf" class="left" width="200"/>
            Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
            laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
            non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
            ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
            sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
            vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
            magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
            cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
        </script>
	</div>
</body>
</html>

Будьте внимательны, с выходом jQuery UI версии 1.9.0 были внесены правки в Widget API, следовательно, большинство доступной инфор- мации устарело, так что читайте официальную документацию [http://wiki.jqueryui.com/w/page/12138135/Widget%20factory], а ещё лучше – заглядывайте в код готовых виджетов "от производителя"

Информация по теме разработки виджетов:

jQuery Tools

Альтернатива jQuery UI, хотя я бы назвал полезным дополнением. Библиотека jQuery Tools состоит из компонентов, которые разделяются на три типа:

UI Tools

Tabs – аналогично jQuery UI по назначению, скромнее по функционалу, плюс еще есть возможность настраивать как "accordion":



Tooltip – всплывающие подсказки, я не уверен в юзабельности оных, но может для кого-то данный компонент окажется полезным, ведь его так же включили в последний jQuery UI:



Overlay – создание всплывающих модальных окошек, полезный компонент, аля "lightbox":



Scrollable – компонент для создания "карусели" из картинок, может пригодиться в любом онлайн-магазине:



Form Tools

Validator – позволяет на декларативном уровне задавать правила проверки вводимых значений, что бывает очень полезно, и крайне удобно (совместим с HTML5).

RangeInput – аналогичен jQuery UI Slider

DateInput – аналогичен jQuery UI Datepicker

Toolbox

Expose – пожалуй самый замечательный компонент из всего набора – он позволяет выделять элемент на странице, путём затемнение остальных элементов; это трудно описать, лучше посмотрите на демо: http://jquerytools.org/demos/toolbox/expose/index.html

Flashembed – компонент для быстрого и удобного встраивания Flash-объектов на страницу, прямой конкурент SWFObject

Данный набор утилит достаточно долго не обновлялся, с марта 2012-го года, но он ещё не канул в лету, работа идёт, и обещают выпустить вторую версию.

На этом, пожалуй, стоит закончить этот обзор.

jQWidgets

jQWidgets – это не просто библиотека, а целый комбайн с кучей полезных виджетов, да их на данный момент насчитывается тридцать четыре штуки, тут только привести список и скриншот на каждый – уже пол книги будет, так что обойдёмся кратеньким резюме на каждый виджет. Начну обзор с комплексных и "тяжёлых" виджетов, к ним картинки я таки приложу.

jqxGrid – это датагрид с кучей полезных и не очень примочек, тут понятное дело есть постраничная навигация, сортировки, фильтрации и группировки, только по этому виджету уже можно здоровенный талмуд написать:



jqxChart – виджет для построения разнообразных графиков с помощью HTML, CSS и JavaScript, сделано всё очень и очень культурно, особенно выделю функцию по сохранению графика как картинки, иногда очень её не хватает:



jqxGauge – этот виджет не часто встретишь и в даже более именитых фреймворках, но по сути – это некий измеритель, т.е. с его помощью можно нарисовать спидометр, манометр, термометр или ещё какой другой измерительный прибор с произвольной шкалой:



jqxTreeMap – ещё один редкий вид, скорее даже уникальный, с его помощью можно построить связанное дерево в виде организованных прямоугольничков, если ничего не понятно, то лучше просмотреть демку, ну и скриншот прилагаю:



jqxTree – это уже не столь экзотический виджет, как понятно из названия, будем садить деревья:



На этом обзор "крутых" виджетов можно заканчивать, углубляться в скучные и обыденный обёртки над элементами форм мне не хочется, замечу лишь, что во многом данный фреймворк обходит jQuery UI, но не всё так радужно в этом королевстве:

Данный фреймворк распространяется под лицензией Creative Commons Attribution-NonCommercial 3.0 License, которая предусматри- вает бесплатное использование библиотеки для не коммерческих проектов, иначе – смотрите расценки.

Ещё стоит упомянуть одну приятную особенность – это возможность легкой интеграции с MVVM фреймворком Knockout, но это уже другая история

jQuery Mobile

А вот это вполне самостоятельный продукт, и как следует из названия предназначен для создания интерфейсов для мобильных устройств с поддержкой "Touch Screen". Этот фреймворк хорошо документирован, с кучей примеров, которые можно пощупать на http://jquerymobile.com/

Данный фреймворк хорошо подходит для создания мобильных версий сайтов, но при этом он будет выглядеть как мобильное приложение, хотя нет – сайт будет выглядеть как сайт на jQuery Mobile. Насколько это хорошо или плохо мне судить сложно, скажем так – это востребовано.

Я не буду рассматривать все компоненты данного фреймворка, приведу лишь некоторые скриншоты (взяты с официального сайта http://jquerymobile.com/):



Для jQMobile существует свой собственный ThemeRoller – http://jquerymobile.com/themeroller/

И еще, посоветую обратить внимание на API данного фреймворка, без изучения оного вам будет затруднительно создать действительно интересные приложения.

Еще плагины

Хотелось бы порекомендовать еще несколько плагинов, которые стоит всегда держать под рукой:

color — если потребуется анимация цвета фона, либо шрифта, либо еще чего-нибудь (если не хотите использовать jQuery UI) [https://github.com/jquery/jquery-color]

cookie— удобная работа с "печеньками" в браузере [http://archive.plugins.jquery.com/project/Cookie]

easing — расширяем стандартный набор функций easing (об этом я рассказывал в главе анимация) [http://gsgd.co.uk/sandbox/jquery/easing/]

form — упрощает работу с формами, сам уже давно не пользуюсь, но для быстрого старта самое оно[http://malsup.com/jquery/form/]

hotkeys — название говорит само за себя [https://github.com/jeresig/jquery.hotkeys]

mouswheel — добавляет к jQuery возможность отслеживать события колесика мышки (да, да, из коробки этого функционала нет в библиотеке) [http://brandonaaron.net/code/mousewheel/docs]

profiler – как и следует из названия – помогает отлаживать код jQuery приложений [http://archive.plugins.jquery.com/project/profile] [http://ejohn.org/blog/deep-profiling-jquery-apps/]

shadow animation — как следует из названия – плагин для анимации теней [http://www.bitstorm.org/jquery/shadow-animation/]

jQuery Transition Events – поддержка CSS Transition из JS [https://github.com/ai/transition-events]

SWFObject — если потребуется вставить какую-нибудь flash’ку на страницу, данный плагин облегчит вам участь[http://jquery.thewikies.com/swfobject/]

SWFObject — если потребуется вставить какую-нибудь flash’ку на страницу, данный плагин облегчит вам участь[http://jquery.thewikies.com/swfobject/]

Обновление на версию 2.х

Чем же нам грозит новая версия? Ну нам то боятся её не стоит, а вот пользователи старых версий Internet Explorer не смогут больше насладиться удобством столь популярной библиотеки. Да, да, именно так, из второй версии убрали поддержку IE с 6-ой по 8-ую. Благодаря этой "оптимизации" библиотека похудела на 10%.

Если более современные IE работают в режиме совместимости со старыми версиями, то jQuery не будет заморачиваться, и откажется работать. Избежать это поможет мета-тег X-UA-Compatible или заголовок HTTP.

В скором времени "под нож" пойдут и другие устаревающие браузеры, будьте бдительны Android/WebKit 2.x, за вами уже выехали.

Если вас не зацепила эта диета, то можно пойти дальше, и собрать jQuery только из того, что вам действительно необходимо, что для этого потребуется – читайте в подробном пошаговом руководстве, которому и перевод-то не требуется.

Если вас не впечатляет этот список измений, то это лишь благодаря труду разработчиков jQuery, которые постарались обеспечить полную совместимость API веток 1.х и 2.х, а вот это уже действительно впечатляет.

Ну ладно, не всё так гладко с совместимостью, был этот путь тернист, и проблемы с обновлением будут, вот только искать причину нужно будет в списке изменений версии 1.9, именно эта версия стала поворотной в вопросе совместимости веток.

История изменений

1.0.0 alphaПервая публикация "для своих", всего 75 скачиваний, вычитка началась
1.0.0 betaИсправление опечаток и грамматических ошибок. Публикация на Хабре и более 13 000 скачиваний
1.0.0Релиз :) Исправлено множество опечаток и несколько ссылок. Добавлена информация о быстродействии селекторов вида "div#id" – спасибо хабрапользователю skorney за тестирование. Более 2 000 скачиваний
1.0.1Ещё несколько мелких правок. Спасибо читателям за отзывы. 4500+
1.0.2И ещё… Опубликовал в ePub формате, выложил на scribd, получилось более 8 000 скачиваний и более 10 000 прочтений на scribd
1.0.3Обновил книгу вслед за обновлением jQuery, добавил информацию о изменениях в версии 2.х относительно 1.х. Добавил описание виджетов jQWidgets. Удалил описание событий для метода data(). Примеры кода используемые в учебнике теперь доступны на GitHub, так что жду pull-запросов – https://github.com/AntonShevchuk/jquery-for-beginners. Счётчик прочтений на scribd перевалил за 20 тысяч, при этом версию 1.0.2 скачали более 10 тысяч раз…
1.0.4Оптимизация шрифтов под печать формата А5. Ушла в тираж.
1.0.5Ждёмс…

Дополнения


Литература