отложенные вызов и отмена функции

интерфейс, который моментально реагирует на действия пользователя — это не всегда здорово. Людям свойственно делать мелкие ошибки и тут же исправлять их, поэтому нередко хорошей идеей оказывается снисходительность к таким ошибкам. В своём докладе на ClientSide'07 я рассказывал об этом подробнее (см. последнюю четверть текста), но формат устного выступления не предполагает внимательное изучение кода. Сейчас я хочу полнее рассмотреть одну из функций, позволяющих добиться отложенной реакции интерфейса

задача стоит следующим образом: спустя определенное время после возникновения какого-то события нужно выполнить определенный код, если с тех пор не случилось другое событие, отменяющее эту команду. Например, если пользователь быстро провел мышью над пунктом меню, событие mouseover должно показать подменю, но событие mouseout отменяет этот вызов. Разумно решать эту задачу симметричным образом, чтобы повторный вызов «включения» в свою очередь отменял предыдущее «выключение»

я реализовал эту функциональность методом функции, подобно .delay() или .curry(). Метод получает необязательным параметром объект, в котором передаются опциональные же значения задержек «включения/выключения». А для максимальной гибкости в использовании метод возвращает целых четыре значения в другом объекте. Два из них — это таймауты, возвращаемые глобальной функцией setTimeout, а два других — это зеркальные функции, отменяющие друг друга. Впрочем, код лучше объяснит себя сам! Вначале объявление метода:

Function.prototype.getDelayedHandlers = function(delay)

инициализация значений задержек:

delay = delay || {};
var overDelay = ('over' in delay) ? delay.over : 0.4;
var outDelay =  ('out'  in delay) ? delay.out  : 0.2;

очевидно, что over — задержка для «включения», а out — задержка «выключения». Теперь нам понадобится пара локальных переменных для сохранения в текущем замыкании:

var data = {timeouts: {}, handlers: {}}; 
var __function = this;

а вот и собственно зеркальные функции:

data.handlers.over = function() { 
    if (data.timeouts.out) 
        clearTimeout(data.timeouts.out);
    data.timeouts.over = 
        __function.curry(true, data).delay(overDelay);
};
data.handlers.out = function() { 
    if (data.timeouts.over) 
        clearTimeout(data.timeouts.over);
    data.timeouts.out = 
        __function.curry(false, data).delay(outDelay);
};
return data;

мы видим, что каждая из них предотвращает вызов другой, после чего планирует выполнение исходной функции через соответствующий интервал времени. Важно заметить, что исходная функция получает два параметра: флаг, означающий тип вызова — «включение» или «выключение», а также объект data, содержащий всю необходимую информацию о состоянии отложенных вызовов. Этот же объект потом возвращается из getDelayedHandlers

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

Function.prototype.getDelayedHandlers = function(delay) {
    delay = delay || {};
    var overDelay = ('over' in delay) ? delay.over : 0.4;
    var outDelay =  ('out'  in delay) ? delay.out  : 0.2;

    var data = {timeouts: {}, handlers: {}}; 
    var __function = this;

    data.handlers.over = function() { 
        if (data.timeouts.out) 
            clearTimeout(data.timeouts.out);
        data.timeouts.over = 
            __function.curry(true, data).delay(overDelay);
    };
    data.handlers.out = function() { 
        if (data.timeouts.over) 
            clearTimeout(data.timeouts.over);
        data.timeouts.out = 
            __function.curry(false, data).delay(outDelay);
    };

    return data;
}

Артемий Трегубенко,
, , ,

comments powered by Disqus