jQuery: Order of events for different browsers

I’m working on a script that replaces checkbox and radios in a form with images. It works well in Firefox, Chrome, and Safari, but it has a minor issue in IE and Opera. The issue is that focus, blur, keyup, and click events are triggering a function that evaluates the status of the form elements, and the focus needs to be indicated if the site visitor is using a keyboard, but not when the site visitor is clicking with a mouse. When I click with a mouse, the events are in a different order in Opera and IE, and this is why there is a difference. I have to make sure I have focus events because of the keyboard usage, but a click triggers the focus event. So, I’m searching for ideas. Here’s some sample code:

// Checkboxes
if( $('[type="checkbox"]').length ){
	$('[type="checkbox"]').each(function(){
		$(this).parent('label').removeClass('c_on c_on_focus c_off_focus');
		if( $(this).is(':checked') ){
			$(this).parent('label').addClass('c_on');
			var on = true;
		}else{
			var on = false;
		}
		if( $(this).is(':focus') && e.type != 'click' ){
			(on) ? $(this).parent('label').addClass('c_on_focus')
			: $(this).parent('label').addClass('c_off_focus');
		}else if( $(this).is(':focus') && e.type == 'click' && e.clientX == 0 && e.clientY == 0 ){
			(on) ? $(this).parent('label').addClass('c_on_focus')
			: $(this).parent('label').addClass('c_off_focus');
		}
	});
}

OK, so I figured it out. I think it needs some serious refinement, but works in Firefox, Safari, Chrome, Opera, and IE (all latest versions). Instead of changing the order of events, which I think is impossible after searching for many hours, I decided to exclude events based on browser detection:

if( $('[type="checkbox"]').length ){
	$('[type="checkbox"]').each(function(){
		// Remove all classes
		$(this).parent('label').removeClass('c_on c_on_focus c_off_focus');
		// Determine if element is checked
		if( $(this).is(':checked') ){
			$(this).parent('label').addClass('c_on');
			var on = true;
		}else{
			var on = false;
		}
		// If element in focus and not a click event
		if( $(this).is(':focus') && e.type != 'click' ){
			/**
			 * If browser is not msie and a focus event
			 * and not Opera and a focus event
			 */
			if(
				! ( $.browser.msie && e.type == 'focus') &&
				! ( $.browser.opera && e.type == 'focus')
			){
				(on) ? $(this).parent('label').addClass('c_on_focus')
				: $(this).parent('label').addClass('c_off_focus');
			}
		// If element in focus and a click event fired by spacebar
		}else if( $(this).is(':focus') && e.type == 'click' && e.clientX == 0 && e.clientY == 0 ){
			(on) ? $(this).parent('label').addClass('c_on_focus')
			: $(this).parent('label').addClass('c_off_focus');
		// IE can't tell the difference between a spacebar click or mouse click
		}else if( $(this).is(':focus') && e.type == 'click' && $.browser.msie ){
			(on) ? $(this).parent('label').addClass('c_on_focus')
			: $(this).parent('label').addClass('c_off_focus');
		}
	});
}

If anyone has any ideas on how to make this cleaner, more efficient, etc., I’d appreciate the insight.

I wrote a jQuery plugin called Checkable (demo: http://afterlight.com.au/demos/checkable/) that does something very similar to this. Check it out, maybe it solves your problem.

It just wraps the input with another label and sets up some events to see when the checkbox/radio is in focus or changes checked state and passes that along to the label as a class so it can be styled accordingly. It’s pretty much what you’re trying to do, but by wrapping the input with a label the browser is taking care of all the hard work :slight_smile:

The only caveat is that because it wraps the input with a label if you are using implicitly associated labels (i.e. you wrap the checkbox/radio + label text in a label without a for=“” attribute) then you would end up with a nested label, which is invalid markup.(It seems to work everywhere that I’ve tested, but according to standards it’s not allowed.)

AussieJohn, your plugin is very interesting in that it is such a different way to deal with the checkbox and radio elements. Your right in that it does accomplish the same thing as my code, however there is one problem that I saw with your code when testing. If using the tab button to move through the checkboxes, if I use the spacebar to “click” a checkbox, that checkbox loses it’s “focused” css class. If your plugin could retain the focused class then I think it would be perfect.

I do prefer to use implicitly associated labels. Maybe I could tweak your plugin so that it doesn’t wrap the inputs, but instead just adds the classes the existing labels. I’d still need to figure out how to retain the “focused” class when using the spacebar to click. Actually, this is one of the harder parts of working with checkboxes and radios, or at least it was for me.

Thanks for your response. It’s cool to see somebody else’s code, and especially somebody that is a Javascript Guru. It’s very light compared to mine, and covers both checkboxes and radios.

Yeah I actually explicitly unfocus when a checkbox is checked, but I can’t remember if there was a specific reason I did that :stuck_out_tongue:

In either case, I’ve added an option to the plugin that will retain the focus on checkbox elements when they’re checked using the keyboard, it defaults to true so if you get the latest version from GitHub that bit will at least work for you :slight_smile:

Personally I tend to prefer explicitly associated labels as they offer better accessibility and semantics (have a read of http://www.sitepoint.com/forums/showthread.php?813007-Implicit-explicit-or-both-for-form-labels and http://www.paciellogroup.com/blog/2011/07/html5-accessibility-chops-form-control-labeling/).

I might have a look into this again and see what I can come up with for an implicit label scenario.

Yes, it is now staying focused on spacebar clicks, but now stays focused on mouse clicks too, which doesn’t mimic the native browser behavior. The challenge is that while Firefox, Chrome, and Safari give a clear indication of where the click came from (e.clientX and e.clientY values), IE and Opera do not. That’s why I was fooling around with all of the events, and the reason why I started this thread.

While on break, I worked on a plugin for my code. While it’s not as clean and awesome as yours, it’s working exactly the way I want it to.

Example checkbox HTML:


<label class="checkbox"><input type="checkbox" name="checkbox1" value="checkbox 1" /> <span>Regular Checkbox</span></label>

New plugin on Gist Github:
https://gist.github.com/3492800

I don’t have a working example online for you to checkout, but I’ve tested in the latest versions of Chrome, Firefox, Safari, IE, and Opera. Also tested on my Android phone, iPad, and old FF3 on Ubuntu. The only problem out of all of these tests has been that Safari on Mac doesn’t get focus. It’s not that it doesn’t display focus, it’s just not getting it at all.

I’m not sure that I should care so much about the display of focus, but I just wanted things to be the same as the browser’s default functionality. I think your solution would be overall safer, while mine is fully working but has potential to be really buggy because it analyzes the event order, which has proven to be inconsistent. So, in more than one way, you’ve done a good job, and I’ll probably go with your code.

Ah yeah, that is probably why I decided to always trigger a focusout on change so that the experience would at least be consistent regardless of whether you use a mouse or keyboard. I’ll have a think on how best to solve that.

I’ll definitely keep an eye on your project. As a basic javascript/jQuery user, I lack the skills to do more than I’ve done.