jQuery on/off method for click event fails from external script - javascript

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);

Related

Changed data attribute not recognized in jquery selector

I've the following html structure
<body data-page="first">
<div class="start">Test</div>
</body>
and the following js
$('body[data-page="first"] .start').on('click',function (){
body.attr('data-page','second');
});
$('body[data-page="second"] .start').on('click',function (){
console.log('Test');
});
I would expect, that after the second click on .start, the console would show "Test", but it doesn't...
Can you tell me what I'm doing wrong?
Thanks in advance!
While you have your answer, I don't think the essential point has been made in any of the answers so far, and that is that the binding of an event handler must happen after the target element exists.
When you try to bind an event handler to a particular element in the DOM, the element must exist at the time. If it does not exist, the handler has nothing to bind to, and so the binding fails. If you later create the element, it's too late, unless you re-run the binding statement.
It will soon become second nature to call appropriate event handler binding statements after you create a new element (by modifying the HTML using javascript) that needs a handler.
For instance, in my current project I regularly make AJAX calls to a server to replace blocks of HTML as things happen on the page. Even if some of the new elements are exactly the same as the ones being replaced, they will not inherit any bindings from the replaced elements. Whenever I update the HTML I call a function that contains necessary statements to bind my event handlers to the new copy of the active elements.
Your code would work if you made the following change:
$('body[data-page="first"] .start').on('click',function ()
{
body.attr('data-page','second');
$('body[data-page="second"] .start').on('click',function (){
console.log('Test');
});
})
A couple of other (off-topic, but related) points:
It's possible to bind a handler to an element multiple times. The trick to avoiding this is to include the .off() method in the chain before binding (noting though that .off("click") will unbind all click handlers bound to that element, not just yours) e.g.
$("#mybutton").off("click").click(function(){myHandler()});
"the arrow function doesn’t have its own 'this' value" () so don't use arrow functions in event handlers if you plan to reference any of the element's properties via 'this'. e.g.
$("#mybutton").off("click").click(() => {console.log(${this.id})}); // >> "undefined"
The issue is that the page is rendered with the data-page set to first, and when you click again on it, that part of javascript still see "first", since is not rerendered, so you need a dynamic function, the read all the intereaction with that button, and than check wich value that attribute has. Like this you can make infinite cases, and still go on.
$('body .start').on('click',function (){
const attr = $('body').attr('data-page');
if(attr === 'first') {
$('body').attr('data-page','second');
} else {
console.log('second');
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body data-page="first">
<div class="start">Test</div>
</body>
And if you don't like the fact that is targetting all the "body" wich is weird, becouse you should have only 1 body, you can use an ID to target the right one
PS: is never a good idea to duplicate your function, if you can set everything in a dynamic function, that reads everything, is easier to debug in the feature, and is lighter and more clean to work on
$('body[data-page="first"] .start').click(function (){
var body = $('body[data-page="first"] .start');
body.attr('data-page','second');
});
This method can help :
var timesClicked = 0;
$('.start').on('click',function (){
timesClicked++;
if (timesClicked>1) {
console.log('Test');
}
});

Setting execution order of bound scripts

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>

Calling a function in a script which is included twice in a page

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!

onclick and jQuery event binding causing pain

I have a link which has an onclick attribute, and there is a toggle event that I bind to the link (I know I'm committing a sin here by mixing these two, but there is nothing I can do about it.)
Now to this background, I have 2 scenarios:
The user clicks on the link - The order of execution of events is :- onclick first, then the toggle event bound via jQuery
Fire the click event via jQuery - The order of execution here is different, the bound event fires first then the onclick.
Something goes horribly wrong because of these 2 scenarios and the flipping of the order. I need the bound events to run first before the onclick. Is there any better way to do this than removing the onclick attribute on init and saving them to the link via .data() and then handling through the toggle event?
Another thing that I need to take care of (life is complicated), the link can be toggled through the querystring. i.e. if the user comes in from another page via another link, there will be a querystring parameter with the link id, which is read by another JavaScript function that does scenario 2 mentioned above.
So if the onclick is to be removed, it will have to be done on init.
What can I do to untangle this mess?
What is so wrong to remove the .onclick function and re-bind it afterwards (after you bound all your methods which should fire before) ?
HTML
<div id="foo" onclick="inline();">click me</div>
Javascript
function inline() {
alert('I was bound through onclick=');
}
$(function() {
var $foo = $('#foo'),
stored = $foo[0].onclick;
$foo[0].onclick = null;
$foo.bind('click', function() {
alert('I was bound via jQuery');
});
$foo.bind('click', stored);
});
After that code, the order of alerts would be:
'I was bound via jQuery'
'I was bound through onclick='
Demo: http://jsfiddle.net/3MKWR/

Does jQuery handle events without setting the normal event handlers?

With a div on an HTML page, I used jQuery to add a click event handler:
$(document).ready(function(){
$("div").click(function() { alert("clicked!"); });
});
Then I used the Visual Studio 2008 (or IE8) debugger to step through the javascript/jquery. I expected to see a value in the debugger for:
$(“div”)[0].onclick
($(“div”)[0] being the first div in the collection of results of the selector, $(“div”)).
But I don’t. I thought jQuery’s event assignment methods (e.g., .click(function)) were actually assigning values to an element’s underlying events. They’re not?
In other words, these two lines of code don’t have the same affect, but I thought they would:
$("div").click(function() { alert("clicked!"); }); // (Line 1)
$("div")[0].onclick = function() { alert("clicked!"); }; // (Line 2)
Can anyone explain this or point out if I’m doing something wrong? I would like to use line 1 in my code, but for my needs it seems I’ll have to use line 2 (FYI, I am planning to use an actual function, and not just show alerts :-) ).
Thank you
jQuery does not use the onclick attribute to attach events to elements. It uses either attachEvent(IE) or addEventListener(all others). You can find the smoking gun in lines 2500-2502 of the uncompressed jquery-1.3.2.js.
You should be able to, however, place a breakpoint inside your jQuery event handler and have Visual Studio stop there. Either click inside the body of the event handler (e.g. on "alert") before hitting F9, or break the event handler body into a its own line, e.g.
$(document).ready(function(){
$("div").click(function() {
alert("clicked!"); // place a breakpoint on this line
});
});

Categories