4

I have a button which has :

  • Inline onclick
  • 3 attached click handlers :

Example

<input id='b1' type='button' value='go' onclick='alert("0")';'/>
$("#b1").on('click',function (){alert('1');});
$("#b1").on('click',function (){alert('2');});
$("#b1").on('click',function (){alert('3');});

It alerts 0,1,2,3.

But How can I insert new onclick at "custom index" ?

For example , Lets say that there is a function which I want to insert between the alerts of 1,2 .

How can I do that ?

p.s :

Controlling the inline onclick is not a problem , I just take its attr and execute it later. The problem for me is the .on registers. I thought about $.data("events") But it ws deprecated and should be used only for debug proposes. ( also - it's syntax has changed).

JSBIN

Royi Namir
  • 144,742
  • 138
  • 468
  • 792

7 Answers7

6

I wrote a small jQuery plugin that claims to do this.

How to use?

Use on jQuery function (that is actually rewritten by my plugin) like below:

<script src="path/to/jQuery.js"></script>
<script src="path/to/jQuery-priorityze.js"></script>

<button id="go">Test 1</button>

<script>
  var $go = $("#go");
  $go.on('click', 5, function () { console.log('nice'); });
  $go.on('click', {priority: 4}, function () { console.log('a'); });
  $go.on('click', 6, function () { console.log('plugin?'); });
  $go.on('click', 1, function () { console.log('Is'); });
  $go.on('click', 3, function () { console.log('this'); });
  $go.on('click', 2, function () { console.log('not'); });
<script>

If priority is not provided the plugin will choose the priority of the event incrementing 1 to the latest provided priority.

How to use in your example

I was hoping to get a solution without changing existing structure

Include my plugin in the page and use this:

$("#b1").on('click', function (){alert('1');}); // 1
$("#b1").on('click', function (){alert('2');}); // 2
$("#b1").on('click', function (){alert('3');}); // 3

$("#b1").on('click', 1.5, function (){alert('4');}); // 1.5

// 0 -> 1 -> 4 -> 2 -> 3
// seems that onclick attribute is stronger that .on

function topPriority() {
  alert('top priority');
}

Setting 1.5 priority puts the last handler between the first (1) and second (2) one. Like I commented onclick attribute is stronger than on jQuery function.

UPDATED JSBIN

Source code

Checkout the source code here on Github and check the tests.

Don't forget to star fork it: https://github.com/IonicaBizau/jQuery-prioritize :-)

How does it work?

The plugin is compatible with jQuery >= 1.8.x and it uses $._data($el[0]).events like it was described here.

Contributing

Do you want to contribute to this project? Great! Follow the following steps:

  1. Search in the repo issues an issue you want to fix.
  2. If you want to add a new feature that is not added as issue, add it first in the issue list.
  3. Fork the project to your profile.
  4. Fix the issue you want to fix.
  5. Make a pull request.

I will try to merge the pull requests as fast I can.

Community
  • 1
  • 1
Ionică Bizău
  • 109,027
  • 88
  • 289
  • 474
  • Very nice and +1 for the effort. but how does it fit when there is an existing page and you need to "push" handler ? as I wrote : _I was hoping to get a solution without changing existing structure_ – Royi Namir Dec 19 '13 at 20:11
  • I dont have the time to prepare a pull request, but your plugin (that its quite good) is not correct. Please note that it only distinguish the event type for the first call, so if you do a $("#b1").pOn('click', 6, function (){alert('click'); }); and then a $("#b1").pOn('mouseover', 1, function (){alert('hover'); }); both of them will be triggered on clic. you can check it in this fork of your jsfiddle: http://jsfiddle.net/Q5fuK/ BTW, @RoyiNamir, did you take a look at my answer? i dont know if that is quite close to what you need of not changing existing estructure. – Carlos Robles Dec 19 '13 at 21:58
  • @CarlosRobles yes I have and it's very close to what i need. I jsut wait a day or 2 to see if other ideas are coming. – Royi Namir Dec 19 '13 at 22:04
  • @CarlosRobles Yes, that's a bug. I am sure that there are others, too. But I will try to improve it. – Ionică Bizău Dec 20 '13 at 05:54
  • @RoyiNamir Thanks! I will update the answer today with a better version. This version was my first approach - a good place to start. – Ionică Bizău Dec 20 '13 at 05:55
  • @RoyiNamir I can say that my plugin is ready to use. Please checkout the latest version and demos. – Ionică Bizău Dec 20 '13 at 09:39
  • I think that overriding "on" is very dangerous. – Royi Namir Dec 20 '13 at 09:42
  • @RoyiNamir It depends... You said you don't want to change the syntax. :-) While we know what we are doing, it's ok to rewrite it. If there are problems using the plugin, please open an issue. – Ionică Bizău Dec 20 '13 at 09:44
2

If you need a dinamic stuff, where you can add some events at any point in your code, and without changing the structure of what you are already doing, i recomend an easy workaround:

at the begining of your code you create:

  • an empty array of callbacks.
  • assign the $("#b1").on('click') to a function that iterate through the array calling every callback
  • and create a new jQuery function, called onclick, to use instead of .on ('clic', that does the same but receives an index. Something like this
  • and now, anywhere in your code where you was calling: $("#b1").on('click',function (){alert('1');}); now you have to call $("#b1").onclick(function (){alert('1');}); // if you want it after all the other events or $("#b1").onclick(function (){alert('1');}, INDEX); //if you want an specific order (INDEX is an integer)

The complete code would be this:

$("#b1").on('click',function(){
 for (var i = 0; i < callbacks.length; i++) 

     callbacks[i]();
});
callbacks= new Array();
jQuery.fn.extend({
  onclick: function(callback, index) {

      if (index) {
        callbacks.splice(index, 0,callback);
      }
      else   callbacks.push(callback); 
  } 
});

You just put that once in the beginning, and then, everywhere you what to register an event, you do almost as you are used to, for example:

$("#b1").onclick(function (){alert('1');}); 
$("#b1").onclick(function (){alert('2');}); 
$("#b1").onclick(function (){alert('3');}); 

$("#b1").onclick(function (){alert('new 2');},2); //index 2

You can try it in this jsfiddle

Carlos Robles
  • 10,828
  • 3
  • 41
  • 60
  • 1
    Improvement: call the callbacks with expected "this" an "arguments". [updated fiddle](http://jsfiddle.net/TY26h/1/) – LeGEC Dec 20 '13 at 09:21
1

Actually, order of multiple callbacks is guaranted in JQuery (and not in vanilla js), but it maintained with some internal JQuery mechanism. You can create array of callbacks and iterate through it:

var callbacks = [
function (){alert('1');},
function (){alert('2');},
function (){alert('3');}
];

$("#b1").click(function() {
   for (var i = 0; i < callbacks.length; i++) callbacks[i]();
});

Now you can maintain order by shuffle or extend your callbacks array like a regular array

Tommi
  • 3,199
  • 1
  • 24
  • 38
  • _order of multiple callbacks is not guaranted_ ?? – Royi Namir Dec 11 '13 at 08:00
  • http://stackoverflow.com/questions/2360655/jquery-event-handlers-always-execute-in-order-they-were-bound-any-way-around-t – Royi Namir Dec 11 '13 at 08:02
  • Ok, JQuery can do smart things about it, but it vanilla js it's not guaranted. Anyway, I don't see not-hack method to interrupt internal JQuery things. – Tommi Dec 11 '13 at 08:03
  • You might want to remove the **incorrect** first line . _order of multiple callbacks is not guaranted._ – Royi Namir Dec 11 '13 at 08:04
  • Fixed, thanks. Once again: I really recommend you not use hack ways to modify your callbacks - behavior can be changed in later versions of JQuery. – Tommi Dec 11 '13 at 08:06
1

Okay, this is not impossible. First, I am only sure that this works with events added with jQuery, but I think it might work in other cases. There were a few things that needed to be considered:

  • There are several ways to add click (or other) events to an element:
    • Add an onclick attribute to the element.
    • Bind an event handler directly to the element.
    • Event delegation, bind a handler to the document or a parent element and scan each event as it is triggered to see if it matches the handler's selector.
  • Multiple event handlers for the same event get queued up, and are executed one after the other. The order in which they are queued is a little complicated, considering that there are different ways of attaching the events and browsers do things differently. Here is an article talking about this issue. My experience is that if you add an event handler after other event handlers have been added it gets queued after any other events added on the same element. In Chrome 31, the onclick attribute of an element goes first, but if you change that attribute after the document has loaded, it gets executed after any other handlers attached to that element.

So the objective is to add a new event handler that will be executed in whatever order you choose, without modifying any existing code, and using jQuery. So I made a script that does that, and here is how it works: It grabs all the click events of all types, from the target event up to the document, and keeps the ones that match with the target. It removes the current onclick function from the target element and adds it to the events. Then it adds a custom onclick function and disconnects all click events that are bound to the target. In the custom onclick function stopPropagation() keeps the event from bubbling up the DOM, inserts the new function at the desired index, and then runs the events in order. It shouldn't mess with any click events triggered by other elements

Here is a working jsfiddle: http://jsfiddle.net/mMG99/8/

Here's the code, it could probably be cleaned up a lot:

<input id='b1' type='button' value='go' onclick='alert("0");'/>

<br>

<input id='index' type='number' min='0' />
<input id='b2' type='button' value='add listener at this index'/>

// Uses different ways to add the event handlers to test if this works with each
$("#b1").on('click',function (){alert('1');});
$("#b1").bind('click',function (){alert('2');});
$(document).on('click', '#b1', function (){alert('3');});

var selector = '#b1';
var insertedFunction = function(){alert('heyooo!');};    
var $target = $(selector);
var oldOnClickFunction = new Function($target.attr('onclick'));

var targetEvents = [oldOnClickFunction];
var boundEvents = $._data($target.get(0), "events");
boundEvents && boundEvents.click && $.each(boundEvents.click, function(i, val){
  targetEvents.push(val.handler);
});

var $targets = $(document).add($target.parents());   
$targets.each(function(){
  var eventObj = $._data(this, "events");
  eventObj && eventObj.click && $.each(eventObj.click, function(i, val){
    if($target.filter(val.selector).length) {
      targetEvents.push(val.handler);
    } 
  });
});

newOnClick = function(event){
  event.stopPropagation();
  $.each(targetEvents, function(i, handler){
    handler(event);
  });     
};

$("#b2").on('click', function(){
  $target.attr('onclick', 'newOnClick(event)');
  var removeAt = targetEvents.indexOf(insertedFunction);
  if (removeAt != -1) {
    targetEvents.splice(removeAt,1);  
  }
  targetEvents.splice(parseInt($('#index').val()), 0, insertedFunction);
  $target.off('click');
});
RustyToms
  • 7,600
  • 1
  • 27
  • 36
0

The simplest way, changing the least amount of your code, would be to put the new function call inside the event handler you want it to go after. But the best way would be to just have one event handler that calls all the functions that you want to call, instead of having some many event listeners:

<input id='b1' type='button' value='go' onclick='alert("0")';'/>

$("#b1").on('click',function (){
  alert('1');
  newFunctionCall();
  alert('2');
  alert('3');
});
RustyToms
  • 7,600
  • 1
  • 27
  • 36
0

I am not sure you can find something to work this around, at least the way you'd like to do it.

Anyway you will need some kind of switcher and as far as you can't read anything about current events and cannot make unbind -> this switcher should be implemented in the function which you replace here by "alert".

Quite funny solution (checked in Chrome only): http://jsbin.com/UsuRanAR/3/edit

function alert(a) {
  setTimeout(function(){
    console.log(a);
  },((a == 1 || a == 'top priority')?0:200));
}

I am afraid that's the maximum you can get from this situation.

smnbbrv
  • 23,502
  • 9
  • 78
  • 109
0

I was going to suggest :

Add a placeholder html node, hide the original node, when clicking on the placeholder, do your "priority" stuff then trigger click on the original node :

var $clone = $('#b1').clone().attr('id', null).attr('onclick', null);
$clone.insertBefore('#b1');
$('#b1').hide();

$clone.on('click', function(e){
    alert('Kilroy was here');
    $('#b1').trigger('click');
    // do not duplicate the bubbling/default action
    return false;
});

fiddle

But for some reason, the onclick callback gets called last in this case ...

LeGEC
  • 46,477
  • 5
  • 57
  • 104