0

I have a page with many buttons, many of which I add after the initial jQuery loading, using ajax.

I have some handlers in my script.js file, here is one:

$('.editor-button.remove').click(function () {
 var row = $(this).parent('.row');
 row.remove();
});

How can I make sure:

  1. The handler is always attached to all the buttons satisfying the jQuery selector, even those who were just loaded to the page via ajax
  2. The handler is not attached zillion times to each element on each ajax call
  3. What is the general convention to make my entire website handled via jQuery classes instead of inline onclick handlers, is this a bad habit? First two questions apply to this one too obviously.

I'm pretty new to event-handling via jQuery concept, I want to keep script.js as clean as possible not polluting it with localized stuff, in the other hand, I want my buttons to contain only class names, not ciphered functions etc. General rule is my code should be as clean as possible while each handler isn't attached zillion times with each ajax call.
I'm not looking for a solution to the issue I mentioned above, but rather for general guidelines on how to treat jQuery handlers as efficiently and clean as possible.

Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632

1 Answers1

5

Event delegation is what you're after:

$('#parent').on('click', '.editor-button.remove', function() {
    $(this).closest('.row').remove();
});

Make sure that #parent is present when the event handler is attached. If there is none, use document.

By attaching the event handler to #parent, events triggered by the children will be delegated to the parent and accepted if the targeted element matches your selector. This will take into account dynamically created elements and won't even attach an event handler to those children to begin with.

Blender
  • 289,723
  • 53
  • 439
  • 496
  • BTW, those functions I'm using must not be page-specific or id-specific, I want to keep the js clean and dynamic, and not pollute it with localized stuff. – Shimmy Weitzhandler May 16 '13 at 00:46
  • So can I use `$('body')` instead? – Shimmy Weitzhandler May 16 '13 at 00:47
  • depending on the elements you're binding to - you may also want to use event.preventDefault(); http://api.jquery.com/event.preventDefault/ $('#parent').on('click', '.editor-button.remove', function(event) { event.preventDefault(); }); – Stone May 16 '13 at 00:47
  • 2
    @Shimmy: Use the closest parent element that's present when you attach the event handler. If there is none, you attach it to `$('body')` or `$(document)`. – Blender May 16 '13 at 00:47
  • You didn't refer to the 3 items in the list, especially the 3rd, I'm pretty new to jQuery event handling and developing webpages in this method, I want to know what are the general rules and conventions. – Shimmy Weitzhandler May 16 '13 at 00:48
  • @Shimmy: The general rule is not to use `onclick`. Attaching event listeners is cleaner, looks nicer and is easier to maintain. – Blender May 16 '13 at 00:49
  • So you say that the `on` works even on the elements loaded via ajax? So I should replace all jQueries as in my question like the following: `$(classSelector).click(handler)` with `$(document).(classSelector).on('click', handler)`? Any shorter ways? Is it true that `$()` refers to `$(document)`? – Shimmy Weitzhandler May 16 '13 at 00:55
  • @Blender "Use closest parent", I will have to pollute my code, which I don't want to do. Code as to be as clean and dynamic as possible, I've added some content to my question. – Shimmy Weitzhandler May 16 '13 at 00:56
  • @Shimmy: Don't over-abstract things. There's nothing wrong with hard-coding a parent element. You have to keep performance in mind as well, as event delegation is slower than a normal event listener. – Blender May 16 '13 at 00:57
  • @Shimmy: `$()` is an empty jQuery object. It doesn't refer to anything. – Blender May 16 '13 at 00:59
  • @Blender, for the sake of the community, please update your answer with our discussion. Also, please update on how to get the parent without messing with it too much, bear in mind that I can't really tell if the parent it gonna change – Shimmy Weitzhandler May 16 '13 at 00:59
  • @Shimmy: This is common knowledge, so I don't see any need to re-write it again. The static parent element is the element that you don't change. If you create the elements dynamically and load them into another element, that element that you load them into is the static parent element. It's worthless to explain this in words, IMO. Just try it out and you'll see what I'm talking about. – Blender May 16 '13 at 01:01
  • @Blender OK. Thanks. Now is `$('#parent').on('click', '.editor-button.remove', handler)` similar to `$('#parent').click('.editor-button.remove', handler)`? Which one is better etc.? – Shimmy Weitzhandler May 16 '13 at 01:07
  • 3
    @Shimmy: The second one isn't valid. – Blender May 16 '13 at 01:08
  • @Shimmy, firstable, +1 for good answer and having same nick as me in real life :) $('#parent').on('click', '.editor-button.remove', handler) is used to bind onClick event at run time. so when you dynamically create element via ajax, this will get binded to this click event. However (classSelector).on('click', handler) only gets binded at page load, and doesn't bind the new element that was dynamically created via ajax – Raver0124 May 16 '13 at 01:10
  • Oh, I found [the bible](http://stackoverflow.com/a/9730309/75500)... Thanks for your patience! – Shimmy Weitzhandler May 16 '13 at 01:10
  • @Raver0124 When you use event delegation, it only binds **one** event - to the original selector. Since events bubble, it matches the target element with the selector in the second parameter. With normal event binding, it gets bound to each element. – Ian May 16 '13 at 01:11
  • 1
    @Shimmy You're not polluting your code by using the nearest parent ID. Finding elements by ID is faster than searching through elements with specific classnames, and makes sure the event handler only runs on a specific set of elements. Furthermore this answer is generally accepted as the best method of handling this problem. As he mentioned, you don't need to bind to multiple elements, and it catches elements loaded after DOM ready. If you're worried about loose coupled/generic implementations you can pass the id into a function via the function arguments. +1 to this answer. – 1nfiniti May 16 '13 at 01:13
  • @mikeyUX Thanks for the explanation. Regarding this issue, how should I make sure all `'.time-picker'`s are jQueried with timepicker widget, even those loaded via ajax? – Shimmy Weitzhandler May 16 '13 at 01:18
  • @Shimmy: That you have to do manually by calling `.timepicker()` on the ones that are loaded. – Blender May 16 '13 at 01:19
  • If you have posted clarifying information, please edit into the question or answer as appropriate. If you have an alternate answer, please post it. The system has flagged these comments because there are so many of them in a short amount of time. Please use [chat](http://chat.stackoverflow.com) for extended discussion. Comments may be deleted at any time, for any reason. – George Stocker May 16 '13 at 01:32
  • @Shimmy - as per Blender's answer you call timepicker & apply it to elements after they've been loaded. Ideally you would do this in your ajax success callback. You don't run into the same issues here as you would with setting an event handler (you will be able to select elements that have been added to the DOM as long as you're running the code after they are inserted. With event handlers, the event may fire after the elements have been inserted, but it gets bound before... which is why the delegation is necessary). – 1nfiniti May 16 '13 at 16:36
  • Your answer is very helpful. Anyway, I'm encountering a situation where I do want to attach a handler for both 'load' and 'ajaxSuccess' meaning, I want to execute when a classed element is loaded no matter if via request or via ajax. I tried `on('load ajaxSucess')` but it doesn't trigger at all. – Shimmy Weitzhandler May 19 '13 at 12:09
  • @Shimmy: `success` as two `c`s. – Blender May 19 '13 at 17:13
  • @Blender type was on SO, wasn't the real problem – Shimmy Weitzhandler May 20 '13 at 08:00