WordPress.org

Make WordPress Core

Opened 22 months ago

Last modified 6 months ago

#18590 new enhancement

Swap out "return false" JS calls for preventDefault

Reported by: Viper007Bond Owned by:
Priority: normal Milestone: Awaiting Review
Component: Administration Version: 3.3
Severity: normal Keywords: needs-testing has-patch
Cc: dkikizas@…, solaris.smoke@…, r@…

Description

While trying to hook into someone clicking the expand/collapse arrow in the nav menu UI, I realized that the existing Javascript is using return false; to abort the hyperlink click from going through.

This is bad and incorrect.

We should be using object.preventDefault() instead so that other bound functions aren't aborted.

Attachments (2)

18590.audit.txt (42.5 KB) - added by robmiller 6 months ago.
Current uses of return false in JS
preventdefault-18590.diff (17.6 KB) - added by robmiller 6 months ago.
Replace obvious candidates for preventDefault

Download all attachments as: .zip

Change History (13)

comment:1 demetris22 months ago

  • Cc dkikizas@… added

comment:2 follow-up: azaozz22 months ago

The linked article explains this well (although in quite a few words): "Only use return false when you want both preventDefault() and stopPropagation()".

We are (should be) using return false to do both which is usually the case. Note that this doesn't stop the 'immediate propagation', i.e. if there are several 'click' handlers on the same element they all fire.

comment:3 in reply to: ↑ 2 ; follow-up: Viper007Bond22 months ago

Replying to azaozz:

We are (should be) using return false to do both which is usually the case. Note that this doesn't stop the 'immediate propagation', i.e. if there are several 'click' handlers on the same element they all fire.

Yes, if my code does $('#foo').click( ... ) it will work, but the return false blocks $('#foo').live('click', ... ) from working (i.e. attaching the click event to subsequently added items).

I ended up having to use $('#foo').live('mouseover', ...) to hack around it.

comment:4 Viper007Bond22 months ago

Here's my code incase things are unclear:

jQuery(document).ready(function($) {
	// ID tracker field
	var IDtracker = $('<input>').attr({
		type: 'hidden',
		id: ADASIParams.idtracker,
		name: ADASIParams.idtracker,
		value: ''
	}).prependTo('#update-nav-menu');

	function adasi_add_checkbox( item ) {
		// Skip items that are known not to be hierarchical post types or that we already modified
		if ( $(item).is('.adasi-checked, .menu-item-custom, .menu-item-category, .menu-item-post_tag') ) {
			return;
		}

		// Don't check this item again
		$(item).addClass('adasi-checked');

		var itemID = parseInt( $(item).attr('id').replace('menu-item-', ''), 10 );

		// Gotta figure it out in PHP, so use an AJAX call
		jQuery.post(
			ajaxurl,
			{
				action: ADASIParams.ajaxaction,
				id: itemID
			},
			function( response ){
				if ( response && response.add ) {
					// Track IDs to check the POST for
					IDtracker.val( IDtracker.val() + ',' + itemID );

					// Add the checkbox
					var checkboxid = ADASIParams.checkboxprefix + itemID;
					$(item).find('.menu-item-actions .link-to-original').after('<p><label><input type="checkbox" id="' + checkboxid + '" name="' + checkboxid + '" value="1" /> ' + ADASIParams.checkboxdesc + '</label></p>');

					if ( response.checked ) {
						$('#' + checkboxid).prop('checked', true);
					}
				}
			},
			'json'
		);
	}

	// Try adding checkboxes to all existing menu items
	$('.menu-item').each(function(){
		adasi_add_checkbox( this );
	});

	// When hovering over a menu item added using Javascript, try adding a checkbox to it.
	// Props DD32 for mouseover hack to get around return false; inside other click bound event.
	$('.menu-item.pending').live('mouseover', function(){
		adasi_add_checkbox( this );
	});
});

comment:6 sanchothefat22 months ago

@Viper have you tried using jQuery's namespaced event bindings?

My approach for hooking onto elements with a click handler attached is to use the syntax described here http://docs.jquery.com/Namespaced_Events

For example you would use:

$( '#foo' ).live( 'click.myplugin', function() { ... } );

Essentially this approach sandboxes your plugin code from the default code. There is a big advantage to this in that it means that people can unbind and rebind specific handlers - useful for plugin/theme authors who need a little extra control or a different behaviour. Think of it as the action/filter API but for jquery events. In some cases I unbind the standard wordpress click handler and add some of my own code before it eg. for pre-processing some data before widgets are saved.

comment:7 solarissmoke22 months ago

  • Cc solaris.smoke@… added

comment:8 in reply to: ↑ 3 nacin19 months ago

Replying to Viper007Bond:

I ended up having to use $('#foo').live('mouseover', ...) to hack around it.

For that matter, we also should kill all uses of .live().

I would not mind an audit of current return false statements in our JS.

robmiller6 months ago

Current uses of return false in JS

comment:9 robmiller6 months ago

Attached is an audit of current uses of return false; it naturally doesn't attempt to make a distinction between logical uses and ones where it's being used to suppress default event handlers. I'm going to nip through and grab the low-hanging fruit (i.e. the most obvious uses where preventDefault is the correct option).

robmiller6 months ago

Replace obvious candidates for preventDefault

comment:10 robmiller6 months ago

  • Cc r@… added
  • Keywords needs-testing added

Aaand here's a patch of the low-hanging fruit. I've not changed anything where there was even ambiguity about whether the return false was necessary, but testing would still be good.

comment:11 robmiller6 months ago

  • Keywords has-patch added; needs-patch removed
Note: See TracTickets for help on using tickets.