Введение в jQuery

События

Показывать лекцию целиком

Прежде чем приступить к прочтению данной главы, стоит определиться, что же из себя представляют события 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;
});

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

  • первым делом на элемент <body> будет повешен обработчик события "click"
  • данный обработчик будет срабатывать только в том случае, когда событие будет относится к элементу <a>
  • Работа данной схемы базируется на "всплытии" событий, так что используя метод "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).

    Вернуться к учебному плану