Search code examples
javascriptdomdom-eventsreadability

How to remove duplicate JavaScript code of DOM event handler?


I'm trying to remove duplicate JavaScript code. I have a page with many <input type="file">. Each loads an image and performs some distinct processing. The problem is that I have many duplicates of the following code:

inputFile1.onchange = function (e) {
        var file = e.target.files[0];
        if (typeof file == 'undefined' || file == null) {
            return;
        }
        var imageType = /image.*/;
        if (!file.type.match(imageType)) {
            window.alert('Bad file type!');
            return;
        }
        var reader = new FileReader();
        reader.onloadend = function (e) {
            var imageLoader = new Image();
            imageLoader.onload = function () {
                // process image
            };
            imageLoader.src = e.target.result;
        };
        reader.readAsDataURL(file);
    };

inputFile2.onchange = ... (repeats all but process image)
inputFile3.onchange = ... (repeats all but process image)

Only the code at process image comment varies. How can I remove the surrounding duplicate code?

I know that JavaScript functions are objects. How can I define a function object and create one distinct instance for each event handler, passing a different function for process image to each object?


Solution

  • You can make a generator for such functions with a closure taking the individual callback as an argument:

    function getChangeHandler(loadCallback) {
        return function (e) {
            var file = e.target.files[0];
            if (typeof file == 'undefined' || file == null) {
                return;
            }
            var imageType = /image.*/;
            if (!file.type.match(imageType)) {
                window.alert('Bad file type!');
                return;
            }
            var reader = new FileReader();
            reader.onloadend = function (e) {
                var imageLoader = new Image();
                imageLoader.onload = loadCallback; // <= uses the closure argument
                imageLoader.src = e.target.result;
            };
            reader.readAsDataURL(file);
        };
    }
    inputFile1.onchange = getChangeHandler(function() { /* custom process image */ });
    inputFile2.onchange = getChangeHandler(function() { /* custom process image */ });
    inputFile3.onchange = getChangeHandler(function() { /* custom process image */ });
    

    An other, eventually superior approach would be to use only one change-event handler for all inputs, that dynamically chooses the custom image processor by the name or id of the input:

    var imageProcessors = {
        "box1": function() { … },
        "anotherbox": function() { … },
        …
    };
    function changeHandler(e) {
        var input = this; // === e.target
        …
        reader.onloadend = function (e) {
            …
            imageLoader.onload = imageProcessors[input.id];
        };
    }
    // and bind this one function on all inputs (jQuery-style):
    $("#box1, #anotherbox, …").click(changeHandler);