Archive for May, 2009

Long Running Javascript, take 3

Friday, May 29th, 2009

The problem of long running javascript tasks has been covered by Julien Lecomte, and Ajaxian. However, I wanted a way to classify this functionality using prototype. I found this post by eternal, which is close to what I wanted but still not quite. So I took it and modified it the way I did want. Basically, you include the following code (requires the prototype library to work):

var Task = Class.create({
    timeout: null,
    options: null,
    working: false,
    workStartTime: NaN,

    initialize: function(options) {
        this.options = Object.extend({
            autoStart: true,
            workMaxTime: 100,
            timeout: 0,
            onProgress: null,
            onComplete: null,
            onError: null
        }, options || {});
        if(this.options.autoStart) {
            this.start();
        }
    },
    start: function() {
        if(this.working) return;
        this.working = true;
        this._doWork();
    },
    stop: function() {
        if(!this.working) return;
        clearTimeout(this.timeout);
        this.working = false;
    },
    toggle: function() {
        if(this.working) {
            this.stop();
        } else {
            this.start();
        }
    },
    _checkTime: function() {
        if (new Date().getTime() - this.workStartTime
            > this.options.workMaxTime) {
            return false;
        } else {
            return true;
        }
    },
    _doWork: function() {
        this.workStartTime = new Date().getTime();
        try {
            result = this._work();
            if(result) {
               if(this.options.onProgress) {
                    if (typeof(result) == "number") {
                        if (0 > result) result = 0;
                        else if (100 < result) result = 100;
                    } else {
                        result = NaN;
                    }
                    try{this.options.onProgress(result)}catch(e){};
                }
                this.timeout = setTimeout(this._doWork.bind(this),
                                            this.options.timeout);
            } else {
                this.working = false;
                if(this.options.onComplete) {
                    try{this.options.onComplete()}catch(e){};
                }
            }
        } catch (e) {
            this.working = false;
            if(this.options.onError) {
                try{this.options.onError(e)}catch(e){};
            }
        }
    },
    _work: function() {
        return false;
        //
        // This is the method that you need to override to make your task
        // work properly, and it should look something like the following:
        //
        // while (there is work to do) {
        //     do some work...
        //     if (!this._checkTime()) {
        //         return true or a number > 0 and <= 100 to signify that
        //         more work needs to be done
        //     }
        // }
        // return false;
        //
    }
});

and then you use it like so:

function onProgress(progress) {
    var divProgress = $("divProgress");
    divProgress.innerHTML = "" + progress + "%";
}

function onComplete() {
    $("divComplete").innerHTML = "complete";
}

var MyTask = Class.create(Task, {
    currNumber: 0,
    maxNumber: 50000,

    _work: function() {
        while (this.currNumber <= this.maxNumber) {
            $("divNumber").innerHTML = this.currNumber++;
            if (!this._checkTime())
                return 100.0*this.currNumber/this.maxNumber;
        }
        return false;
    }
});

var myTask = new MyTask(
                           {onProgress:onProgress, onComplete:onComplete}
                        );

An example of this working can be found here.