Заметки в категории «JavaScript» (страница 14)

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

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

Клонирование элемента select

У элемента select в Internet Explorer есть неприятный побочный эффект. Если нужно клонировать опции одного элемента в другой, то не стоит это делать через свойство innerHTML . Лучше всего клонировать каждый элемент option по отдельности.

$(target).empty().append($("option", source).clone());
Оставте свой комментарий

Метод, возвращающий данные, в 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 контролируемого поля.

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

Часы со стрелками без применения Flash

Для сайта компании «Международный Центр Финансовых Операций » заказчик попросил сделать часы со стрелками, показывающие время в Москве, Нью-Йорке и Токио. Первое, что могло бы прийти в голову – «Flash». А не тут-то было… Часики очень быстро можно сделать и на HTML+CSS+JavaScript.

Самое сложное и долгое это, пожалуй, спрайты стрелок.

Дизайнер приготовил для меня контуры стрелок в Photoshop, а я с помощь экшина создал 24 слоя, на которых стрелки повернуты на 15° относительно предыдущего слоя. Признаюсь, что эти значения были выбраны эмпирически исключительно исходя из здравого смысла. Часы на странице носят скорее декоративный характер, нежели практически и поэтому сомнительно, что кто-то будет за ними специально следить. Технически, можно было бы сделать и все 60 фаз.

Спрайты стрелок часов

Затем я расположил слои один над другим, чтобы изменять изображение исключительно с помощью смещения положения фона у блока.

В стрелки часов я отделил от фона с помощью техники, аналогичной той, что описывал Сергей Чикуенок в статье «Как вырезать картинку из фона ». Использовать PNG-24 с прозрачностью было не приемлемо из-за отсутствия поддержки в IE6, а отрисовка таких мелких деталей без антиалиасинга выглядела просто ужасно.

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


<div id="clocks">
    <div class="city moscow">
        <div class="hour"></div>
        <div class="minute"></div>
    </div>
    <div class="city newyork">
        <div class="hour"></div>
        <div class="minute"></div>
    </div>
    <div class="city tokyo">
        <div class="hour"></div>
        <div class="minute"></div>
    </div>
</div>

#clocks {
    background: url(clocks-template.png) no-repeat left top;
    width: 150px;
    height: 77px;
    position: relative;
}
#clocks .city,
#clocks .hour,
#clocks .minute {
    width: 45px;
    height: 45px;
    position: absolute;
}
#clocks .city {
    overflow: hidden;
    top: 14px;
}
#clocks .hour {
    background: url(clock-hour-sprite.gif) no-repeat 0 0;
}
#clocks .minute {
    background: url(clock-minute-sprite.gif) no-repeat 0 0;
}
#clocks .moscow {
    left: 0;
}
#clocks .newyork {
    left: 53px;
}
#clocks .tokyo {
    left: 105px;
}

(function () {
    var dateObj = new Date();
    var m = dateObj.getMinutes(), h = dateObj.getHours() + (dateObj.getTimezoneOffset() + m) / 60;

Переменные m и h содержат минуты и часы соответственно. Более того, значение часов приведено к GMT и скорректировано в зависимости от количества минут.

Вычисляем номер спрайта для минут и часов. Причем, нужно предусмотреть возможность изменение часового пояса при вычислении.


    var minute = Math.floor((m + 1.25) / 2.5) % 24;

    function getHourForCity(h, offset) {
        return Math.floor((12 + h + offset + 0.25) / 0.5) % 24;
    }

Волшебная функция, которая позволит нам определить используется ли у пользователя «летнее» время в системе или нет.


    function daylightSaving() {
        var now = new Date(),
            nowTZ = now.getTimezoneOffset(),
            winterTZ = new Date(now.getFullYear(), 1, 1).getTimezoneOffset(),
            summerTZ = new Date(now.getFullYear(), 7, 1).getTimezoneOffset();
        return (winterTZ !== summerTZ) && (nowTZ === summerTZ) ? 1 : 0;
    }

    var recovery = daylightSaving();

Устанавливаем позицию фона так, чтобы отобразилась нужная нам стрелка.


    $('#clocks .minute').css('backgroundPosition', 'left -' + 45 * minute + 'px');
    $('#clocks .mosсow .hour').css('backgroundPosition', 'left -' + 45 * getHourForCity(h, 3 + recovery) + 'px');
    $('#clocks .newyork .hour').css('backgroundPosition', 'left -' + 45 * getHourForCity(h, -5 + recovery) + 'px');

В Японии не переводят стрелки на «летнее» время, по этому коррекция часового пояса не требуется.


    $('#clocks .tokyo .hour').css('backgroundPosition', 'left -' + 45 * getHourForCity(h, 9) + 'px');

Обновляем положение стрелок каждую минуту. Мне очень нравится этот шаблон – замыкание + setTimeout, потому что он позволяет выполнить функцию сразу, чего не может обеспечить setInterval.

    setTimeout(arguments.callee, 60000);
})();

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