Why does the handler bound to an event of an element fire the wrong result? I would expect the click event of Div1 below to popup a dialog stating 'div1' but it popup's 'div2'.
I am new to this and I am scratching my head to work out why this is happening. I would appreciate any help to explain.
Cheers,
Alex
<!DOCTYPE html>
<html>
<head>
<title>TestEvents</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.js" type="text/javascript"></script>
<script type="text/javascript">
//Object Array
var objToTest = [{ TabName: "div1" },
{ TabName: "div2"}];
//Adds events to each div
function TestWhatIsGoingOn(myObjToTest) {
for (i in myObjToTest) {
$('#' + myObjToTest[i].TabName).click(function() { TestResult('TabName: ' + myObjToTest[i].TabName); });
}
}
function TestResult(message){
alert(message);
}
$(document).ready(function() {
TestWhatIsGoingOn(objToTest);
});
</script>
<style type="text/css">
#div1, #div2
{
border: solid thin black;
height: 100px;
width: 300px;
}
</style>
</head>
<body>
<div id='div1'>div1; click here to show expected result: 'TabName: div1'</div>
<div id='div2'>div2; click here to show expected result: 'TabName: div2'</div>
</body>
</html>
it seems a classic closure problem, because when you click on div (any) i variable has already reach the end of for loop (so it always prints the last value). Try to change like so
function TestWhatIsGoingOn(myObjToTest) {
for (i in myObjToTest) {
(function(i) {
$('#' + myObjToTest[i].TabName).click(function() { TestResult('TabName: ' + myObjToTest[i].TabName); });
)(i);
}
}
Your problem is in this section of code:
for (i in myObjToTest) {
$('#' + myObjToTest[i].TabName).click(function() {
TestResult('TabName: ' + myObjToTest[i].TabName);
});
}
The trouble is that the value of i is not hard-coded into this section. When the function runs, it will see what the current value of i is. Since you have since incremented it to refer to your second tab, this function will always refer to the second tab. This feature of Javascript is called a closure -- it closes in the value of i.
The easiest way around this is to use jQuery to bind to more than one object at once, and then evaluate based on the object clicked on:
$(document).ready(function(){
$('div').click(function(){
alert('TabName: ' + this.id);
});
});
This will do everything you want your code in the question to do.
In a real-world situation, you would probably need to give the divs a common class (e.g. toClick) and then use a jQuery class selector ($('.toClick')).
Related
I am trying to write a tutorial for my students, in the form of a webpage with hidden "spoilers" that the student can unhide, presumably after thinking about the answer. So, long story short, the behavior I am looking for is:
in the beginning, the text appears with a lot of hidden words;
when a piece of text is clicked, it appears, and stays uncovered afterwards;
this should work with minimal overhead (not forcing me to install a complex framework) and on all my students' machines, even if the browser is outdated, even if jquery is not installed.
I searched for off the shelf solutions, but all those I checked were either too complicated or not doing exactly what I wanted. So I decided to do my own.
What I have so far is this:
<HTML>
<STYLE>
span.spoil {background-color: black;}
span.spoiled {background-color: white;}
</STYLE>
<HEAD>
<TITLE>SPOIL</TITLE>
<META http-equiv="Content-Type" content="text/html;charset=UTF-8">
<!--LINK rel="Stylesheet" type="text/css" href=".css"-->
</HEAD>
<BODY>
This is a text with <span class="spoil" onclick="showspoil(this)">spoil data</span>.
<br>
<span class="spoil" onclick="showspoil(this)">Unspoil me.</span>
<br>
<span class="spoil" onclick="showspoil(this)">And me.</span>
<script>
function showspoil(e) {
e.className="spoiled";
}
// var classname = document.getElementsByClassName("spoil");
// for (var i = 0; i < classname.length; i++) {
// classname[i].addEventListener('click', showspoil(WHATEXACTLY?), false);
// }
</script>
</BODY>
</HTML>
It does the job, except that I find it annoying to have to write explicitly the "onclick..." for each element. So I tried adding an event listener to each member of the class, by imitating similar resources found on the web: unfortunately, this part (the commented code above) does not work. In particular, I do not see which parameter I should pass to the function to transmit "the element itself".
Can anyone help? If I may play it lazy, I am more looking for an answer to this specific query than for pointers to a series of courses I should take: I admit it, I have not been doing html for a loooooong time, and I am sure I would need a lot of readings to be efficient again: simply, I do not have the time for the moment, and I do not really need it: I just need to solve this issue to set up a working solution.
Problem here is you are calling the method and assigning what it returns to be bound as the event listener
classname[i].addEventListener('click', showspoil(WHATEXACTLY?), false);
You can either use a closure or call the element directly.
classname[i].addEventListener('click', function () { showspoil(this); }, false);
or
classname[i].addEventListener('click', showspoil, false);
If you call it directly, you would need to change the function to
function showspoil(e) {
this.className="spoiled";
}
Another option would be to not bind click on every element, just use event delegation.
function showspoil(e) {
e.className="spoiled";
}
document.addEventListener("click", function (e) { //list for clcik on body
var clicked = e.target; //get what was clicked on
if (e.target.classList.contains("spoil")) { //see if it is an element with the class
e.target.classList.add("spoiled"); //if it is, add new class
}
});
.spoil { color: red }
.spoiled { color: green }
This is a text with <span class="spoil">spoil data</span>.
<br>
<span class="spoil">Unspoil me.</span>
<br>
<span class="spoil">And me.</span>
function unspoil() {
this.className = "spoiled"; // "this" is the clicked object
}
window.onload = function() {
var spoilers = document.querySelectorAll(".spoil"); // get all with class spoil
for (var i = 0; i < spoilers.length; i++) {
spoilers[i].onclick = unspoil;
}
}
span.spoil {
background-color: black;
}
span.spoiled {
background-color: white;
}
This is a text with <span class="spoil">spoil data</span>.
<br>
<span class="spoil">Unspoil me.</span>
<br>
<span class="spoil">And me.</span>
An additional approach could be to add the click-listener to the document and evaluate the event target:
document.addEventListener("click", function(e){
if (e.target.className == "spoil"){
e.target.className = "spoiled";
}
});
That way
you only need one event listener in the whole page
you can also append other elements dynamically with that class without the need for a new event handler
This should work, because the event's target is always the actual element being clicked. If you have sub-elements in your "spoil" items, you may need to traverse up the parent chain. But anyway I think this is the least resource-wasting way.
var spoilers = document.getElementsByClassName('spoil');
for(i=0;i<spoilers.length;i++){
spoilers[i].addEventListener('click',function(){
this.className = "spoiled";
});
}
I have a <DIV> which I'm using as a button like this :
<div id='project_1_task_2_is_done'
class='button_style_1_small' onClick='taskIsDone(1,2,"y");'>
Is done?
</div>
I want to change the onClick string value to taskIsDone(1,2,"n") once the user clicks on it, and then, when it clicks again, back to taskIsDone(1,2,"y") and so on.
I'm trying to accomplish this from the taskIsDone javascript function such as
if(is_done=='n'){
document.getElementById('project_' + project_id + '_task_' + task_id + '_is_done')
.onclick = "taskIsDone(" + project_id + "," + task_id + ",'y');"
}
but it's not working.
I looked over the net to more than 7-8 examples, most from here (Stackoverflow) but I'm not finding the answer to my question (which is basically changing dynamically the onclick string for that DIV id. I don't want to run a function, as most of the examples i found present, just want to change the onclick string value, modifying "y" to "n", and again "y", etc, for each click on the div button.
thanks!!
See http://jsfiddle.net/wnw0oskd/
You can change it as an attribute, as the alert shows onlick of the element is not 'taskIsDone(1,2,"y");' but a function.
document.getElementById('project_' + project_id + '_task_' + task_id + '_is_done').setAttribute("onclick", "taskIsDone(" + project_id + "," + task_id + ",'n'");"
If you print the current value of document.getElementById('your_div').onclick you realize tht it's not the string taskIsDone(1,2,"y");, but it's:
function (event) {
taskIsDone(1,2,"y");
}
So, if you want to change the value of .onclick, assign it one such a function.
Example (toggle the value of .onclick between a() and b()):
<html>
<body>
<script>
function a() {
alert("a()");
document.getElementById('foo').onclick = function(event) {
b();
}
}
function b() {
alert("b()");
document.getElementById('foo').onclick = function(event) {
a();
}
}
</script>
<div id="foo" style="position: absolute; left: 20px; top: 20px; width: 200px; height: 50px; background-color: red;" onclick="a();">
</div>
</body>
</html>
to answer your question specifically, try setting "onlick" to a function instead of a string:
if (is_done=='n'){
var elmt = document.getElementById(...); // arguments omitted for brevity
elmt.onclick = function(project_id, task_id) {
taskIsDone(project_id, task_id, 'y');
}
}
Having said that, though, there are better ways of doing this sort of thing. You could, for instance keep your state in a data attribute attached to the div:
<div id='project_1_task_2_is_done' data-done="no"
class='button_style_1_small' onClick='toggleDone(ev, 1, 2)'>
Is done?
</div>
<script type="text/javascript">
function toggleDone(ev, projectId, taskId) {
var elmt = ev.target
elmt.dataset.done = elmt.dataset.done == "yes" ? "no" : "yes"
}
</script>
See http://www.w3schools.com/tags/att_global_data.asp
Try this.
<div id='project_1_task_2_is_done'
class='button_style_1_small' >
Is done?
</div>
var check=false;
var button = document.getElementsByClassName('button_style_1_small');
button.click =function(){
if(check==false){
check=true;
taskIsDone(1,2,"n");
}else{
check=false;
taskIsDone(1,2,"y");
}
}
I use a Modernizr media query in JavaScript to change an element margin and add a class "small". My Modernizr media query doesn't work when I resize my browser, but when I refresh the page then it works. I know I can solve this problem using the jQuery $( window ).resize() function, but I want to solve it using a media query. Can any one tell me how I can solve this problem?
<html class="no-js">
<head>
<title>Foundation 5</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="modernizr.js"></script>
<script type="text/javascript">
$(document).ready(function() {
if (Modernizr.mq('(max-width: 767px)')) {
$("#secondary").addClass("small");
$("#secondary").css("margin", " 25px");
}
});
</script>
<style type="text/css">
#primary {
width: 300px;
height: 200px;
background-color: black;
}
#secondary {
margin: 0 auto;
width: 250px;
height: 150px;
background-color: white;
position: absolute;
}
</style>
</head>
<body>
<div id="primary">
<div id="secondary">
</div>
</div>
</body>
</html>
At the moment it runs once only (on page load), so of course it only changes when you refresh the page.
Solution: You need your code to run onload and when the browser/window resizes. :
e.g.
<script type="text/javascript">
var mod = function(){
if (Modernizr.mq('(max-width: 767px)')) {
$("#secondary").addClass("small").css("margin", " 25px");
} else {
// Clear the settings etc
$("#secondary").removeClass("small").css("margin", ""); // <<< whatever the other margin value should be goes here
}
}
// Shortcut for $(document).ready()
$(function() {
// Call on every window resize
$(window).resize(mod);
// Call once on initial load
mod();
});
</script>
Option 2
A common alternative I now use is to simply trigger a window resize event at the end of the onload (e.g. after the handler is connected).
<script type="text/javascript">
// Shortcut for $(document).ready()
$(function() {
// Call on every window resize
$(window).resize(function(){
if (Modernizr.mq('(max-width: 767px)')) {
$("#secondary").addClass("small").css("margin", " 25px");
} else {
// Clear the settings etc
$("#secondary").removeClass("small").css("margin", ""); // <<< whatever the other margin value should be goes here
}
}).resize(); // Cause an initial widow.resize to occur
});
</script>
Simple JSFiddle example: http://jsfiddle.net/TrueBlueAussie/zv12z7wy/
Great answer above in Option 2
Helped me immensely as I was having the same issue of not seeing my changes reflect on initial page resizes. Causing the initial window.resize saves the day.
Just to make the above solution in Option 2 a little cleaner I created a mediaQ variable which I store inside the if statement. This un clutters the if statement. I also store your #secondary id inside a variable.
$(window).resize(function(){
var mediaQ = Modernizr.mq('only screen and (max-width:767px)');
var secondaryId = $("#secondary");
// if mediaQ is true
if(mediaQ){
secondaryId.addClass("small");
secondaryId.css("margin", " 25px");
// if mediaQ is false
} else {
secondaryId.removeClass("small");
secondaryId.css("margin", "");
}
}).resize();
I am developing a page that will create many <div>s and appending them to a container <div>. I need to know which one of them is being clicked on. At first, I figured putting an eventListener on each one of them would be fine. However, I just read an article about using Smart Event Handlers here: Best Practices for Speeding Up Your Web Site. And it talks about using only 1 eventListener and figuring out which one of the elements the event originates from.
EDIT
I removed the previous example, here is a small example that is much closer to my target functionality and illustrates why it would be preferable to have one listener that dispatches what was clicked on. The main thing is that the <div> is what knows which index in an array needs to be grabbed for data. However, the data gets presented in <div>s that are inside the <div> that knows the array index and the event doesn't hook with him for some reason.
When you run it, you see that the log only lists the contact whenever the green "middle" <div> gets clicked but not the red "information" <div>s. How can I get the red information <div>s to trigger the listener as well without adding a zillion listeners?
JSFiddle
<!DOCTYPE html>
<html>
<head>
<title>Bubbling</title>
<meta charset="UTF-8">
<style>
div{
margin: 10px;
padding: 10px;
width: 200px;
}
#big{
background: #ccffcc;
}
.contactDiv{
background: #99ff99;
}
.nameDiv, .phoneDiv{
background: #ff9999;
}
#log{
background: #ccccff;
}
</style>
</head>
<body>
<div id="log">I log stuff</div>
<button id="adder">Add Some Div</button>
<!--Highest div that encloses multiple other div-->
<div id="big"></div>
<script>
var log = document.getElementById("log"),
adderButton = document.getElementById("adder"),
bigDiv = document.getElementById("big"),
numDivsToMake = 100,
i, contactDiv, nameDiv, phoneDiv,
contacts = [];
for (i = 0; i < numDivsToMake; i++) {
contacts.push({
name: "Bob-" + i,
phone: "555-1234"
});
}
// Make more divs whenever we click the super button
adderButton.addEventListener("click", function() {
// Don't make more
adderButton.setAttribute("disabled");
// Make the divs with data
for (i = 0; i < numDivsToMake; i++) {
// Make name and number divs
contactDiv = document.createElement("div");
nameDiv = document.createElement("div");
phoneDiv = document.createElement("div");
// Add classes
contactDiv.className = "contactDiv";
nameDiv.className = "nameDiv";
phoneDiv.className = "phoneDiv";
// Set their values
nameDiv.innerHTML = contacts[i].name;
phoneDiv.innerHTML = contacts[i].phone;
// Set the container to know how to get back to the data in the array
contactDiv.setAttribute("data-contactId", i);
// Add them to the dom
bigDiv.appendChild(contactDiv);
contactDiv.appendChild(nameDiv);
contactDiv.appendChild(phoneDiv);
}
});
// Make smart handler
bigDiv.addEventListener("click", function(e) {
// Get whether the element has the attribute we want
var att = e.target.getAttribute("data-contactId");
// Say if it does or not
if (att) {
log.innerHTML = "You clicked: " +
contacts[att].name + " " +
contacts[att].phone;
}
else {
console.log("No attribute");
}
});
</script>
</body>
</html>
I think I understand what you're doing. The event delegation you've set up in the fiddle seems to work well. The data attribute you want to select could easily be selected inside your click handler by querying the DOM. Instead of looking at the source of the click if you know you always want the data, just do another document.getElementById to retrieve your data. I think you're trying to collapse two steps into one in a way that won't work with your design.
I've spent the better part of a day tracking down a problem I've been having with jQuery animation. There appear to be issues with applying jQuery.animate() to anchor elements, or to child elements inside of anchor elements, at least with regard to movement animations. I've boiled the problem down to a fairly simple example which illustrates the problem:
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script>
var foo = {};
function TestMove(newx, newy) {
this.newx = newx;
this.newy = newy;
}
TestMove.prototype = {
movex:function () {
$("#newsec").animate({left: this.newx + "px"});
},
movey:function () {
$("#newsec").animate({top: this.newy + "px"});
}
}
function bar() {
foo[1].movex();
foo[1].movey();
}
function init() {
foo[1] = new TestMove(200,200);
}
</script>
</head>
<body onload="init()">
<a href="" style="position: relative;">
<div style="position: relative; height: 50px; width: 50px; background-color: red;" id="newsec" onclick="bar()"></div>
</a>
</body>
</html>
The animation doesn't work, regardless of whether I put the id attribute and onclick event handler call in the <a> tag or in the <div> within it. If, on the other hand,I remove the <a> element tags altogether, the animation works as expected on the <div> element.
Does anyone have any idea why this happens?
The issue is almost moot, since I can easily do with <div> elements in the working page what I could also do with <a> elements. In the working code (much more complex) I'm using event.preventDefault() on the anchor elements so that linking and other actions are driven by explicit event handlers and this can be done from a <div> just as well. I believe I can even change the pointer icon when one does a mouseover on the <div> so that it mimics a true anchor in this regard as well.
It's because the browser is going to the anchor prior to the animation being put in place. There are plugins to get around these sort of issues, or you can put together your own.
http://briangonzalez.org/arbitrary-anchor
Example of a simple implementation:
jQuery.fn.anchorAnimate = function(settings) {
settings = jQuery.extend({
speed : 1100
}, settings);
return this.each(function(){
var caller = this
$(caller).click(function (event) {
event.preventDefault()
var locationHref = window.location.href
var elementClick = $(caller).attr("href")
var destination = $(elementClick).offset().top;
$("html:not(:animated),body:not(:animated)").animate({ scrollTop: destination}, settings.speed, function() {
window.location.hash = elementClick
});
return false;
})
})
}