Is Simulating User-Input Events With jQuery Ever A Good Idea?

Posted January 30, 2010 at 3:20 PM

Tags: Javascript / DHTML

The other day, I was having a discussion with Ryan Jeffords about programmatically simulating user-based input events with jQuery. Ryan was in the middle of building a very rich, very dynamic user interface for an eCommerce system's price adjuster module when he hit a bit of snag trying to programmatically simulate a user's click event on a checkbox. As I blogged about a while back, when you simulate a click() event on a checkbox, the checkbox's "checked" status does not actually change until after your event handler has executed. This is very different from a user-triggered checkbox click event in which the checkbox's "checked" status changes prior to the event handler execution.

After discussing the possible work-arounds to this problem, I start to think about simulating user-based input events in general. Checkboxs happen to cause a problem because of their state; but, state aside, is simulating user-based input events ever a good idea? Before we get into that discussion, let's take a quick look at what it means to programmatically simulate a user-based input event.

 
 
 
 
 
 
 
 
 
 

In the following demo, I have a paragraph of information that the user can show and hide by clicking on certain links:

 Launch code in new window » Download code as text file »

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Simulating User-Generated Events In jQuery</title>
  • <style type="text/css">
  •  
  • #more-info {
  • background-color: #F0F0F0 ;
  • border: 1px solid #CCCCCC ;
  • display: none ;
  • padding: 15px 15px 15px 15px ;
  • width: 400px ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="../jquery-1.4.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready, initialize scripts.
  • jQuery(function( $ ){
  •  
  • // Get references to our links.
  • var toggleLink = $( "#toggle" );
  • var closeLink = $( "#close" );
  •  
  • // Get a reference to our more-info container.
  • var moreInfo = $( "#more-info" );
  •  
  •  
  • // Bind the toggle link so that it toggles the more
  • // info box's visibility.
  • toggleLink.click(
  • function( event ){
  • // Cancel default event (navigation).
  • event.preventDefault();
  •  
  • // Slide toggle the more info container.
  • moreInfo.slideToggle( 500 );
  • }
  • );
  •  
  • // Bind to the close link. Since this will only be
  • // available when the more-info is open, we can
  • // leverage the toggle link.
  • closeLink.click(
  • function( event ){
  • // Cancel default event (navigation).
  • event.preventDefault();
  •  
  • // Trigger the click event on the toggle link.
  • // This will simulate the user clicking on the
  • // toggle link directly.
  • toggleLink.click();
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Simulating User-Generated Events In jQuery
  • </h1>
  •  
  • <p>
  • <a id="toggle" href="#">Click here</a> for more info.
  • </p>
  •  
  • <p id="more-info">
  • Wow! Did you see Joanna yesterday? Should it even be
  • allowed for someone to be looking that good? It seems
  • rather unfair to the rest of us who are trying to
  • concentrate.... <a id="close" href="#">Close</a>
  • </p>
  •  
  • </body>
  • </html>

As you can see in the above code, the paragraph of information starts off closed and the page has a primary "Toggle" link. If the user clicks this toggle link, it will show the paragraph if it is hidden and hide it if it is visible. Once the paragraph is made visible, the user has the option to click another "Close" link contained within the paragraph. This close link simply turns around and programmatically triggers a click() event on the Toggle link. Since the close link is only available when the paragraph is open, triggering a click() event on the toggle link will successfully close the paragraph.

At first glance, this looks really awesome; we're centralizing are client-side business logic in a single method (the toggle link event handler) and then we're invoking that business logic in several different places. It appears that we're keeping our code DRY (Don't Repeat Yourself) and making good use of code reuse. But, is that really what we're doing? Let's take a step back and think about "intent."

Every time a user interacts with the browser, the user is acting on some internal intent; they click a button or a link because they believe that the button or link click will precipitate some desired response. In that case, it is the browser's responsibility to translate the user's intent into some action. With intent in mind, let's think about the first demo - when the user clicked the "Close" button, did they intend for the paragraph to "toggle", or to "close?"

Clearly, because the "close" link is labelled "close", the intent of the user was to close the paragraph; it was not to "toggle" the paragraph as that would imply some possibility that the close button would ever do anything other than close the paragraph. That said, does it make sense that our Close link simulates a click() event on the Toggle link? It does by coincidence, but certainly not by intent.

So the real question becomes, do you want your code to work by coincidence? Or by intentful design?

The more I think about it, the more it becomes clear that the role of the event handler is simply to translate the intent of the user-based input event into calls made against the core API of the current page or piece of software.

 
 
 
 
 
 
jQuery Event Handlers Should Only Act As Translators Between The User's Intent And The Underlying API. 
 
 
 

This has more of a benefit that pure personal satisfaction; when you code in this manner, your software becomes less susceptible to bugs. Taking the first demo again, imagine that this page had been live for a while and then needed to be updated. A programmer comes along and sees the "Click here for more info" link and decides that that link shouldn't toggle the paragraph, it should only open it since the language on the page makes no mention of "toggle". As such, he changes the toggle link event handler to simply open the paragraph. At this point, our page breaks because the Close link piggybacked by the toggling nature of the, now-altered "click here" link.

If the event handlers' only action had been to translate the intent of the user into API-based actions, then altering the Toggle link would not have caused a problem.

 
 
 
 
 
 
 
 
 
 

With this new outlook in mind, let's factor the "intent" out of the event handlers and leave the event handlers as mere translators of said intent.

 Launch code in new window » Download code as text file »

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Simulating User-Generated Events In jQuery</title>
  • <style type="text/css">
  •  
  • #more-info {
  • background-color: #F0F0F0 ;
  • border: 1px solid #CCCCCC ;
  • display: none ;
  • padding: 15px 15px 15px 15px ;
  • width: 400px ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="../jquery-1.4.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready, initialize scripts.
  • jQuery(function( $ ){
  •  
  • // Get references to our links.
  • var toggleLink = $( "#toggle" );
  • var closeLink = $( "#close" );
  •  
  • // Get a reference to our more-info container.
  • var moreInfo = $( "#more-info" );
  •  
  •  
  • // ---------------------------------------------- //
  • // ---------------------------------------------- //
  •  
  • // These functions will act as our very simple
  • // page API.
  •  
  •  
  • // I open the more info container.
  • var openMoreInfo = function(){
  • moreInfo.slideDown( 500 );
  • };
  •  
  • // I close the more info container.
  • var closeMoreInfo = function(){
  • moreInfo.slideUp( 150 );
  • };
  •  
  •  
  • // ---------------------------------------------- //
  • // ---------------------------------------------- //
  •  
  •  
  • // Bind the toggle link so that it toggles the more
  • // info box's visibility.
  • toggleLink.click(
  • function( event ){
  • // Cancel default event (navigation).
  • event.preventDefault();
  •  
  • // Check to see if the container is currently
  • // open. If it is, close container, otherwise
  • // open it.
  • if (moreInfo.is( ":visible" )){
  •  
  • // Close it.
  • closeMoreInfo();
  •  
  • } else {
  •  
  • // Open it.
  • openMoreInfo();
  •  
  • }
  • }
  • );
  •  
  • // Bind to the close link.
  • closeLink.click(
  • function( event ){
  • // Cancel default event (navigation).
  • event.preventDefault();
  •  
  • // Close the more info container.
  • closeMoreInfo();
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Simulating User-Generated Events In jQuery (v2)
  • </h1>
  •  
  • <p>
  • <a id="toggle" href="#">Click here</a> for more info.
  • </p>
  •  
  • <p id="more-info">
  • Wow! Did you see Joanna yesterday? Should it even be
  • allowed for someone to be looking that good? It seems
  • rather unfair to the rest of us who are trying to
  • concentrate.... <a id="close" href="#">Close</a>
  • </p>
  •  
  • </body>
  • </html>

In the above code, you can see that we took the slide open and close actions and factored them out into API methods, openMoreInfo() and closeMoreInfo(). Our event handlers now act as links between the user's input event and these new API methods. It's a little more code, but in doing this, I believe that our event handlers become both more closely aligned with the user's intent as well as more resistant to changes in the event handler layer.

Simulating user-based input events is an attractive idea in our software because it works; but, when it is done to make something work merely by coincidence, as in our first demo, I believe that this feature is being abused and can quickly come back to bite us (as in the checkbox-click-simulation problem). This makes me question - is there ever a good time to simulate user-based input events (outside of truly simulating a user for testing purposes)? Right now, I can't think of one.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Print Page





Reader Comments

Jim
Jan 30, 2010 at 4:01 PM // reply »
4 Comments

I find myself doing it a lot, but it's because I am working in an old system which needs some refactoring to its core. I completely agree that designing an API to avoid simulating user input or triggering the inout events is absolutely ideal.


Jan 30, 2010 at 6:31 PM // reply »
3 Comments

I agree; simulating user input to change the 'core' is not optimal. In MVC parlance, this would be a dependency of the model on the view. You should be able to invoke model behavior without use of the view. The view should observe the model's state-change and adjust itself accordingly.


Feb 1, 2010 at 4:38 AM // reply »
1 Comments

I am very thankful that you are explaining everything in a video in your recent jQuery posts since im not good in jQuery so far but this helps me a lot. Definately better than just reading something.


Feb 1, 2010 at 8:03 AM // reply »
7,572 Comments

@L,

Glad you are liking the videos. I do my best to try and explain in the text everything that I talk about in the video, so people who care to skip it can; but, I think it really adds a nice supplement to the explanation.


Feb 1, 2010 at 10:04 AM // reply »
4 Comments

I agree that you probably don't want to simulate a click event.

I think the intent idea you describe might be an overstatement, though. If the link text changes from "close" to "toggle" do you have to rethink your API because the user is now thinking about toggling and not closing?

This seems like it's a good use of delegates. You don't have to simulate a click event because you can just call the same event handler from both places.

Here's a small refactor.

// Create a toggle delegate.
var toggleMoreInfo = function(event){
event.preventDefault();
if (moreInfo.is( ":visible" )){
// Close it.
moreInfo.close();
} else {
// Open it.
moreInfo.open();
}
};

// Pass the delegate to any links you want to have that functionality.
toggleLink.click(toggleMoreInfo);
closeLink.click(toggleMoreInfo);

Now, I'm not sure I would want the 'event.preventDefault()' in a generic toggle event, but that's the general idea.


Feb 1, 2010 at 10:15 AM // reply »
8 Comments

@Ben, Is there a way to subscribe to one of your posts (comments) without commenting on it?


Feb 1, 2010 at 6:31 PM // reply »
7,572 Comments

@Keith,

I don't think you have to change the core API if you change the link verbiage; but, I think you *might* have to change the contents of the event handlers.

I am not sure I like the event object being referenced in the delegate. To me, the delegate should probably be ignorant of the concept of events. You might want to create a toggleMoreInfo() method in the API, and then have the delete pass off to that (after canceling the default event). At that point, however, I might only use a delegate *if* two events cause the same outcome explicitly.

@David,

Not currently, sorry.


Feb 2, 2010 at 11:37 AM // reply »
4 Comments

I agree that I don't especially like referencing the preventDefault() method of the event object in the delegate. That's because it changes expected behavior of a link and it's not obvious from the toggle command that it's doing that.

It's clearer if you write "preventDefault()" everywhere that you're preventing the default. It's still code repetition, though. If you had enough links which did this then it might be obvious that you meant to preventDefault every time so you'd expect to put it in the delegate.

In principle there's nothing wrong with using the event object in a delegate. Delegates by definition have the same signature so there's the expectation to use them.

The part I really don't like about this code is that you've hard coded the moreInfo target into the function. It would be better if it was in the event object.

In the example below I've created an ICloseable Interface for any object. It will let you open, close, or toggle any item on a click.

You add the ICloseable interface to the object you want to open and close and then you bind any links you want to work to that object.

The key lines are:

$.extend(moreInfo,ICloseable);

// Bind any links
moreInfo.bind('open',toggleLink);
moreInfo.bind('toggle',closeLink);

Here's how I'd probably code this:
// Get references to our links.
var toggleLink = $( "#toggle" );
var closeLink = $( "#close" );

var moreInfo = $( "#more-info" );

// I open the more info container.
var openInfo = function(event){
obj = event.data.toggleItem;
obj.slideDown( 500 );

};

// I close the more info container.
var closeInfo = function(event){
obj = event.data.toggleItem;
obj.slideUp( 150 );
};

// I close the more info container.
var toggleInfo = function(event){
obj = event.data.toggleItem;
if (obj.is( ":visible" )){
// Close it.
obj.close(event);
} else {
// Open it.
obj.open(event);
}
};

var ICloseable = {
"open" : openInfo,
"close" : closeInfo,
"toggle" : toggleInfo,
"bind" : function(action,obj) {
obj.bind('click',{"toggleItem" : this},this[action]);
}
}

// Attach the ICloseable Interface
$.extend(moreInfo,ICloseable);

// bind any links to the object.
moreInfo.bind('toggle',toggleLink);
moreInfo.bind('toggle',closeLink);

As you can see, I use the event object in the delegates.

I took out the preventDefault() behavior, but if you wanted to add it, you could create a bool variable in the bind event which toggled that behavior pretty easily.


Feb 2, 2010 at 11:49 AM // reply »
7,572 Comments

@Keith,

That's definitely an interesting approach; but there's something about it that just doesn't sit right with me. You're creating a sort of abstract, target-agnostic open/close mechanism; but, the jQuery wrapper is sort of all ready doing that as well. So, you end up having a general method calling another general method. Seems like doubling up on layers of abstraction, and for what purpose, I am not sure?

Personally, I like the explicit execution of item open/closing. I find that it makes the code much more readable.

I feel like in order to maintain flexability, your approach would require a lot of overloading of the event object itself. For example, let's stay that I have multiple elements that I want to open / close, but they don't necessarily have the same open / close durations. How might you account for that? I assume you would make the "duration" a property of the event data?

At that point, though, it almost feels like the open/close methods aren't doing much more than applying the event... and at that point, I think you'll gain readability by having in-line event bindings that make calls on the API.

Of course, this is just my opinion. Whatever works better for you is the way to go.


Feb 2, 2010 at 12:22 PM // reply »
1 Comments

those "Head First" cloned schemes are not so useful, they have somewhat of "deja-vu" appeal...


Feb 2, 2010 at 12:22 PM // reply »
4 Comments

"For example, let's stay that I have multiple elements that I want to open / close, but they don't necessarily have the same open / close durations."

I wouldn't overload the event object here. I would probably add the durations to the ICloseable interface. The general idea would be that objects know how to close themselves and you want your links to just call a close method on the object.

The only think I would attach to the event object is the target.

As for readability, the bind event for the interface system is easier to read than the click event in your example, but the rest of it is more complicated.

So, if you have lots of bind/click events then the Interface code would get more and more readable than the other way, I imagine.

It's also easier to pass off to other developers since all they need to know is the bind signature.

Finally, and here's where I use this sort of thing, I run loops where I bind a whole mess of objects. Since they all share an interface I can do something like

foreach (var obj in objList)
{
obj.bind();
}

It doesn't matter what the bind event does for these objects as long as they share the interface.
I admit, it does get pretty complicated in Javascript, though.


Feb 2, 2010 at 12:41 PM // reply »
4 Comments

@rhett,

Point taken. At the very least I've properly de-railed this thread from Ben's very valid point.


Feb 2, 2010 at 6:52 PM // reply »
7,572 Comments

@Keith,

You make a convincing argument. I think I'm just having trouble visualizing all of the references. I think this is something that I actually need to try out myself before I'll truly understand how it's working.


Feb 10, 2010 at 2:24 AM // reply »
4 Comments

"Ben - A Good Teacher"

Really I am seeing good teacher by watching at least one video per day.
(Hey! don't make this as commercial online tutorial)

Demos are awesome with crystal clear explanation.
Observing your efforts in making the demos interesting with the beautiful images and even in variable names :)

And all your works are forcing/keeping target me to do lot of work on coldfusion n JQuery.

Thanks alot Ben!

With Best Regards,
Raghuram Reddy
http://raghuramcoldfusion.blogspot.com/


Feb 10, 2010 at 8:05 AM // reply »
7,572 Comments

@Raghuram,

I'm glad you like what I'm doing! Thanks :)


Post Comment  |  Ask Ben

Recent Blog Comments
Mar 19, 2010 at 7:26 PM
MySQL 3/4 - com.mysql.jdbc.Driver And allowMultiQueries=true
Thank you very much for this post. Adding allowMultiQueries="true" in context.xml didn't help until I added it to url as allowMultiQueries=true Good idea is to use prepared statements and it will he ... read »
Jim
Mar 19, 2010 at 4:49 PM
Nobody Puts Baby In The Corner!
Wow. This is like suddenly finding a support group for your secret shame. I'm not alone! I always liked this movie, even though it is extremely cheesy. I just wish Jennifer Grey hadn't gotten the ... read »
Mar 19, 2010 at 4:47 PM
Application.cfc OnRequest() Method Affects OnError() Arguments
@Jason and @Ben, I've been doing some CF9 refactoring on our systems and noticed an odd occurrence with onError as well. Found a way to work around my problem, but what I saw was... Background: Our ... read »
Jim
Mar 19, 2010 at 4:44 PM
Shoot 'Em Up Starring Clive Owen And Paul Giamatti
I actually enjoyed this movie quite a lot. It was different, certainly, but I think they were going for more of a Quentin Tarentino-"wow, that was weird"-vibe than an actual spoof. Once I realize ... read »
Mar 19, 2010 at 4:34 PM
An Intensive Exploration Of jQuery With Ben Nadel (Video Presentation)
Hey I guess the video is down. Is there anyway you can upload to youtube or vimeo or some other service? Greatly appreciated. ... read »
Mar 19, 2010 at 4:24 PM
ColdFusion CFPOP - My First Look
@Ben Thanks for the follow up! The root of the problem had to do with being able to trace bounced emails to specific records in a DB table. Let's say you run an email campaign and you get 1,000 bou ... read »
Mar 19, 2010 at 4:15 PM
SQL COUNT( NULLIF( .. ) ) Is Totally Awesome
Thank you Ben and Tony! Either of these work for the summary report I am working on and the info is much appreciated! I think I like Tony's a little better because I won't have to educate every ... read »
Mar 19, 2010 at 3:35 PM
ColdFusion Path Usage And Manipulation Overview
@Ben, Sorry. Clarification. expandpath worked for me in application.cfc, but not in other templates. ... read »