Заметки с тегом «jquery» (страница 2)

Sizzle задом наперед

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

Хотя в некоторых современных браузерах у DOM-элементов есть специальные методы querySelector и querySelectorAll, которые реализуют этот функционал, тем не менее актуальность этой библиотеки остаётся неизменной из-за поддержки старых браузеров и браузеров, в которых нет эти методов.

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

Хорошим тоном при составлении правила будет первым селектором использовать получение элемента по атрибуту id, так как эта функция работает очень быстро и реализована во всех браузерах. Как вариант, можно задавать контекст поиска, если он известен, что сильно сокращает количество элементов, которые придется перебирать.

$("#data div.frame p span.dashed");

$("#data").find("div.frame p span.dashed");

$("div.frame p span.dashed", $("#data"));

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

$("div.frame p span.dashed");

выполнится быстрее чем

$(".frame p .dashed");

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

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

$("div.frame p span.dashed");

В этом примере сначала будут найдены все элементы span в документе. Затем отфильтрованы элементы, которые содержат класс dashed. После этого для каждого найденного элемента будет предпринята попытка найти у него предка с тегом p, затем предка с тегом div и классом frame.

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

Как и везде, в алгоритме Sizzle есть исключение. Запрос типа $("#data span.dashed") будет обработан особым образом — селекторы в этом случае традиционно применятся слева направо.

Комментарии к заметке: 2

Определение IE6 через CSS селектор

Определение версии браузера через переменную User-Agent не всегда работает правильно из-за того, что это значение легко подменить. Разработчики jQuery вообще решили отказаться от его использования, предложив как альтернативу определение особенностей браузера программно. Различные реализации одних и тех же свойств или отсутствие какой-либо реализации позволяет четко выделить Internet Explorer на фоне остальных. Но вот выделить какую-либо версию уже становится большой проблемой.

Следуя предложенной концепции выявления особенностей браузера, я решил использовать CSS селектор tag[class] для обнаружения IE6. В старших версиях IE, как и в других современных браузерах, этот селектор корректно обрабатывается, а в 6-й версии он игнорируется.

(function ($) {
    var cssCode = 'html.jQueryDetectIE6{background-color:#ffffff;}html.jQueryDetectIE6[class]{background-color:#fefefe;}',
        styleElement, html;
    try {
        html = $("html");
        if (html.length) {
            styleElement = document.createElement("style");
            styleElement.type = "text/css";
            if (styleElement.styleSheet) {
                styleElement.styleSheet.cssText = cssCode;
            } else {
                styleElement.appendChild(document.createTextNode(cssCode));
            }
            $("head").prepend(styleElement);
            html.addClass("jQueryDetectIE6");
            $.support.cssClassSelector = /254|fe/ig.test(html.css("backgroundColor"));
        }
    } catch (e) {
        // nothing here
    } finally {
        // cleanup
        if (html && html.length) {
            html.removeClass("jQueryDetectIE6");
        }
        if (styleElement && styleElement.parentNode) {
            styleElement.parentNode.removeChild(styleElement);
        }
    }
}(jQuery));

Во время своего выполнения функция добавляет необходимый стиль в тег head и проверяет их влияние на элемент. Результат проверки заносится в поле $.support.cssClassSelector.

Флаг хотелось установить еще до полной загрузки документа. По этому требовалось свести к минимуму модификацию DOM-документа. Это удалось решить через проверку цвета у корневого элемента html.

Другая проблема возникла из-за различного поведения браузеров при добавлении стилей через JavaScript. IE и Safari начинают применять эти правила только, если они были добавлены в тег head. Тут нет побочных эффектов, так как этот элемент уже существует на момент выполнения кода. Но сам элемент style нужно сформировать особым образом. В IE есть свойство styleSheet у элемента, которое позволяет работать с правилами. В свою очередь у объекта styleSheet есть поле cssText, которое позволяет получать и устанавливать текстовое представление правил. Хочу отметить, что изменить свойство cssText можно только однажды. Если все же потребуется его поменять, то нужно будет удалить старый элемент style и добавить новый с обновленными значениями.

Оставте свой комментарий

Выбор диапазона дат с помощью jQuery UI Datepicker

Для очередного проекта понадобился виджет для выбора даты. Очевидным, по крайней мере для меня, решением было jQuery UI Datepicker. Но когда позже понадобилось выбирать не одну дату, а диапазон, я попал в тупик. В API не было ни какого упоминания о такой возможности. Поисковики все как один выдавали какие-то «самоделки» далеко не первой свежести.

Копнув глубже исходники Datepicker, я все-таки обнаружил возможность выбора диапазона. Реализуется все логика через событие «onSelect».

У экземпляра Datepicker есть флаг stayOpen. Если его установить внутри события, то календарь не закроется после клика, а занесет выбранную дату в поле rangeStart и позволит выбрать еще дату. Когда будет выбран конец диапазона, нужно сбросить флаг stayOpen и обновить содержимое текстового поля.

Внимание! Плагин актуален только для jQuery UI 1.7.x. Для последних версий нужно использовать другую редакцию этого плагина.

