Про jQuery и велосипеды — мое дополнение

:

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

Для затравки начнем с простого.

1. Пишите так, как вы говорите это вслух


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

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

$(".info").html("")

потому что раньше использовали удобную фразу .innerHTML = "". Более же предпочтительный вариант:
$(".info").empty()

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

$(".info").html("").html("<b>Ok</b>")

Но не забывайте – вы пишете на jQuery! А он заботится о вас, и .html() сам очистит элемент перед вставкой нового значения.

Причем он сделает это более грамотно, нежели это делать через .innerHTML. Дело в том, что внутри очищаемого элемента могут находиться такие элементы, на которые вы ранее повесили обработчики событий или привязали данные с помощью .data(). jQuery почистит это и никакой утечки памяти не будет.
Кстати, иногда для того, чтобы убрать информацию, целесообразнее не просто очистить элемент, а удалить его вовсе:

$(".info").remove()

Далее…

Вряд ли при написании вот такого:

$("#history").css("display", "none")
$("#description").css("display", "none")
$("#category").css("display", "none")
$("#contact").css("display", "none")

вы проговаривали: «Удалить историю, удалить описание, удалить категории, удалить контакты». Скорее всего, вы сказали: «Удалить историю, описание, категории и контакты». Так напишите же это:

$("#history, #description, #category, #contact").hide()

и не забывайте, что jQuery любит вас, и что для скрытия элемента есть .hide(), а для показа – .show().

2. mouseenter/mouseleave VS mouseover/mouseout


Вы, наверное, замечали неприятность: иногда, когда вы вешаете на элемент пару событий mouseover/mouseout для отображения всплывающей подсказки, происходит мелькание этой подсказки. А происходит это из-за того, что внутри элемента, на который вы навесили обработчики, находится другой элемент. При наведении на него курсора мышки браузер генерирует для вашего внешнего элемента событие mouseout, а для внутреннего – mouseover, после чего опять для внешнего – mouseover, что и приводит к передергиванию.

Но jQuery любит нас и предлагает нам другую пару событий — mouseenter/mouseleave, которые решают эту проблему следующим образом. Для переданных обработчиков делается некая обертка. В момент, когда курсор переходит на внутренний элемент, генерируется событие mouseout для внешнего элемента. При этом jQuery не настолько наивен и не ведется на козни браузера. Скептическая функция-обертка проверяет свойство event.relatedTarget – то, на что навели теперь курсор – и если оно находится внутри внешнего элемента, то не вызывает переданный вами обработчик mouseleave. И никаких мельканий.

В довесок к этому, не забываем про функцию .hover(handlerIn, handlerOut), которая принимает два обработчика и навешивает их как mouseenter и mouseleave:

$(selector).hover(function() {
    tooltip.show()
}, function() {
    tolltip.hide()
})

UPD: тов. Waxer напоминает нам, что начиная с версии 1.8 .hover() считается устаревшей функцией.

3. .parent() VS .closest()


Нередко натыкаюсь вот на такую конструкцию:
var person = $(".name").parent().parent().parent()

Понятно, что здесь представлена попытка добраться до важного родителя, у которого есть важная информация, или в котором находится другой нужный элемент. А что делать, если пока вы были в отпуске, ваш $(".name") приютился в другом месте, но в рамках все того же person? Многие умельцы циклически вызывают .parent() до нужного элемента.

Более прошаренные знают, что есть .parentsUntil(selector), который вернет всех родителей до указанного (исключая его). Но все равно решение громоздко:

var person = $(".name").parentsUntil(".person").last().parent()

Но есть более наглядный способ:

var person = $(".name").closest(".person ")

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

4. $.ajax() – лучше меньше, да лучше!


Все мы любим AJAX, особенно $.ajax(). А насколько сильно любите его вы? В предыдущей статье другого автора приводились полезности работы с этим компонентом. Приведу парочку своих…

Я вряд ли совру, если скажу, что вы часто – если не всегда – обращаетесь по AJAX к одному и тому же файлу, передавая ему для разных действий разные команды. Вы ведь не плодите кучу файлов для коротких действий, надеюсь? Возможно даже, что вы всегда передаете какой-то параметр, который не меняется; и, возможно, вы всегда возвращаете только json. Давайте слегка упростим себе жизнь, ведь jQuery любит нас:

$.ajaxSetup({
    dataType: "json",
    url: "ajax.php",
    data: {
        user_id: userId
    }
});

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

$.ajax({
    data: {
        product_id: productId
    },
    success: function(json) {
        console.log(json)
    },
    alert: "Загрузка товара..." // Для чего? См. ниже
})

и запросы не будут изобиловать повторами кода, которые мы так не любим. Обратите внимание, что параметры data из $.ajaxSetup и $.ajax склеиваются, и серверу посылается их сумма (параметры из $.ajax перезатирают одноименные из $.ajaxSetup).

5. $.ajax() – оповести пользователя


Так… А может теперь оповестим пользователя о своих подпольных делах и будем выдавать ему сообщения при AJAX-запросах? Уверен, вы об этом задумывались, но избегали в силу того, что надо часто писать одно и то же.

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

<div id="popup" class="по-центру-жирным-красным"></div>

И свяжем его с нашими запросами:

$("#popup").ajaxSend(function(event, xhr, options) {
    $(this).text(options.alert || "Подождите...").show()
}).ajaxStop(function() {
    $(this).fadeOut("fast")
})

.ajaxSend() вызывает обработчик каждый раз, когда происходит AJAX-запрос. Здесь мы выводим переданное (см. выше) сообщение или сообщение по умолчанию.

.ajaxStop() вызывается в конце, после того, как все AJAX-запросы отработаны; т.е. если у вас параллельно обрабатывается несколько AJAX-запросов, то обработчик вызовется только один раз – после выполнения последнего запроса.

А если вы хотите выдавать каждый раз новое сообщение в отдельном попапе, то надо совсем немного изменить код:

$("body").ajaxSend(function(event, xhr, options) {
    $('<div class="popup" />')
        .appendTo(this)
        .css({/* координаты */})
        .text(options.alert || "Подождите...")
        .delay(2000) // убрать через 2 сек
        .fadeOut("fast", function() {
            $(this).remove()
        })
})

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

6. $.ajax() – оповести код


Давайте теперь посмотрим на наш попап из примера выше с другой стороны. Допустим, один человек является разработчиком некоторого сложного виджета, в котором отображается информация о текущих событиях на странице. А другой человек разрабатывает саму бизнес-логику и реализует AJAX-запросы. Очевидно, что потребность в виджете могла возникнуть уже после того, как было написано множество AJAX-запросов. Т.е. пока над проектом работает только один человек. Если бы мы любили наш код и своих будущих коллег так же, как любит нас jQuery, то мы бы предугадали потребность другого кода в оповещении его о завершении какого-нибудь нашего AJAX-действия. И сделали бы так:
$.ajax({
    data: {
        action: "search-name",
        name: $("#name").val()
    },
    beforeSend: function() {
        $(searchForm).trigger("start.search")
    },
    complete: function() {
        $(searchForm).trigger("finish.search")
    }
})

Теперь мы можем не беспокоиться, что кто-то в будущем ворвется в наши владения и попортит малину. Нам достаточно сообщить (в комментариях?), что все желающие могут подписаться, если им так будет угодно, на события start.search и finish.search:

$(searchForm)
    .on("start.search", function() {
        // Показать крутящийся прелоадер
    })
    .on("finish.search", function() {
        // Убрать крутящийся прелоадер
    })

Ведь нам дана возможность создавать свои типы событий, генерировать их и подписываться на них. Все просто! :) jQuery любит нас.