Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the right way to wire together 2 javascript objects?

I'm currently facing a conundrum: What is the right way to wire together 2 javascript objects?

Imagine an application like a text editor with several different files. I have some HTML page that represents the view for the notebook. I have a file notebook.js that contains class definitions for NotebookController and Notebook View.

NotebookControler object responsible for performing business logic on the Notebook like "Save Notebook," "Load Notebook," "New Notebook." NotebookView is responsible for managing the HTML that is used for presentation. It does low level stuff like "get/set notebook body" "get/set notebook name." It also listens for DOM events (onClick) and fires business events (saveNotebook). This is my attempt at the Passive View pattern.

I want my javascript client-side code to be object-oriented, separated concerns, and unit-testable. I want to test NotebookController with a mock NotebookView and vice versa. This means that I can't just instantiate a NotebookView inside the NotebookController. So do I

  • Put some logic in my notebook.js that wires the 2 together
  • Have a global function in my application that knows to instantiate one of each and wire them together
  • Use Dependency Injection, either a home-grown one or something like SquirrelIoc

In Java, the choice is a natural one: use Spring. But that doesn't seem very JavaScript-y. What's the right thing to do?

like image 807
Jonathan Hess Avatar asked Mar 06 '09 17:03

Jonathan Hess


2 Answers

Dependency injection is probably your best bet. Compared to Java, some aspects of this are easier to do in JS code, since you can pass an object full of callbacks into your NotebookController. Other aspects are harder, because you don't have the static code analysis to formalize the interface between them.

like image 109
Andrey Fedorov Avatar answered Oct 04 '22 20:10

Andrey Fedorov


Thanks for the insight. I ended up writing a simple JavaScript dependency injection utility. After debating for a while and your comments, it occured to me that DI was really the right answer because:

  1. It totally separated the concerns of wiring from the business logic while keeping the wiring logic close to the things being wired.
  2. It allowed me to generically provide a "you're all wired up" callback on my objects so that I could do a 3 phase initialization: instantiate everything, wire it all up, call everyone's callbacks and tell them they're wired.
  3. It was easy to check for dependency missing problems.

So here's the DI utility:

var Dependency = function(_name, _instance, _dependencyMap) {
    this.name = _name;
    this.instance = _instance;
    this.dependencyMap = _dependencyMap;
}

Dependency.prototype.toString = function() {
    return this.name;
}

CONCORD.dependencyinjection = {};

CONCORD.dependencyinjection.Context = function() {
    this.registry = {};
}

CONCORD.dependencyinjection.Context.prototype = {
    register : function(name, instance, dependencyMap) {
        this.registry[name] = new Dependency(name, instance, dependencyMap);
    }, 
    get : function(name) {
        var dependency = this.registry[name];
        return dependency != null ? dependency.instance : null;
    },

    init : function() {
        YAHOO.log("Initializing Dependency Injection","info","CONCORD.dependencyinjection.Context");
        var registryKey;
        var dependencyKey;
        var dependency;
        var afterDependenciesSet = [];
        for (registryKey in this.registry) {
            dependency = this.registry[registryKey];
            YAHOO.log("Initializing " + dependency.name,"debug","CONCORD.dependencyinjection.Context");

            for(dependencyKey in dependency.dependencyMap) {
                var name = dependency.dependencyMap[dependencyKey];
                var instance = this.get(name);
                if(instance == null) {
                    throw "Unsatisfied Dependency: "+dependency+"."+dependencyKey+" could not find instance for "+name;
                }
                dependency.instance[dependencyKey] = instance; 
            }

            if(typeof dependency.instance['afterDependenciesSet'] != 'undefined') {
                afterDependenciesSet.push(dependency);
            }
        }

        var i;
        for(i = 0; i < afterDependenciesSet.length; i++) {
            afterDependenciesSet[i].instance.afterDependenciesSet();
        }
    }

}
like image 36
Jonathan Hess Avatar answered Oct 04 '22 21:10

Jonathan Hess