Обработчики события и функции обратного вызова в jQuery UI

Интересной особенностью jQuery UI для меня стала обработка событий и запуск функций обратного вызова. У обычных событий цепочки функций-обработчиков выполняются от начала и до конца или пока не будут прерваны через метод stopImmediatePropagation. К сожалению, в отличие от YUI 3, в API jQuery не предоставлена возможность определить, когда закончится выполняться эта цепочка. Зато в jQuery UI виджеты генерируют события, в которых такая возможность есть.

При конфигурировании виджета можно задать функцию обратного вызова (callback) для любого события, а так же подписаться на это событие извне. По сути, callback и цепочка обработчиков вызываются одним и тем же приватным методом _trigger. Но callback всегда вызывается последним вне зависимости от результата работы цепочки функций. А так как им обоим передается ссылка на одно и тот же событие, то в callback можно проверить статус события после обработчиков через методы isDefaultPrevented, isPropagationStopped или isImmediatePropagationStopped.

Более того, метод _trigger возвращает значение true, если все функции были выполнены и ни одна из них не вызвала preventDefault. В противном случае он вернет значение false. Это свойство фабрики виджетов тоже можно использовать для управления логикой работы событий.

Простой пример виджета:

$.widget("my.input", {
    _init: function () {
        var me = this;
        // подписываемся на некоторые события элемента input,
        // чтобы отслеживать его изменение
        me.element.find(".input").bind('keyup change blur', function (event) {
            var value = $(this).val(), opts = me.options, data;
            // исколючаем повторяющие события
            if (!opts.inProgress && value != opts.value) {
                try {
                    opts.inProgress = true;
                    data = {'current': value, 'last': opts.value};
                    me._change(data, me._trigger("change", event, data));
                } finally {
                    opts.inProgress = false;
                }
            }
        });
    },

    _change: function (data, status) {
        $(this.element).toggleClass("error", !status);
        this.options.value = data.current;
    }
});

$.extend($.my.input, {
    defaults: {
        value: "",
        inProgress: false
    }
});

$(function () {
    $("#demo")
            .input({'change': function (e, data) {
                // функция обратного вызова
                // будет выполнена самой последней при обработке события
                return data.current <= 1000;
            }})
            .bind('inputchange', function (e, data) {
                // специальный обработчик, который прерывает цепочку событий,
                // если встречается определенная строка — 007
                if (data.current == "007") {
                    e.stopImmediatePropagation();
                    return false;
                }
            })
            .bind('inputchange', function (e, data) {
                // еще один специальный обработчик
                $(".log", this).prepend("<p>" + data.current + "</p>");
            });
});

Этот виджет отслеживает вводимые в поле данные, ведет лог этих данных и осуществляет валидацию. В примере я использую метод stopImmediatePropagation, чтобы прервать выполнение цепочки. Если это специально не требуется, то лучше использовать preventDefault или возвращать false в обработчике.

Итак, если в поле ввести «007», то в лог ничего не запишется, а виджету будет назначен класс «error». Так же этот класс будет назначен, если значение в поле — число, больше 1000.