Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Jonathan Hau and David Bainbridge and Scott Markovits
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Jonathan Hau David Bainbridge Scott Markovits

Using State Change To Bind And Unbind Event Handlers vs. Handler Branching

By
Published in Comments (9)

Recently, I have been working on an old project that has some fairly complex JavaScript interactions. One thing that I am noticing is that my event handlers are getting very complex. When a user clicks on certain elements, the event handler that listens for that "click" event may perform up to 5 or 6 logic checks before deciding how to react to said click event. This is a pain and is becoming quite a problem in terms of maintenance. If I could go back and write this all over again, I would probably take more of a "finite state machine" approach that would completely change the handlers as the state changed. Such an approach would probably leave my event handlers much more simple and easy to maintain.

To compare and contrast the two approaches (in the lightest way possible), I thought I would demonstrate the use of a button click handler to show an alert() box. In the following demo, I have a button alongside two radio buttons. The radio buttons indicate "mood". The click event handler uses the mood to determine which alert() box to show:

<!DOCTYPE html>
<html>
<head>
	<title>Bind / UnBind Event Handlers vs. Decision Logic</title>

	<!-- Linked scripts. -->
	<script type="text/javascript" src="./jquery-1.6.1.js"></script>
</head>
<body>

	<h1>
		Bind / UnBind Event Handlers vs. Decision Logic
	</h1>

	<form>

		<p>
			<button type="button">
				Click Me!
			</button>
		</p>

		<p>
			Mood:

			<label>
				<input type="radio" name="mood" value="goofy" />
				Goofy
			</label>

			&nbsp;

			<label>
				<input type="radio" name="mood" value="grumpy" />
				Grumpy
			</label>
		</p>

	</form>


	<script type="text/javascript">


		// Get a reference to the releveant dom elements.
		var dom = {};
		dom.button = $( "button" );
		dom.goofyMood = $( "input[ value = 'goofy' ]" );
		dom.grumpyMood = $( "input[ value = 'grumpy' ]" );


		// Preselect one of the moods.
		dom.goofyMood.attr( "checked", "checked" );


		// Bind the click handler. In this version, we're just
		// going to put decision logic in the handler to dicate
		// the behavior based on the current selection of mood.
		dom.button.click(
			function( event ){

				// Check to see which mood is selected in order
				// to figure out how to respond to this violation
				// of my personal space!
				if (dom.goofyMood.is( ":checked" )){

					alert( "Ha ha, that tickles!" );

				} else {

					alert( "Take your dang hands off of me!" );

				}

			}
		);


	</script>

</body>
</html>

As you can see, the click() event handler looks at the state of the "mood" checkboxes and then uses that information to display the alert() box. This branching logic is encapsulated entirely within the event handler. In this particular demo, this approach appears to be non-problematic. The logic is so simple and easy to understand that branching does not pose any practical concern.

Unfortunately, the project that I am working on has gotten quite a bit out of control. Instead of simple checkbox logic, I have event handlers that perform multiple, complex checks in order to decide what type of behavior to exhibit. And, I have many of these kind of event handlers. And, they all have to be updated when new business logic gets added to the page.

