Google Groups

closures, what are they good for?


Richard Cornford May 8, 2003 4:10 PM
Posted in group: comp.lang.javascript
Another closure offering! I envision this function as a centralised
scheduling function for DHTML animation effects. A reference to a
function that does the animation (including a private instance method)
is passed to the TimedQue function and is placed in a queue. All of the
functions in the queue are executed at 40 millisecond intervals (or as
closes as practical). The functions are required to provide a return
value of - true - if they want to stay in the queue.

var TimedQue = function(){
    var base, timer;
    var interval = 40;
    var newFncs = null;
    function addFnc(next, f){
        function t(){
            next = next&&next();
            if(f()){
                return arguments.callee;
            }else{
                f = null;
                return next;
            }
        };
        t.addItem = function(d){
            if(next){
                next.addItem(d);
            }else{
                next = d;
            }
            return this;
        };
        t.finalize = function(){
            return ((next)&&(next = next.finalize())||(f = null));
        };
        return t;
    };
    function tmQue(f){
        if(newFncs){
            newFncs = newFncs.addItem(addFnc(null, f));
        }else{
            newFncs = addFnc(null, f);
        }
        if(!timer){
            timer = setTimeout(tmQue.act, interval);
        }
    };
    tmQue.act = function(){
        var f = newFncs, strt = new Date().getTime();
        if(f){
            newFncs = null;
            if(base){
                base.addItem(f);
            }else{
                base = f;
            }
        }
        base = base&&base();
        if(base){
            var t = interval - (new Date().getTime() - strt);
            timer = setTimeout(tmQue.act, ((t > 0)?t:1));
        }else{
            timer = null;
        };
    };
    tmQue.act.toString = function(){
        return 'TimedQue.act()';
    };
    tmQue.finalize = function(){
        timer = clearTimeout(timer);
        base = base&&base.finalize();
        slotIndex = 0;
        newFncs = [];
    };
    return tmQue;
}();

To avoid Steve van Dongen's circular reference problem I have included a
finalize function to free all of the function references. That function
would need to be called onunload, so...

<snip>
>>That doesn't solve the problem of creating additional
>>references, however.
<snip>
>..., maybe a task for a destroy/finalize
>function to free the function references...


What is needed is a general mechanism for adding any finalize methods
(private or public, static or instance) to the window.onunload event.
This closure has a go at that using a very similar queue to the example
above. Called as - finalizeMe(fn); - it will arrange that fn is called
onunload (if the same function is already queued it will not be added).
finalizeMe.remove(fn) - will remove the function from the queue (and is
harmless if the function is not in the queue).

var global = this;
var finalizeMe = function(){
    var base;
    var safe = false;
    var svType = (global.addEventListener && 2)||
                      (global.attachEvent && 1)|| 0;
    function addFnc(next, f){
        function t(ev){
            if(next)next(ev);
            f(ev);
        };
        t.addItem = function(d){
            if(f != d.getFunc()){ //don't add duplicates!
                if(next){
                    next.addItem(d);
                }else{
                    next = d;
                }
            }
            return this;
        };
        t.remove = function(d){
            if(f == d){
                f = null;
                return next;
            }else if(next){
                next = next.remove(d);
            }
            return this;
        };
        t.getFunc = function(){return f;};
        t.finalize = function(){
            if(next)next = next.finalize();
            return (f = null);
        };
        return t;
    };
    function addFunction(f){
        if(base){
            base = base.addItem(addFnc(null, f));
        }else{
            base = addFnc(null, f);
        }
    };
    function ulQue(f){
        addFunction(f);
        if(!safe){
            switch(svType){
                case 1:
                    global.attachEvent("onunload", base);
                    safe = true;
                    break;
                case 2:
                    global.addEventListener("unload", base, false);
                    safe = true;
                    break;
                default:
                    if(global.onunload != base){
                        if(global.onunload)addFunction(global.onunload);
                        global.onunload = base;
                    }
                    break;
            }
        }
    };
    ulQue.remove = function(f){
        if(base)base.remove(f);
    };
    function finalize(){
        if(base){
            base.finalize();
            switch(svType){
                case 1:
                    global.detachEvent("onunload", base);
                    break;
                case 2:
                    global.removeEventListener("unload", base, false);
                    break;
                default:
                    global.onunload = null;
                    break;
            }
            base = null;
        }
        safe = false;
    };
    ulQue(finalize);
    return ulQue;
}();

The only problem that I have seen with this function to date is that on
Mozilla (Gecko) browsers an onunload event specified in the HTML body
tag will not get executed if addEventListener is used to listen to
unload events.

Richard.