Search code examples
javascriptminimizereducing

"Outsource" recurring code into a function with parameters (js)


so i use one js file to load multiple html and js files whenever they are needed. I have a working code for plenty modules. In the example below you can see the first two modules. All of them look exactly the same. Now i want to "outsource" recurring code into a function with parameters so that the code-amount overall gets minimized. Since i have never done something like this before i could need some help (i am learning js at the moment). I would realy appreciate some help.

 //first module
 if (moduleID === "placeone") {
            var isLoaded = 0;
            if (isLoaded) {
                console.log("file already loaded");
                returnValue = new PlaceOneModule(id, moduleInitialData);
            }
            $("#placeone").load("html/modules/PlaceOneModule.html", function (response, status, xhr) {
                console.log("PlaceOneModule.html" + " " + status);
                $.getScript("js/modules/PlaceOneModule.js").done(function () {
                    console.log("PlaceOneModule.js geladen");
                    isLoaded = 1;
                    returnValue = new PlaceOneModule(id, moduleInitialData);
                }).fail(function () {
                    console.log("PlaceOneModule.js nicht geladen");
                });
            });
        }

    //second module
        if (moduleID === "placetwo") {
            var isLoaded = 0;
            if (isLoaded) {
                console.log("file already loaded");
                returnValue = new PlaceTwoModule(id, moduleInitialData);
            }
            $("#placetwo").load("html/modules/PlaceTwoModule.html", function (response, status, xhr) {
                console.log("PlaceTwoModule.html" + " " + status);
                $.getScript("js/modules/PlaceTwoModule.js").done(function () {
                    console.log("PlaceTwoModule.js geladen");
                    isLoaded = 1;
                    returnValue = new PlaceTwoModule(id, moduleInitialData);
                }).fail(function () {
                    console.log("PlaceTwoModule.js nicht geladen");
                });
            });
        }

Solution

  • The question is rather complex to answer, as there are many things to account for.

    var cache = {};
    
    function module(name, target, done) {
        if (!(name in cache)) {
            return $(target).load('html/modules/' + name + '.html', function(response, status, xhr) {
                console.log(name + '.html ' + status);
    
                $.getScript('js/modules/' + name + '.js')
                    .done(function() {
                        console.log(name + '.js geladen');
                        cache[name] = window[name];
    
                        done(null, cache[name]);
                    })
                    .fail(function() {
                        var message = name + '.js nicht geladen';
    
                        cache[name] = function() {
                            console.error(message);
                        };
    
                        done(message);
                    });
            });
        }
    
        setTimeout(function() {
            done(null, cache[name]);
        }, 0);
    }
    

    I'll try to explain my train of thought behind this:

    • var cache = {} - you will need something to keep track of each individual module
    • function module(name, target, done) {
      • name would be the base name of your module, e.g. PlaceTwoModule, this was already used consistently across the html and js files and the js function name
      • target would be the selector where the html file should be loaded
      • as one of the actions you take requires async operation, the entire functionality needs to become async, I introduce a callback (done) argument
    • if (!(name in cache)) - if the module is not yet cached, it requires some fetching, so the load is triggered first thing
    • once the load completes, it will fire the $.getScript
      • if the $.getScript works out, the name will be assumed to be in window and a reference is stored in the cache variable, after that, the done callback is invoked (with the function as second argument).
      • if the $.getScript didn't work out, we add a function to the cache, which does nothing more than telling you it will not work, after that, the done callback is invoked (with an error as first argument).
    • if the name did exist in the cache, we will be calling the done callback right after we exit the module function

    So, how to use this? It now boils down to calling the module function

    module('#placeone', 'PlaceOneModule', function(error, PlaceModule) {
      if (error) throw new Error(error);
    
      var instance = new PlaceModule(id, initial);
    
      // ...
    }
    

    I have used the common function(error, value) {..} signature for the callback function, which always has the error as first argument (allowing for other arguments to be added and made optional).

    There are some caveats/assumptions worth mentioning:

    • I have not provided a failsafe for preventing multiple loads of the same module (so it is the same as in your example) if earlier calls to module are still loading
    • no matter what target you invoke module with, it will only load 'once' (well, see the previous line ;-) )
    • I assume the loaded modules are in the global (window) scope in order to keep the example simple, keep in mind to not 'pollute the global scope'

    This has become a rather elaborate answer, I hope I explained every step involved sufficiently.