Without showing you actual project code (which I can't do), it's hard to even paint the right picture. Just believe me when I say it has become a bit of a nightmare.

Ideally, what I'd want is for the event handlers to be much more focused and independent, as they probably should be. What I'd love is for an event handler to believe that it is executing in a "static" context. Meaning, that if the event handler is being invoked, it is doing so only to carry out a small, focused task.

In order to move in this direction, we have to factor the branching logic out of the event handlers. And, in order to do that, we have to create different event handlers for the different behavior. Then, in order to make sure that different event handlers respond only when appropriate, we'll have to actually bind and unbind event handlers as the state of the page changes.

To explore this approach, I've refactored the above code to use state objects. Each of these state objects has a setup() and teardown() method that binds and unbinds event handlers, respectively. This helps to ensure that a particular set of event handlers are only being used in a specific circumstance. These "states" are then manipulated using some additional controller logic attached to the radio buttons.

<!DOCTYPE html>
<html>
<head>
	<title>Bind / UnBind Event Handlers vs. Decision Logic</title>

	<!-- Linked scripts. -->
	<script type="text/javascript" src="./jquery-1.6.1.js"></script>
</head>
<body>

	<h1>
		Bind / UnBind Event Handlers vs. Decision Logic
	</h1>

	<form>

		<p>
			<button type="button">
				Click Me!
			</button>
		</p>

		<p>
			Mood:

			<label>
				<input type="radio" name="mood" value="goofy" />
				Goofy
			</label>

			&nbsp;

			<label>
				<input type="radio" name="mood" value="grumpy" />
				Grumpy
			</label>
		</p>

	</form>


	<script type="text/javascript">


		// Get a reference to the releveant dom elements.
		var dom = {};
		dom.button = $( "button" );
		dom.goofyMood = $( "input[ value = 'goofy' ]" );
		dom.grumpyMood = $( "input[ value = 'grumpy' ]" );


		// This time, we're gonna use a sort of "state machine" (used
		// in the losest possible sense of the word) in order to set
		// up the way the click handling works.
		var states = {};


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// Set up the goofy state configuration.
		states.goofy = {

			// Initialize the goofy state.
			setup: function(){

				// Bind the click handler.
				dom.button.click( this.clickHandler );

			},


			// Tear down the goofy state.
			teardown: function(){

				// Unbind the click handler.
				dom.button.unbind( "click", this.clickHandler );

			},


			// Define the click handler.
			clickHandler: function( event ){

				alert( "Ha ha, that tickles!" );

			}

		};


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// Set up the grumpy state configuration.
		states.grumpy = {

			// Initialize the grumpy state.
			setup: function(){

				// Bind the click handler.
				dom.button.click( this.clickHandler );

			},


			// Tear down the grumpy state.
			teardown: function(){

				// Unbind the click handler.
				dom.button.unbind( "click", this.clickHandler );

			},


			// Define the click handler.
			clickHandler: function( event ){

				alert( "Take your dang hands off of me!" );

			}

		};


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// Preselect one of the moods.
		dom.goofyMood.attr( "checked", "checked" );

		// Set up the goofy mood state.
		states.goofy.setup();


		// Bind the click handlers on the mood selection in order to
		// setup / teardown the appropriate states.
		dom.goofyMood.click(
			function( event ){

				// Switch to the goofy mood state.
				states.grumpy.teardown();
				states.goofy.setup();

			}
		);

		dom.grumpyMood.click(
			function( event ){

				// Switch to the grumpy mood state.
				states.goofy.teardown();
				states.grumpy.setup();

			}
		);


	</script>

</body>
</html>

As you can see, each state object - goofy and grump - has its own setup() and teardown() method. Furthermore, each state has its own version of the click event handler. While the code, as a whole, appears more verbose, notice that these event handlers have become greatly simplified. Now, rather than having decision logic directly in the event handlers, we are essentially turning different states on and off as the checkbox configuration changes.

By thinking in terms of "state," our event handlers can become more focused. They don't respond to events in a generic manner; rather, they respond to very specific events for very specific reasons - reasons dictated by the state of the page.

I don't pretend to really know what I'm talking about. The only thing that I do know at this point is that 1) my current event handlers are bloated, complex, and hard to maintain and 2) the more modular and cohesive you can make your code, the better you will be in the long run. I don't even know how to go about refactoring some of the code in this particular project (the coupling is far too intense); right now, I'm just trying to figure out how I want to approach the next rich-client application that I build.

Want to use code from this post? Check out the license.

Reader Comments

1 Comments

I feel your pain Ben and I like your approach but I would definitely recommend you have a look at backbonejs for heavy JS apps. Actually backbone views do basically what you are attempting to do in your post, with a little extra sugar added :-)

15,878 Comments

@Dave,

Yeah, I've read up a bit about Backbone and Spine, etc., but haven't had a chance to dive in just yet. I started to mock something up for testing, but then got side tracked.

Part of my talking about LAB.js (previous few posts) was in preparation of trying to build some stuff that has more JS files associated with it.

Hopefully, this will all lead somewhere useful :)

290 Comments

@Ben,

I wholeheartedly agree that it's worthwhile to decouple message from delivery, even if the mode of delivery is goofy/grumpy. Here's my favorite example, which I've probably bored you with already:

