While working on Hootroot and Careplane, I found myself getting frustrated with the way I was having handling events. Over time, however, I stopped fighting the language and learned a pattern that I believe is easiest to test and read.

Because of the way this works in JavaScript, I had to assign the this that referred to the current instance of Rocket to a temporary variable that is referenced in the event handlers. This seemed kludgy to me, and I soon discovered the $.proxy() method that jQuery (and other frameworks similarly) provide:

The problem now is there are all sorts of functions hanging around within Rocket#launch() that are a bit difficult to test in a straightforward manner. Solution: create some functions on Rocket that act as event handlers.

// Rocket.jsRocket=function(){this.location='Cape Canaveral, FL';};Rocket.prototype.igniteWhenReady=function(data){if(this.isReady()){alert('Launching from '+this.location);this.ignite(data.launchCode);}else{alert('Not ready to launch!');}};Rocket.prototype.invalidLaunchCode=function(){alert('Failed to get launch code for '+this.location);this.scrub();};Rocket.prototype.launch=function(){$.ajax('/launch_code',{data:{location:this.location},success:$.proxy(this.igniteWhenReady,this),error:$.proxy(this.invalidLaunchCode,this)});};//RocketSpec.jsdescribe('Rocket',function(){varrocket;beforeEach(function(){rocket=newRocket('Cape Canaveral, FL');spyOn(rocket,'ignite');spyOn(rocket,'scrub');});describe('#launch',function(){// we don't need to test launch() because we'd really just be testing $.ajax});describe('#igniteWhenReady',function(){it('ignites if ready',function(){rocket.isReady=function(){returntrue;};rocket.igniteWhenReady({launchCode:12345});expect(rocket.ignite).toHaveBeenCalled();});it('does not ignite if not ready',function(){rocket.isReady=function(){returnfalse;};rocket.igniteWhenReady();expect(rocket.ignite).not.toHaveBeenCalled();});});describe('#invalidLaunchCode',function(){it('scrubs if a bad launch code is given',function(){rocket.invalidLaunchCode();expect(rocket.scrub).toHaveBeenCalled();});});});

This is much cleaner and easier to test, but those lingering $.proxy() calls were bugging me. They also made debugging a bit more tedious when having to step through the calls to $.proxy.

My solution: stop fighting with this and create my own event proxy pattern. Testing is now much cleaner.

//Rocket.jsRocket=function(location){this.location=location;};Rocket.prototype.ignite=function(){/* ... */};Rocket.prototype.scrub=function(){/* ... */};Rocket.events={igniteWhenReady:function(rocket){returnfunction(data)if(rocket.isReady()){alert('Launching from '+rocket.location);rocket.ignite(data.launchCode);}else{alert('Not ready to launch!');}};},invalidLaunchCode:function(rocket){returnfunction(){alert('Failed to get launch code for '+rocket.location);rocket.scrub();};}};Rocket.prototype.launch=function(){$.ajax('/launch_code',{data:{location:this.location},success:Rocket.events.igniteWhenReady(this),error:Rocket.events.invalidLaunchCode(this)});};//RocketSpec.jsdescribe('Rocket',function(){varrocket,igniteWhenReady,invalidLaunchCode;beforeEach(function(){rocket=newRocket('Cape Canaveral, FL');spyOn(rocket,'ignite');spyOn(rocket,'scrub');igniteWhenReady=Rocket.events.igniteWhenReady(rocket);invalidLaunchCode=Rocket.events.invalidLaunchCode(rocket);});describe('.events',function(){describe('.igniteWhenReady',function(){it('ignites if ready',function(){rocket.isReady=function(){returntrue;};igniteWhenReady({launchCode:12345});expect(rocket.ignite).toHaveBeenCalled();});it('does not ignite if not ready',function(){rocket.isReady=function(){returnfalse;};igniteWhenReady();expect(rocket.ignite).not.toHaveBeenCalled();});});describe('.invalidLaunchCode',function(){it('scrubs if a bad launch code is given',function(){invalidLaunchCode();expect(rocket.scrub).toHaveBeenCalled();});});});});

The result is much more readable code, easier debugging (when absolutely necessary), and simpler testing without all those nested closures and AJAX stubs. As an added bonus, you get to keep the this in your event handlers that refers to the event itself.

This experience has led me to believe that a lot of the problems CoffeeScript tries to solve (like function binding) can really just be solved using good, simple JavaScript coding practices. I’m happy to hear from anyone who has a better pattern or has had similar experiences.

Brighter Planet helps clients identify opportunities for data-driven sustainability initiatives, designs programs around these opportunities to deliver business value, and assists with integration and development