Structuring a JavaScript app using a modular approach-Collection of common programming errors

I am trying to write a javaScript application that is of sufficient size that modularity is probably a good idea. I am using the famous inherit function to enable objects to inherit from constructors that have parameters. The problem is I get an error that the parent being passed into the inherit function is undefined. This is because, well, the parent constructor hasn’t been defined when inherit is called.

I have come up with a few ugly solutions to this problem:

  1. Declare all child constructors after their parent (yuk).

  2. Bind all prototype assignments to a custom event and use an ordered callback chain to ensure all the prototypes are assigned in order (perhaps there is potential there).

  3. Move all prototype assignments from the area in the code that their constructor lives and consolidate (and exile) them to some ‘assign prototypes’ function (twice the yuk of item one, no?).

The bottom line is I don’t want to have to base the order in which my constructors, methods, and objects are written in the code on the order in which the code is loaded/interpreted. I want my code to be easily extensible and with related code grouped together so it is easy to understand (i.e., shouldn’t the prototype assignment be near the constructor declaration/definition?).

I am placing modules in separate files and using a PHP script to append them all together before they are sent to the browser. The PHP script only ensures the file that declares the namespace is included first, after that it concatenates the js files with glob.

I wrote a little script that demonstrates exactly the issue I am having. It is is live at this url. The screen is white because there is no html — I am using the console for analysis here.

/*global console, jQuery, $, myNS: true*/
/*jslint browser: true*/

/*
 * This file is deliberately set as the first file in the php glob.
 */
var myNS = (function (myNS, $, window, undefined) {
    "use strict";

    /*
     * @param {object} coords -- e.g. {x: 3, y: 26}
     */
    myNS = function (coords) {
        return new myNS.CartesianLoc(coords);
    };

    // static methods
    myNS.inherit = function (parent) {
        function F() {}
        F.prototype = parent.prototype;
        return new F();
    };
    myNS.getWinCenter = function () {
        return {
            x : $(window).width() / 2,
            y : $(window).height() / 2
        };
    };

    // prototype
    myNS.prototype = {
        log: function () {
            if (window.console) {
                console.log(this);
            }
        }
    };

    return myNS;
}(myNS, jQuery, window));



/*
 * This is another file.
 */
(function (myNS, $, window, undefined) {
    "use strict";
    /*
     *  CartesianLoc constructor
     *
     *  @param {object} coords -- given in a conceptual
     *  Cartesian space where the origin (0,0) is
     *  the middle of whatever screen
     */
    function CartesianLoc(coords) {
        myNS.Loc.call(this, coords);
    }
    CartesianLoc.prototype = myNS.inherit(myNS.Loc);
    CartesianLoc.prototype.constructor = CartesianLoc;

    CartesianLoc.prototype.getWinCoords = function () {
        return {
            x: myNS.getWinCenter().x + this.x,
            y: myNS.getWinCenter().y + this.y
        };
    };

    myNS.CartesianLoc = CartesianLoc;
}(myNS, jQuery, window));



/*
 * This is another file.
 */
(function (myNS, $, window, undefined) {
    "use strict";
    // Location constructor
    function Loc(coords) {
        this.x = coords.x;
        this.y = coords.y;
    }
    Loc.prototype = myNS.inherit(myNS);
    Loc.prototype.constructor = Loc;

    Loc.prototype.translate = function (coords) {
        this.loc.x += coords.x;
        this.loc.y += coords.y;
        return this;
    };

    myNS.Loc = Loc;
}(myNS, jQuery, window));



/*
 * Application js file
 *
 */

(function (myNS, $, window, undefined) {
    "use strict";

    $(document).ready(function (event) {
        if (console) {
            console.log("%o", new myNS({x: 100, y: -45}));
        }
    });

}(myNS, jQuery, window));

Thanks for any help or ideas you can give me!
Chris

  1. I am not sure whether this is a sound practice or messy, but I created a custom event that triggers on document.ready and placed all the code that assigns prototypes in callbacks to that event. I know I am adding an event that fires when another event is fired (which seems pointless at its face), but I want the custom event to be the one that is listened to in case I want change the way it is triggered later.

    /*global console, jQuery, $, myNS: true*/
    /*jslint browser: true*/
    
    /*
     * This file is deliberately set as the first file in the php glob.
     */
    var myNS = (function (myNS, $, window, undefined) {
        "use strict";
    
        /*
         * @param {object} coords -- e.g. {x: 3, y: 26}
         */
        myNS = function (coords) {
            return new myNS.CartesianLoc(coords);
        };
    
        // Triggered after all constructors have been defined
        $(document).ready(function (event) {
            $(document).trigger("myNS");
        });
    
        // static methods
        myNS.inherit = function (parent) {
            function F() {}
            F.prototype = parent.prototype;
            return new F();
        };
        myNS.getWinCenter = function () {
            return {
                x : $(window).width() / 2,
                y : $(window).height() / 2
            };
        };
    
        // prototype
        myNS.prototype = {
            log: function () {
                if (window.console) {
                    console.log(this);
                }
            }
        };
    
        return myNS;
    }(myNS, jQuery, window));
    
    
    
    /*
     * This is another file.
     */
    (function (myNS, $, window, undefined) {
        "use strict";
        /*
         *  CartesianLoc constructor
         *
         *  @param {object} coords -- given in a conceptual
         *  Cartesian space where the origin (0,0) is
         *  the middle of whatever screen
         */
        function CartesianLoc(coords) {
            myNS.Loc.call(this, coords);
        }
        $(document).on('myNS', function (event) {
            CartesianLoc.prototype = myNS.inherit(myNS.Loc);
            CartesianLoc.prototype.constructor = CartesianLoc;
    
            CartesianLoc.prototype.getWinCoords = function () {
                return {
                    x: myNS.getWinCenter().x + this.x,
                    y: myNS.getWinCenter().y + this.y
                };
            };
        });
        myNS.CartesianLoc = CartesianLoc;
    }(myNS, jQuery, window));
    
    
    
    /*
     * This is another file.
     */
    (function (myNS, $, window, undefined) {
        "use strict";
        // Location constructor
        function Loc(coords) {
            this.x = coords.x;
            this.y = coords.y;
        }
        $(document).on('myNS', function (event) {
            Loc.prototype = myNS.inherit(myNS);
            Loc.prototype.constructor = Loc;
    
            Loc.prototype.translate = function (coords) {
                this.loc.x += coords.x;
                this.loc.y += coords.y;
                return this;
            };
        });
        myNS.Loc = Loc;
    }(myNS, jQuery, window));
    
    
    
    /*
     * Application js file
     *
     */
    
    (function (myNS, $, window, undefined) {
        "use strict";
    
        $(document).ready(function (event) {
            if (console) {
                console.log("%o", new myNS({x: 100, y: -45}));
            }
        });
    
    }(myNS, jQuery, window));
    

Originally posted 2013-11-10 00:12:05.