If an error occurs on an individual form element, then I want the change handler to pop up an alert with just that message. The user may choose to ignore the alert and continue editing other form elements. So later, in the submit handler, I want to redo all of the same edits, not stop if an error occurs, and produce an alert with all errors found on the page, preferably as a nicely formatted bulleted list.

In this situation, the state is silent mode, in which you just push the error message onto a string array, versus yakity mode, in which you alert it right away. That way, the form's submit handler can simply call all of its elements' change handlers in silent mode.

<form ... onsubmit="
window.silentMode = true;
window.errorMsgs  = [];
this.FirstName.onchange();
this.LastName.onchange();
this.PhoneNumber.onchange();
...
window.silentMode = false;
if (errorMsgs.length == 0)
return true;
// Format bulleted string and alert it.
return false;
">
...
</form>

The onchange handlers are left as an exercise for your readers. :-)

It's very old school OOP to code a form that tells its elements to validate themselves. And yet minty fresh too.

3 Comments

I think what you call a state-machine is actually Aspect-Oriented-Programming. Your example focuses on the power of before and after advice (or, your setup and teardown methods). From what I"ve read - and believe me, I'm no scholar - Finite-State-Machines (fsm's) have related states, wherein one systems observes and manages changes. Your example does this manually - which makes it appear to be an AOP approach. (Mind you, I wouldn't be surprised if fsm's were AOP on a grander scale.)

The most accurate fsm I've seen is javascript-state-machine, at https://github.com/jakesgordon/javascript-state-machine. I believe that library even supports nondeterministic finite state-machines. (All a tad over my head.)

Less academic and more practical fsm's are hash-routing libraries like SugarSkull, at https://github.com/hij1nx/SugarSkull. The hash-router from your single-page-app presentation is of the same ilk. Both have predefined routes (or states), and the system can not "be" anywhere else.

Lastly, there is my own library Flow, at https://github.com/bemson/Flow. I resist calling it an fsm, since I focus on it's declarative AOP syntax... but my better-educated friends have convinced me otherwise. (It's in rewrite now, but I'd love your thoughts.)

That said, I think the conversation of managing state is critical to code maintenance and reducing complexity. The user interface is where business-rules, interactive logic, and technical policies converge. Because we know these factors will change over time, managing their interconnections is all the more important.

I call it the mess (modes, exceptions, states, and steps) hidden in our code. The only way to combat this intricacy is to use an architecture which accounts for each moving part. I agree that fsm's are the best approach.

There may be no "silver bullet", but revealing complexity has proven my best weapon against monster projects. [Nod to Fred Brooks.] I apologize for dragging on, as I haven't blogged for some time! Your focus on this topic is exciting, and I certainly appreciate your addressing it here.

3 Comments

Another approach to maintaining form state, is using class-names - I posted a fiddle based on your example here:

http://jsfiddle.net/Ps5ng/

This relies on the DOM for state, and jQuery live event handlers for the binding.

There are two things I like about this approach:

1. You can easily include/exclude or combine multiple states in other ways, by writing appropriate selectors for the live() event-binders.

2. Forms frequently have areas that show or hide depending on form state - you can now manage the visibility (or color, size, etc.) of various elements by writing CSS selectors that show or hide elements depending on the form states.

Another approach, is to not respond conditionally to events, at all - instead, create a button for each specific action, and use form states and CSS to control the visibility of the buttons.

http://jsfiddle.net/3pnZb/1/

In this example, this has the benefit of being able to hide the button when none of the radio buttons are selected. You also have the freedom to use different labels on those buttons - I personally like to see buttons change to reflect the action that will be performed, if some other condition on the form is going to change the outcome of submitting the form.

I personally think it's cleaner to have only one event-handler on the same button instance, and not have to try to change that handler - it seems like a somewhat error-prone approach. Every time you add a new handler/state, you'll have to run around and make sure that this new handler gets removed everywhere else.

So this approach probably scales a little better - you add another state, it's not going to affect existing functionality or require you to change anything anywhere else.

All just ideas - I don't claim to be a genius either, just thought I'd share my own approach and add it to the discussion :-)

3 Comments

@Rasmus,

Should a function fire just because an element is accessible? This, instead of checking whether the function can execute (via an AOP or state-machine approach)? Which sounds more foolproof?

