7
\$\begingroup\$

I'm binding a handler to the keyup event of all input and textareas in a document:

$(document).on('keyup','input,textarea',$.debounce(600, editor.handleGlobalChange));

I don't want the handler to fire on a specific input field. So I want to unbind the keyup event:

$('#afield').off('keyup');

However this does not unbind the keyup event, the handler is still called. I assume this is because there is no keyup event bound to the $('#afield') element, the event is just bubbling up to the $(document) where the actual handler is bound.

So to stop it triggering I can do the following:

$('#afield').on('keyup',function(e) {
    e.stopImmediatePropagation();
});

This works, but it feels grim, setting a handler to stop another.

Is this acceptable or should I just no bind the event in the first place?

I could ignore certain classes or a custom data-nokeyup attribute:

$(document).on('keyup','input,textarea,not([data-nokeyup]',$.debounce(600, editor.handleGlobalChange));

I have to use a delegated event handler because my inputs and textareas are created after the initialisation code is run. $('input,textarea').keyup(...) would return no elements. $(document).on('keyup','input,textarea',...) catches all keyup events and inspects the event's target element.

\$\endgroup\$
  • 1
    \$\begingroup\$ Note that for the data-nokeyup solution the selector string should be input:not([data-keyup]), textarea:not([data-keyup]) \$\endgroup\$ – Flambino Oct 13 '12 at 5:33
  • 1
    \$\begingroup\$ Gah, I just realized I also wrote it wrong. It should really be input:not([data-nokeyup]), textarea:not([data-nokeyup]) :) \$\endgroup\$ – Flambino Oct 14 '12 at 11:51
4
\$\begingroup\$

Well, as far as I can tell, there are 3 ways to deal with this; you've mentioned 2 of them already:

  1. Pre-empt the event for a specific element by adding another handler,
  2. Scope the event handler differently to exclude a specific element, or
  3. Do the filtering in the common handler

(Edit: All of this is assuming you really want to keep the document-level, event bubble handler, rather than go with mseancole's straightforward solution)

The 3rd option would look something like:

$(document).on('keyup','input,textarea', function (event) {
  if( event.type === 'sometype' && this === someElement ) {
    return;
  }
  $.debounce(600, editor.handleGlobalChange).call(this, event);
});

... which isn't pretty either. Of course you could make it fancier by maintaining a list of event type/element combos to be ignored, rather than hardcoding it, but... eh, seems like a lot of work.

I'd say go with option 1, but perhaps wrap that bit of logic in a function or plugin. I.e. something that would let you say:

$(document).on('keyup','input,textarea',$.debounce(600, editor.handleGlobalChange));
$('#afield').ignore('keyup');

.ignore() would then set the event handler that kills the event in its tracks. In your case, you can use stopPropagation instead of stopImmediatePropagation. That way, you can still attach other handlers to that specific element, without ignore eating the event.

Edit: Now that I think about it, ignore is a bad name. Too strong a word. dontBubble is accurate, but I'm a stickler for using proper apostrophes, and doNotBubble sounds too aggressive. Maybe popBubble? Too cutesy? Maybe just something like halt? debubble? Yessss, I'm thinking way too much about this :)

Here's a demo. It's in CoffeeScript, because... well, because I like CoffeeScript. The general idea should be clear enough though. Here's the code in case jsfiddle implodes

makeNoise = ->
  $('pre').append "You typed!<br>"

# 4-line jQuery plugin
$.fn.ignore = (eventType) ->
  $(this).each ->
    $(this).on eventType, (event) ->
      event.stopPropagation();

$ ->
  # General event handler for all inputs
  $(document).on 'keyup', 'input', makeNoise

  # Exempt a specific element
  $('#quiet').ignore 'keyup'

  # Handlers added *directly* to the #quiet element
  # are still called, only the bubbling is silenced.
  $('#quiet').on 'keyup', ->
    alert "Shhhh!"
     ​

None of this is any different from what you're already doing. It's just wrapped up for convenience.

\$\endgroup\$
  • \$\begingroup\$ As my comment above, I have inputs & textareas dynamically created at any point, None of them are in the dom for the initial binding, so $('input,textarea') will match no elements, I have to use a delegate. \$\endgroup\$ – Rob Oct 12 '12 at 15:14
  • \$\begingroup\$ @Rob Not sure if you're clarifying your point and confirming the assumption I made, or if you're thinking I'm using $('input,textarea') somewhere. If it's the latter, I'm not: This is all using a delegate. For this to work, though, you have to be able to call .ignore() on the elements you add dynamically, when you add them. \$\endgroup\$ – Flambino Oct 13 '12 at 5:28
  • \$\begingroup\$ I was just clarifying why I'm using a delegate as I missed it form my original question. I'm torn between your example using an ignore function or just scoping to ignore in the first place, im inclined to go with your option1, which means im not clustering the initial binding with various specifics. Thanks for your advise \$\endgroup\$ – Rob Oct 14 '12 at 11:44
0
\$\begingroup\$

I'm still not 100% on JQuery yet, but why are you selecting the entire document? Just select the textareas and inputs, then use not() to remove the desired field from the selectors.

$fields = $( 'input' ).add( 'textarea' ).not( '#afield' );
$fields.on( 'keyup', $.debounce( 600, editor.handleGlobalChange ) );

Edit

And apparently there's a keyup() function too, so...

$fields.keyup( $.debounce( 600, editor.handleGlobalChange ) );

Also, I'm assuming #afield is an input or textarea, if not, you can skip the not as it should then not be necessary.

\$\endgroup\$
  • 1
    \$\begingroup\$ FYI, your first line can be shortened to $('input, textarea').not(... - jQuery has no probs with multiple selectors \$\endgroup\$ – Flambino Oct 12 '12 at 14:50
  • \$\begingroup\$ Well damn... that makes life so much easier... Thanks \$\endgroup\$ – mseancole Oct 12 '12 at 14:53
  • \$\begingroup\$ Because im using a delegate. $('input,textarea') will only bind to elements available at initialization of my code, i'm basically using the updated "delegate" function api.jquery.com/delegate "bind", "live", "delegate" are all rolled into "on" now, I have input,textareas that can be created at any point after initial event binding. \$\endgroup\$ – Rob Oct 12 '12 at 15:08
  • \$\begingroup\$ I see, and I see I still have a lot to learn here myself... I'm still using live. Thank you for teaching me something new. \$\endgroup\$ – mseancole Oct 12 '12 at 15:17
  • \$\begingroup\$ No problem at all, appreciate your input, I think I may move to using "not". \$\endgroup\$ – Rob Oct 12 '12 at 15:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.