(function ($) {

  $.fn.daterange = function () {
    // опции
    var opts = $.extend({
      "dateFormat": "dd.mm.yy",
      "changeMonth": false,
      "changeYear": false,
      "numberOfMonths": 2,
      "rangeSeparator": "-"
    }, arguments[0] || {}, {
      // обработчики событий datepicker
      // закрытие
      "onClose": function (dateText, inst) {
        if ($.isFunction(opts.callback)) {
          opts.callback.apply(this, arguments);
        }
      },
      // выбор даты
      "onSelect": function (dateText, inst) {
        var textStart;
          if (!inst.rangeStart) {
            inst.stayOpen = true;
          } else {
            inst.stayOpen = false;
            textStart = $.datepicker.formatDate(opts.dateFormat, inst.rangeStart);
            if (textStart !== dateText) {
              $(this).val(textStart + " " +
                opts.rangeSeparator + " " + dateText);
            }
          }
      }
    });

    return this.each(function () {
      var input = $(this);
      if (input.is("input")) {
        input.datepicker(opts);
      }
    });
  };

}(jQuery));

Еще одно событие, которое может пригодиться, это «onClose». Оно будет сгенерировано в момент закрытия календаря. В плагине я просто делегирую все исполнение внешнему обработчику, но на практике мне удобно было добавить туда больше функционала.

Плагин может быть проинициализирован любыми параметрами, которые используются в Datepicker. Так как обработчик события «onClose» переопределяется, то вместо него используется поле callback.

Второе дыхание

Обновлено 2013-06-19

Для последних версий jQuery UI вместо stayOpen нужно использовать флаг inline. Однако это только запретит календарю закрыться. Нужно ещё позаботиться о сохранении первой выбранной даты.

(function ($) {

  $.fn.daterange = function () {
    // опции
    var opts = $.extend({
      "dateFormat": "dd.mm.yy",
      "changeMonth": false,
      "changeYear": false,
      "numberOfMonths": 2,
      "rangeSeparator": "-"
    }, arguments[0] || {}, {
      // обработчики событий datepicker
      // закрытие
      "onClose": function (dateText, inst) {
        if ($.isFunction(opts.callback)) {
          opts.callback.apply(this, arguments);
        }
      },
      // выбор даты
      "onSelect": function (dateText, inst) {
        var textStart;
          if (!inst.rangeStart) {
            inst.inline = true;
            inst.rangeStart = dateText;
          } else {
            inst.inline = false;
            textStart = inst.rangeStart;
            if (textStart !== dateText) {
              $(this).val(textStart + " " +
                opts.rangeSeparator + " " + dateText);
              inst.rangeStart = null;
            }
          }
      }
    });

    return this.each(function () {
      var input = $(this);
      if (input.is("input")) {
        input.datepicker(opts);
      }
    });
  };

}(jQuery));
Комментарии к заметке: 20

Событие окончания загрузки картинки

Чтобы отследить момент загрузки картинки одного обработчика события «load» бывает не достаточно. Если он будет прикреплен к картинке после того, как она уже была загружена, то событие больше не повторится и обработчик не будет вызван.

Поможет решить эту проблему вспомогательный плагин к jQuery.

;(function ($) {
    $.fn.bindImageLoad = function (callback) {
        function isImageLoaded(img) {
            // Во время события load IE и другие браузеры правильно
            // определяют состояние картинки через атрибут complete.
            // Исключение составляют Gecko-based браузеры.
            if (!img.complete) {
                return false;
            }
            // Тем не менее, у них есть два очень полезных свойства: naturalWidth и naturalHeight.
            // Они дают истинный размер изображения. Если какртинка еще не загрузилась,
            // то они должны быть равны нулю.
            if (typeof img.naturalWidth !== "undefined" && img.naturalWidth === 0) {
                return false;
            }
            // Картинка загружена.
            return true;
        }

        return this.each(function () {
            var ele = $(this);
            if (ele.is("img") && $.isFunction(callback)) {
                ele.one("load", callback);
                if (isImageLoaded(this)) {
                    ele.trigger("load");
                }
            }
        });
    };
})(jQuery);

Обновление: Работу плагина можно посмотреть в примере. Там используется немного модифицированная версия, которая формирует специальное событие.

Комментарии к заметке: 31

Очереди в jQuery

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

В jQuery тоже есть механизм, реализующий очередь — это функции queue и dequeue.

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

Рассмотрим пример:

$("#result")
    .queue("test", function () {
        $(this)
            .append("<p>1st function</p>")
            .queue("test", function () {
                $(this)
                    .append("<p>4th function</p>")
                    .dequeue("test");
            })
            .dequeue("test");
    })
    .queue("test", function () {
        $(this)
            .append("<p>2nd function</p>")
            .dequeue("test");
    })
    .queue("test", function () {
        $(this)
            .append("<p>3rd function. " +
                    $(this).queue("test").length +
                    " function(s) in queue</p>")
            .queue("test", function () {
                $(this)
                    .append("<p>5th function</p>")
                    .dequeue("test");
            })
            .dequeue("test");
    })
    .dequeue("test");

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

Результатом работы этого кода будет:

1st function
2nd function
3rd function. 1 function(s) in queue
4th function
5th function

В третьей по счету функции выводится количество оставшихся функций в очереди. На момент исполнения там остается только одна функция (4-ая по счету), но сразу после этого добавляется еще и 5-ая.

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

Комментарии к заметке: 3