Including CSS as a part of your controller logic (i.e., what the user can and can not do) is asking for trouble. A sustainable architecture for maintaining state, should isolate such logic in JavaScript alone. Your controllers could then manipulate other technologies as needed (like the DOM and CSS) to reflect it's capabilities.

Separating style from functionality is not a new concept, but many limit this practice to the files in an application. I heartily recommend applying this principle to the architecture of your application as well.

3 Comments

@bemson,

But your functions also fire when the element is accessible - which is all the time. You're just switching out the event handlers - you're not performing any checking to see if the function should execute.

In fact, as stated, if you were to add a third function, you would need to make sure you remove the third handler when the other two handlers are applied. You're basically repeating the logic that determines if a function is attached or not, in every one of those handlers, and they will grow in complexity with every added function, and the size of the codebase (and odds of you making a mistake) grows exponentially.

At some point, you're going to miss the removal of one of those functions, and clicking the button is going to cause two functions to fire in succession - that does not seem foolproof to me at all.

If you had a use-case where you actually wanted two functions to fire, (say, if you had two checkboxes instead of a set of radio buttons), maybe I could see this being useful - but in this example, you have one button performing two different functions, which doesn't seem like a clean approach.

It's a matter of perspective of course :-)

I perceive JS/CSS as being merely the technology that drives the view. There is no "controller" on the client-side, the "controller" is on the server-side. There is no business logic on the client-side, just some view-logic that drives visibility, perhaps validates some input ahead of submission, but merely for convenience - the business logic happens on the server-side.

I understand you're trying to find something that will scale up to support much more complex client-side interaction, but I don't see how this approach achieves that - it looks to me like this approach only leads to exponential growth in complexity.

Adding and removing handlers as a means of maintaining "state" seems like a frail approach to me - for example, you cannot (easily) inspect your form and discover what state it's currently in.

And I realize you're trying to avoid having to perform state checks in the first place, but have you really achieved that, or has your state "setup" code just moved into those radio-button handlers?

3 Comments

@Rasmus,

If you're managing interactions with JavaScript, JavaScript is the controller. If you're updating the DOM... JavaScript is the view. Model? Yep, that too. You can't separate what's already tied together.

Client-side state is and must be managed by JavaScript. Even if your strategy is reflect a server's last known state (or "pseudo state" - see http://blog.new-bamboo.co.uk/2010/2/4/let-them-eat-state )

Semantics aside (i.e., what you call a "view" vs. what I call a "controller"), here's how you AOP'ize an interface with jQuery-aop, at http://code.google.com/p/jquery-aop/. (I don't use this tool, but it demonstrates AOP well.)

var submitted = 0;
 
function submitForm() {
$('myForm').submit();
}
 
function submitCheck(invocation) {
if (!submitted) {
	invocation.proceed();
}
}
 
function submitStateChange() {
submitted = 1;
}
 
function submitUpdateGui() {
$('myFormButton').attr('disabled','disabled');
}
 
$.aop.after(
{target: window, method: 'submitForm'},
submitStateChange
);
 
$.aop.after(
{target: window, method: 'submitForm'},
submitUpdateGui
);
 
$.aop.around(
{target: window, method: 'submitForm'},
submitCheck
);
 
// rig events to call `submitForm`
$('myFormButton').click(submitForm);
$('myFormField').keyup(function (evt) {
if (evt.keyCode === 13) {
	submitForm();
}
});

This example separates the (interactive and design) logic involved with submitting a form, from the functionality needed to do the submission. Thus, no matter when "submitForm" is called, it's logic will now be enforced and it's functionality will always be protected. This is what I meant by distinguishing logic in order to deal with changes to logic.

Again, I believe FSM's are the best tool for this job, but few are easy to use. A good explanation of clientside FSM usage (mind you, not implementation) of is at http://blog.metawrap.com/2008/07/21/javascript-finite-state-machinetheorem-prover/.

There is always a state, and it must always be managed.

3 Comments

@bemson,

"There is always a state, and it must always be managed" - of course, I just happen to find that the DOM, since it's there anyway, might as well be in charge of managing the state of the view, to the extend that this is possible or practical.

You cite a lot of theory, but you haven't really addressed any of the practical problems I pointed out. Maybe I'm missing something - your approach may actually work, but your example so far has me convinced of the opposite. Maybe it's going to take more than two radio buttons to convince me that this approach is worthwhile ;-)

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel