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

Обработчики события и функции обратного вызова в 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.

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

Метод, возвращающий данные, в jQuery UI

При написании виджетов для jQuery UI важно не забыть объявить метод, который возвращает какие-то данные (getter). По умолчанию фабрика виджетов вернет ссылку на корневой элемент, чтобы обеспечить цепочечные вызовы.

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


$.widget("foo.bar", {
    …
    getFields: function () {
            var fields = [];
            $.each(this._fields, function () {
                fields.push(this.element);
            });
            return $(fields);
    }
});

Пока мы не сделаем такое объявление, метод будет возвращать не то, что мы ожидаем.


$.extend($.foo.bar, {
    getter: "getFields",
    defaults: {}
});

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

Обновление: В jQuery UI 1.8 изменилась политика в отношении таких методов. Теперь не нужно специально объявлять их. Когда метод возвращает значение undefined, оно заменяется на корневой элемент экземпляра виджета, что позволяет строить вызовы цепочкой. В любом другом случае это значение передается без изменений.

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

Перехват изменения полей checkbox и radio

Для корректной обработки изменения состояния элемента checkbox стоит использовать комбинацию из событий change и click . Это необходимо, чтобы компенсировать ошибку в IE, из-за которой событие change не приходит вовремя. Вместо того чтобы генерировать это событие во время активации поля, IE генерирует его только после смены фокуса. Безусловно, это не приемлемо в случае, когда пользователь «кликает» по нему (фокус остается в поле, но его состояние меняется).

Благодаря системе подписки на несколько событий одновременно в jQuery это очень просто реализовать без лишнего кода:


$("input:checkbox").bind("change click", function () {
    // do something
});

Этот же способ нужно применять, для обработки событий radio кнопок.

Только в этом случае нужно внимательно проверять статус checkbox из-за того, что события будут происходить несколько раз подряд. Другими словами, категорически не стоит использовать toggle или что-либо в этом роде, а реализовать нужную логику в зависимости от состояния checked контролируемого поля.

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

Запомнить порядок сортировки jQuery UI Sortable

В сервисе Google «Вопросы и ответы» одним пользователем был задан этот вопрос и, к моему удивлению, очень долго никто не давал на него ответа. А на самом деле эта задача очень просто решается благодаря встроенному методу serialize.

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

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

var root = $('#items');

Назначаем всем элементам списка уникальные идентификаторы. Это обязательное условие правильной работы метода serialize . Префикс может быть произвольным, но в наших интересах, чтобы он был как можно короче. В качестве разделителя можно использовать символы подчеркивание, равенство или дефис.

$('> *', root).each(function (index) {
    this.id = 'item-' + index;
});

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


root.sortable({
    'update': function (event, ui) {
        var order = $(this).sortable('serialize');
        $.cookies.set('sortable', order);
    }
});

Восстанавливаем порядок элементов списка после загрузки страницы.


var c = $.cookies.get('sortable');
if (c) {
    $.each(c.split('&'), function () {
        var id = this.replace('[]=', '-');
        $('#' + id).appendTo(root);
    });
}

Пробегаемся по всем элементам, сохраненным в cookie , и перемещаем их в конец списка. Если был сохранен порядок для всех элементов, то они естественным образом займут свои места. Если для каких-то элементов позиция не сохранялась, то они «всплывут» наверх списка.

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

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

Презентация картинок в виде слайдшоу

Как сделать красивое оформление для презентации я писал в заметке о Lightbox . А теперь расскажу, как собственно можно сделать саму презентацию.

Поставщиком картинок у нас будет сервис Flickr.


/**
 * Flickr photos provider
 */
(function () {

    var FLICKR_KEY = '4e1a2666b38001e7cb295852430274fe';
    var FLICKR_PREFIX = 'flickr-photo-';

Создаем замыкание, ограничивающее область видимости переменных и объявляем некоторые константы.

Замечу, что FLICKR_KEY содержит уникальный ключ Flickr API, который каждый разработчик должен бесплатно получить для своего приложения.

    APP.Flickr = function () {
        var me = Observable(this);
        me.init();
    };

Конструктор нашего поставщика. Он так же заботится об инициализации сервиса в целом. Для связи сервиса с приложением используется система событий.


    APP.Flickr.prototype = {
        init: function () {
            this.imageCache = document.createElement('div');
            $(this.imageCache)
                .attr('id', FLICKR_PREFIX + 'holder')
                .css({
                    'position': 'absolute',
                    'width': 0,
                    'height': 0,
                    'overflow': 'hidden'
                })
                .appendTo('body');
        },

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


        load: function (tags) {
            var me = this;
            $.getJSON('http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=' + FLICKR_KEY + '&format=json&tags=' + tags + '&content_type=1&jsoncallback=?',
                function(data) {
                    me.photos = data.photos;
                    me.notify('photosDidLoad', me.photos);
                }
            );
        },

Загружаем список картинок с заданными критериями поиска. Для взаимодействия с Flickr используется команда flickr.photos.search и протокол обмена информацией JSON.

Метод генерирует событие photosDidLoad, после удачного получения списка.


        showPhoto: function (index) {
            var me = this;
            var photo = me.photos.photo[index], img;
            var cached = $('#' + FLICKR_PREFIX + photo.id);
            if (cached.length === 0) {
                img = $('<img id="' + FLICKR_PREFIX + photo.id + '" title="' + photo.title + '" class="photo" />')
                        .appendTo(this.imageCache)
                        .load(function () {
                            me.notify('imageReady', img);
                        })
                        .attr('src', 'http://farm' + photo.farm + '.static.flickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '.jpg');
            } else {
                me.notify('imageReady', cached);
            }
        }
    };
})();

Загружаем изображение из списка или берем его из кеша, если оно было уже загружено ранее.

Метод генерирует событие imageReady и передает подписчикам изображение, когда оно будет готово к использованию.

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


$(function () {
    var provider = new APP.Flickr(),
        wait = false;
    provider.attachObserver('photosDidLoad', function (data) {
        var i = 0;
        (function () {
            i = (i >= data.length) ? 0 : i;
            if (!wait) {
                wait = true;
                provider.showPhoto(i++);
            }
            setTimeout(arguments.callee, 2000);
        })();
    });

Когда поставщик сообщит о том, что список загружен, мы запускаем циклический просмотр изображений с интервалом в 2 секунды.

Так как в JavaScript нет паузы как таковой, то применяется такой отложенный асинхронный вызов анонимной функции. Это стало возможным благодаря тому, что внутри любой функции поле callee объекта arguments всегда ссылается на выполняющуюся функцию.


    provider.attachObserver('imageReady', function (img) {
        $('#showroom img.photo')
                .animate({
                    'opacity': 0
                }, function () {
                    $(provider.imageCache).append(this);
                });
        $(img).appendTo('#showroom')
                .css({
                    'opacity': 0
                })
                .centering()
                .animate({
                    'opacity': 1
                }, function () {
                    wait = false;
                });
    });

Как только очередная картинка будет загружена, она центрируется на странице и ее прозрачность плавно меняется с 0 до 1. Тем временем, у текущей картинки меняется прозрачность до 0 и затем она отправляется в кеш.


    provider.load('apple,mac');
});

Загружаем список картинок с тегами «apple» и «mac».

Слайдшоу начнется сразу после загрузки страницы и будет продолжаться до тех пор, пока страница не будет закрыта.

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