Is there a way to control execution order of JS scripts bound to element click? For example, we have two separate scripts, that are fired on the same action and for the same element.
First script (fired by script on website - which I have no permission to change):
$('body').on('click', '#button', function1);
And the second script (which is fired by browser plugin, which I can change, and would run after everything else was fired):
$('body').on('click', '#button', function2);
Those two scripts are separated (in two different files) and cannot be merged into one like this, because I can change only the second one:
$('body').on('click', '#button', function() {
function1();
function2();
});
Can I set the order of execution? Can I force one script to be executed last?
In your second script you can read which function was bound to the click event by the first script. Then you can detach that handler and add your own, where you can (if you want) add the execution of the original function in the last step:
// script 1 ///////////////////////////////////////////////////////////
function f1() {
console.log('f1: function bound by script 1');
}
$('body').on('click', '#button', f1);
// script 2 ///////////////////////////////////////////////////////////
function f2() {
console.log('f2: my own function that should run first');
}
var original_f = $._data(document.body, 'events').click[0].handler;
$('body').off('click', '#button').on('click', '#button', function () {
f2();
original_f();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="button">Trigger handlers</button>
This makes use of $._data, which is not part of the public API of jQuery. See this answer.
Remarks
The above solution assumes that
the site's script (the first script) uses jQuery event delegation on the body element for the element #button; as follows:
$('body').on('click', '#button', function1);
Note that there are other ways to attach event handlers, but this answer assumes that the first script does it like this (as you provided in the question). Of course, function1 can be any function, or even an anonymous, in-line function.
You have full control over the second script and can redefine the click handler you have defined in it.
You write in comments that you cannot access the function of the first script (function1). But please realise that if JavaScript is able to execute it, there is no reason why you could not have access to it via JavaScript.
Furthermore, since you informed that the first script uses jQuery to attach their handler, also jQuery knows which function it is and can access it (how else could it execute it when a click event happens?).
The solution I provide above actually asks jQuery to provide you the reference to that function. And then you can execute it just like jQuery would do when a click event occurs.
Testing the presence of the handler
In order to verify that indeed the first script has set a handler in this way:
$('body').on('click', '#button', function1);
... and that is still there, you can use the following script for debugging:
// script 1 -- you don't need to enter this, it is for this snippet to work with.
function f1() {
console.log('f1: function bound by script 1');
}
$('body').on('click', '#button', f1);
// script 2 -- your code ///////////////////////////////////////////////////////////
var click_handlers = $._data(document.body, 'events').click;
console.log('Info on BODY click handlers that have been set via jQuery:\n',
JSON.stringify(click_handlers, null, 2));
console.log('First click handler is this function:\n', click_handlers[0].handler);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Related
For a dynamic added element via ajax or any after page load, I know that we have to use the .on like
$(document).on('click', '#dynamically-added-element', function() {
// do something
console.log('Hello World!');
});
I even wrapped mine with (function($){})(jQuery); to make sure no conflict; and to make sure everything should be run/loaded in proper order, I also have $(document).ready(function(){});.
Now that event was originally meant to run on Page A and it works well, but when I made and try it run on Page B having the same id for the main container and have the same child html, it doesn't work.
So I've done some experiment/tests and both works.
(1) So what I've done, I wrote exact same event listener through my browser's console with just console.log and click the target element, it works!
(2) On the same .js script, I've added exact same event listener with just console.log but wrapped it inside setTimeout(function(){}, 5000);, after 5 seconds, I clicked the target element and it worked!
So my question now is, why my original code doesn't work? Why these tests works? So what's the solution?
Also, my html elements aren't loaded dynamically, it's loaded on page load, generated via server side PHP.
Edit:
This info might be useful.
running in magento
js script is being included on head (that's why $(document).ready is really important).
jquery script is being loaded before my custom script.
Page A and Page B are on different pages.
It's not an ajax generated element nor js/jquery added element on the fly; it's generated on postback with php code
(3) My third experiment works as well. But I don't want this to be my solution if possible, nor using javascript inline onclick. So what I did, I added a whole new script block inside the php file where that 'part' of the page is being included, with exact same code and it works. But the thing is, I have now two identical event listener.
So now I have 2 identical event listener (regardless if the other one doesn't work - only for Page B), 1 from within (internal) and 1 from external .js file.
<script type="text/javascript">
(function($) {
$(document).ready(function() {
$(document).on('click', '#the-container a', function(e) {
e.preventDefault();
var target = $(this).attr('href');
});
});
})(jQuery);
</script>
Just write you document.on outside of the document.ready that should work and comment your "e.preventDefault();". It worked on the fiddle when I changed it.
<script type="text/javascript">
$(document).on('click', '#the-container a', function(e) {
//e.preventDefault();
var target = $(this).attr('href');
});
</script>
jQuery event handlers are attached to the elements themselves not the selector, if you are instantiating an event handler to an element selector before the element has been loaded, it is not attaching a listener to anything.
For example this will not work:
$.get('http://example.api.com',
function(data){
$node = $('<p></p>')
$node.text(data)
$node.addClass('elementToListen')
$('body').append($node)
})
// this gets executed before the ajax request completes. So no event
// listener gets attached
$('.elementToListen').on('click',function(e){'do somthing'})
because the event listener has been set up before any of the elements have been added to the DOM.
While this will work:
$.get('http://example.api.com',
function(data){
$node = $('<p></p>')
$node.text(data)
$node.addClass('elementToListen')
// here it is getting executed after the AJAX request in the
// success callback
$node.on('click',function(e){'do somthing'})
$('body').append($node)
})
This works when the script is embedded. Why does it only work for two clicks when the code is in an external script?
Embedded case:
<html>
<head>
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
</head>
<body>
<h1 id="title" onclick="sayGoodbye();"> Hello </h1>
</body>
<script type="text/javascript">
function sayGoodbye() {
console.log("goodbye");
$("#title").html("Goodbye");
$("#title").click(function() {
$("#title").html("Hello");
$("#title").off("click");
});
};
</script>
</html>
External file tested as follows:
<body>
<h1 id="title"> Hello </h1>
</body>
External Javascript file:
function sayGoodbye() {
console.log('goodbye');
$('#title').html('Goodbye');
$('#title').click(function() {
$('#title').html('Hello');
$('#title').off('click');
});
};
$(function(){
$('#title').on('click', sayGoodbye);
});
The problem has nothing to do with the file being external or embedded.
You've changed your code, and that's why it stopped working. You could keep your HTML code identical to the original one <h1 id="title" onclick="sayGoodbye();"> Hello </h1>.
Since you've changed, that's the reason it's not working anymore.
When adding event listeners via Javascript's addEventListener or jQuery's on, you must always pass a function reference, and never a function execution. If you use the (), the function will be called immediately, and the returned value of it will be passed to the other function you're calling.
For example, if I do that:
function num() {
return 5;
}
console.log(num); // it will log 'function num()'
console.log(num()); // it will log 5
Since your sayGoodbye function returns nothing, the value passed to jQuery's on will be undefined.
function num() {
var y = 5;
y = y + 10;
}
console.log(num); // it will log 'function num() '
console.log(num()); // it will log 'undefined'
When you put in HTML onclick="sayGoodbye();", you're in fact saying:
Element.onclick = function() {
sayGoodbye();
}
It automatically wrap your code into an anonymous function, and pass it as a reference to the event.
Tl;dr
Your code isn't working because you're using:
$('#title').on('click', sayGoodbye());
Which evaluates/calls the function immediately, because the parenthesis are present.
Instead, you should supply a callback function by name:
$('#title').on('click', sayGoodbye);
Or as an anonymous function:
$('#title').on('click', function() {
sayGoodbye();
});
Update
After you changing the question, it seems to be another simple problem.
Where are you defining your <script src="externalscript.js"></script>?
You should be placing it at the same place where your embedded one was. If you've put it into the head tag, it will be evaluated even before your h1#test is loaded, and then, it won't find the Element, and don't add the event listener to it.
But if you really want/need to put it inside the head tag, then, you must wrap your code into an load event listener. If using vanilla Javascript, it would be:
document.addEventListener('DOMContentLoaded', function() {
// your code
});
But since you're using jQuery, you can use $(document).ready:
$(document).ready(function() {
// your code
});
And there is a shorthand for it, that's just wrap into $();:
$(function() {
// your code
});
Note: I've just realised that your biggest problem here is that you don't understand the difference between doing <h1 id="title" onclick="sayGoodbye();"> Hello </h1>, and adding your event handler through jQuery.
Your first example is working the way you expected, because when you do:
$("#title").html("Goodbye");
$("#title").click(function() {
$("#title").html("Hello");
$("#title").off("click");
});
You're saying:
Change the innerHTML property of the element with id title to 'Goodbye'
Add a click event handler that:
2.1 Change the innerHTML property of the element with id title to 'Hello'
2.2 Remove all the jQuery event handlers for the event click
So, what is going to happen:
When you click the first time
1.1 Your h1 will change to 'Goodbye'
1.2 And there will be a new click event handler for it
When you click the second time
2.1 It will fire the same event again firstly, so your h1 will change to 'Goodbye'
2.2 There will be a new click event handler for it
2.3 The previously event handler added on 1.2 will be fired
2.4 Then, your h1 will change back to Hello
2.5 And you remove all the jQuery click event handlers of it
So, when you click the third time, it will be like it was when the page was loaded, because the event handler placed into the HTML code will still remains, but all the jQuery event handlers that were attached to the h1 were removed.
That said, 3rd will be the same as 1st, 4th will be the same as 2nd, and so on.
When you stop attaching the event handler into your HTML code, and start attaching it through jQuery, your code stops working because:
When you click the first time
1.1 Your h1 will change to 'Goodbye'
1.2 And there will be a new click event handler for it
When you click the second time
2.1 It will fire the same event again firstly, so your h1 will change to 'Goodbye'
2.2 There will be a new click event handler for it
2.3 The previously event handler added on 1.2 will be fired
2.4 Then, your h1 will change back to Hello
2.5 And you remove all the click event handlers of it
As you can see, pretty the same as the example above. But there is one big difference: When 2.5 happens, you don't have any click event handlers attached to the h1 anymore, since you've removed all of them there.
That happens, because when you pass only one parameter to the jQuery's off method, you're saying "Remove all the jQuery's event handlers for that type of event" (in that case, click). So, it will remove both the event handler attached to sayGoodBye and the one that you've created with the anonymous function.
In the first example, it doesn't happen, because jQuery doesn't know about the event you've attached via HTML code, so it never removes it, and your code works the way you expect.
So, if you want to work with jQuery only, your code should be something like:
$(function(){
function sayGoodbye() {
$('#title')
.html('Goodbye')
.off('click', sayGoodbye)
.on('click', sayHello);
}
function sayHello() {
$('#title')
.html('Hello')
.off('click', sayHello)
.on('click', sayGoodbye);
}
$('#title').on('click', sayGoodbye);
});
The syntax for passing a function reference is incorrect.
$('#title').on('click',sayGoodbye());
Will call the function immediately due to the ().
Change to:
$('#title').on('click',sayGoodbye);
In this example someone shows a jQuery onclick event.
My shows this:
$(function() {
$('.post').on('click', function() {
SOME STUFF
});
});
But what are the first and last line doing?
if i remove the lines, it's not working:
$('.post').on('click', function() {
SOME STUFF
});
But why? In the linked example is a second commenter. He shows this way (without first/last line).
Can someone explain this?
It is a shortcut for $( document ).ready(...)
See http://api.jquery.com/ready/
Quoting the doc :
While JavaScript provides the load event for executing code when a page is rendered, this event does not get triggered until all assets such as images have been completely received. In most cases, the script can be run as soon as the DOM hierarchy has been fully constructed. The handler passed to .ready() is guaranteed to be executed after the DOM is ready, so this is usually the best place to attach all other event handlers and run other jQuery code. [...]
All three of the following syntaxes are equivalent:
* $( document ).ready( handler )
* $().ready( handler ) (this is not recommended)
* $( handler )
That is short for document.ready. It waits until the entire document is loaded and the element with class .post can be found and bound to.
If you omit that part, the jQuery function will not find the element and your event will not work.
The first and last lines create an anonymous function. In computer programming, an anonymous function is a function defined, and possibly called, without being bound to an identifier.
In the example here it is used to set the event listener that is loaded onload of the page.
$(function() {
$('.something').on('click', function() {
alert('hello');
$(this).addClass('classOne');
});
});
$(function(){});
is jQuery short hand for
$(document).ready(function() {});
which ensures your document is ready for manipulation before executing anything within it. In many ways its similar to the browser window.onready event. See jQuery docs..
The risk if you don't wrap your jQuery code in either of these forms of the functions is that you will try and manipulate elements before they have been created by the browser. Your code is not guaranteed to fail, but you could, at the very least, get inconsistent behaviour.
Today, I read a question about the danger in including the same JavaScript library twice. I created a small project to test that with a simple alert in a function called on the click of a link. I used jQuery to help me binding an event handler to the click like that:
Case 1
Javascript:
$(document).ready(function () {
$("#TestLink").click(function() {
alert("Message");
});
});
Html:
<script type="text/javascript" src="#Url.Content("~/Scripts/Test.js")"></script>
<script type="text/javascript" src="#Url.Content("~/Scripts/Test.js")"></script>
<p>
<a id="TestLink">Click here</a>
</p>
Now, when I click on the link, the message is displayed twice. I understand that jQuery bind an event handler twice because the reference to the script has been added twice.
In another case, I change the code to looks like that:
Case 2
JavaScript:
function showMessage() {
alert("Message");
}
Html:
<script type="text/javascript" src="#Url.Content("~/Scripts/Test.js")"></script>
<script type="text/javascript" src="#Url.Content("~/Scripts/Test.js")"></script>
<p>
<a onclick="showMessage();" id="TestLink">Click here</a>
</p>
When I click on the link, the message is just displayed once even if I included the script twice.
My questions:
In the case 1, how the process of binding an event handler twice is done when the same script is added more than once? I just want more details about the hook of events when a the same script is added more then once; articles, references, etc...
In the case 2, how the browser resolves the conflict having to choose which script to used for calling the function?
In the first case, the event is binded twice. It is sometimes desireable to do so. If you want a click event on a div, and another click event on the same div that isn't related, you might want both functions to execute. That's why both click events will fire and the functions called. In this case, it's the same function that is called twice! An example of a useful scenario for binding two functions on an event:
You might want to know whether a user is active. A click event on the
body will tell you so. But, if an overlay is active, you want to
remove the overlay. Both functions are executed on the click event on
the body.
In the second case, the function you declared in the first include of the javascript is overwritten by the same function in the second javascript. The first declaration is gone. The function is therefore just called once:
var test = function() {
alert('first one!');
}
var test = function() {
alert('second one!');
}
test(); // second one!
Here's what I'm trying to do :
I have a page with some links. Most links have a function attached to them on the onclick event.
Now, I want to set a css class to some links and then whenever one of the links is clicked I want to execute a certain function - after it returns , I want the link to execute the onclick functions that were attached to it.
Is there a way to do what I want ? I'm using jQuery if it makes a difference.
Here's an attempt at an example :
$("#link").click(function1);
$("#link").click(function2);
$("#link").click(function(){
firstFunctionToBeCalled(function (){
// ok, now execute function1 and function2
});
}); // somehow this needs to be the first one that is called
function firstFunctionToBeCalled(callback){
// here some user input is expected so function1 and function2 must not get called
callback();
}
All this is because I'm asked to put some confirmation boxes (using boxy) for a lot of buttons and I really don't want to be going through every button.
If I understand you correctly, is this wat you wanted to do..
var originalEvent = page.onclick; //your actual onclick method
page.onclick = handleinLocal; //overrides this with your locaMethod
function handleinLocal()
{ ...your code...
originalEvent ();
// invoke original handler
}
I would use jQuery's unbind to remove any existing events, then bind a function that will orchestrate the events I want in the order I want them.
Both bind and unbind are in the jQuery docs on jquery.com and work like this...
$(".myClass").unbind("click"); // removes all clicks - you can also pass a specific function to unbind
$(".myClass").click(function() {
myFunctionA();
myFunctionB($(this).html()); // example of obtaining something related to the referrer
});
An ugly hack will be to use the mousedown or mouseup events. These will be called before the click event.
If you can add your event handler before the rest of handlers, you could try to use jQuery's stopImmediatePropagation