I'm writing a simple chrome extension that lists all the open tabs, I have this code on it
function changeTab(tabID){
chrome.tabs.update(tabID,{active:false})
}
chrome.windows.getCurrent({populate: true},function (window){
list = document.getElementById('open-tabs');
for (var i = 0; i < window.tabs.length; i++)
{
var li = document.createElement('li');
var element = document.createElement('a');
element.setAttribute('href','#');
element.innerHTML = window.tabs[i].title;
element.addEventListener("click",function(){
changeTab(window.tabs[i].id);
},false);
li.appendChild(element);
list.appendChild(li);
}
});
It lists the open tabs, but doesn't seem to add the onClick event, when I checked the chrome console I get this
Why is not adding the event correctly?
--edit--
Adding the html if it helps
<!doctype html>
<html>
<head>
<title>Count Me</title>
<link rel="stylesheet" href="popup.css" type="text/css">
<script src="popup.js"></script>
</head>
<body>
<div id="main">
<ul id="open-tabs"></ul>
</div>
</body>
</html>
Edit2
I tried using the sugestion given on an answer using the .bind(this,i) but still doesn't work, I added console.log() to see what's happening, and it seems it's not going inside the addEventListener heres the code with the log calls:
function changeTab(tabID){
chrome.tabs.update(tabID,{active:false})
}
chrome.windows.getCurrent({populate: true},function (window){
list = document.getElementById('open-tabs');
for (var i = 0; i < window.tabs.length; i++)
{
var li = document.createElement('li');
var element = document.createElement('a');
element.setAttribute('href','#');
element.innerHTML = window.tabs[i].title;
console.log('before');
console.log(window.tabs[i].id);
element.addEventListener("click",function(iVal){
console.log('inside');
changeTab(window.tabs[iVal].id);
}.bind(this,i),false);
console.log('after');
console.log(window.tabs[i].id);
li.appendChild(element);
list.appendChild(li);
}
});
As you can see I have a Before and After console.log() as well as inside the addEventListener and it doesn't seem to call anything inside the addEventListener as you can see here:
It's calling the console.log inside the addEventListener but still isn't working
Try adding a closure around the function
(function(num) {
element.addEventListener("click",function(){
changeTab(window.tabs[num].id);
},false);
})(i)
The event will be executed a later stage when you click the element. So when the for loop is completed, i always points to last iterated value.
So enclosing it in an anonymous function creates a closure around the variable which will be available at the time the click event occurs.
Your function callback is happening in a context that does not recognize i..
You can bind the i value to function and by that make it work:
element.addEventListener("click",function(iVal){
changeTab(window.tabs[iVal].id);
}.bind(this,i),false);
Related
I have this click event attached to each button and when I click on each of them, it is printing the output meant for the third button. I'm not sure what is going on.
<!DOCTYPE html>
<html>
<head>
<title>JS Bin</title>
</head>
<body>
<button>test1</button>
<button>test2</button>
<button>test3</button>
</body>
<script>
var nodes = document.getElementsByTagName('button');
for (var i = 0; i < nodes.length; i++) {
nodes[i].addEventListener('click', function() {
console.log('You clicked element #' + i);
});
}
</script>
</html>
when I click on any of the buttons, it is printing
"You clicked element #3"
Simple solution to this:
<!DOCTYPE html>
<html>
<head>
<title>JS Bin</title>
</head>
<body>
<button>test1</button>
<button>test2</button>
<button>test3</button>
</body>
<script>
var nodes = document.getElementsByTagName('button');
console.log(nodes);
for (var i = 0; i < nodes.length; i++) {
//converted click function into an IIFE so it executes then and there only
nodes[i].addEventListener('click', (function (j) {
return function () {
console.log('You clicked element #' + j);
}
})(i));
}
</script>
</html>
You should go through two concepts to understand this thing
1) Closures
2) Javascript is single-threaded and synchronous. So how does it handle events?
Here is what it is happening in your code:
==> for loop gets executed synchronously as it is part of javascript engine post which javascript handles event queue which is a FIFO (first in first out)
==> When for loop finished value of i is three which remains in memory until the function inside it executes
==> Each time it takes a value 3 and prints it.
When this button is listening to event, at that time the value of i is nodes.length -1 that is 2. Because loop has already finished it's execution and have set value of i to 2.
So it is consoling You clicked element #3.
Such issues arise because of scope & closure
Create an IIFE and pass the value of i.
Hope this snippet will be useful
var nodes = document.getElementsByTagName('button');
for (var i = 0; i < nodes.length; i++) {
(function(i){
nodes[i].addEventListener('click', function() {
console.log('You clicked element #' + i);
});
}(i))
}
Check this jsfiddle
This is other way using jQuery.
$("button").each(function(e) {
$(this).data("number", e);
}).bind("click", function(e) {
console.log("You clicked element #" + $(this).data("number"));
});
https://jsfiddle.net/ChaHyukIm/uxsqu70t/3/
This is the closure with function inside a loop issue.
JavaScript closure inside loops – simple practical example
Watch out for this!
Side note: questions around this issue are frequently asked in interviews to demonstrate proficiency with JS.
Below is my code for a simple text based game that i am trying to make but i cannot understand why the first time i call my function with a hyperlink 'link1', it works but when i add another link to my html document using javascript, and try to call another function onclick upon that link, it doesn't work. can somebody explain?
var getupvar = document.getElementById("attack");
getupvar.onclick = attack;
function attack() {
$('<p> Some text </p>').insertBefore("#placeHolder");
$('link2').insertBefore("#placeHolder");
}
var link2event = document.getElementById("defend");
link2event.onclick = defend;
function defend() {
alert("working now");
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="console">
<p id="startGameMessage"></p>
<div id="gameArea">
<p id="gameMessage">Some Text</p>
link1
<div id="placeHolder"></div>
</div>
</div>
When you link up your attack onclick, the element exists. But because #defend does not exist on the dom when you run var link2event = document.getElementById("defend");, your onclick never gets set.
Instead try:
var getupvar = document.getElementById("attack");
getupvar.onclick = attack;
function attack(){
$('<p> Some text </p>').insertBefore("#placeHolder");
$('link2').insertBefore("#placeHolder");
var link2event = document.getElementById("defend");
link2event.onclick= defend;
}
function defend(){
alert("working now");
}
jsfiddle https://jsfiddle.net/fv9mpbm6/
This will get your code working how you wish. If you don't do so, your code cannot work because the two lines added to the attack() function will be executed when you call the .js file in your html document as a script and not as a function. This means that the method getElementById("defend") will not find anything, because when you initialize the page, you cannot find any element with this id, you create it when you click on the link.
But do note that if you click attack more than once, your code will break because there will be more than one defend element.
When creating dynamic content, never use ids for handlers! Use classes instead
function attack() {
$('link2').insertBefore("#placeHolder");
}
Also, to ensure proper handling for elements that may not exist yet, leverage jquery's on functionality
$('body').on('click', '.defend', function() {
alert("working now")
})
it is simple, just put the two lines of code
var link2event = document.getElementById("defend");
link2event.onclick= defend;
inside the attack() function, at the end,and it works. This is the new attack() function:
function attack(){
$('<p> Some text </p>').insertBefore("#placeHolder");
$('link2').insertBefore("#placeHolder");
var link2event = document.getElementById("defend");
link2event.onclick= defend;}
If you don't do so this code cannot works because these two lines will be executed when you call the .js file in your html document as a script and not as a function. This means that the method getElementById("defend") will not find nothing, because when you initialize the page, you cannot find any element with this id, you create it when you click on the link.
I hope that this helps!
Cheers!
Try this, I am not sure is this what you expect
var getupvar = document.getElementById("attack"), i=0;
getupvar.onclick = attack;
function attack() {
$('<p> Some text </p>').insertBefore("#placeHolder");
$('link2').insertBefore("#placeHolder");
var link2event = document.getElementById("defend"+i);
link2event.onclick = defend;
i++;
}
function defend() {
alert("working now");
}
I am struggling with the following problem.
I have made a memorygame with javascrpt for school.It all works fine, but my teacher told me that i can not have on line of javascript in my HTML, like this :
HTML :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="styles.css">
<title>Memory spelen</title>
</head>
<body>
<script type="text/javascript" src="javascript.js"></script>
<div id="memory_board"></div>
<script>newBoard();</script>
</body>
</html>
The newBoard() is applied to the memory_board div. How can i take this little piece of script out of my HTML file and place it in my js file, to still function properly.
Thanks in advance
Inside of your javascript.js put this
window.onload = function {
// content of your script
var newBoard = function(){
// the new updated newBoard() function from below
}
// other parts of your script
if(window.location.href == 'your-url') {
// now, after the newBoard() has been updated
// the next to lines are not needed
// var board = document.getElementById('memory_board');
// board.innerHTML = newBoard();
// just call the function
newBoard();
}
};
UPDATE
I just took a look at your old fiddle and I changed your newBoard function to this
function newBoard(){
tiles_flipped = 0;
var output = '';
memory_array.memory_tile_shuffle();
for(var i = 0; i < memory_array.length; i++){
var div = document.createElement('DIV');
div.id = "tile_" + i;
(function(div,i){
div.onclick = function() {
memoryFlipTile(this, memory_array[i]);
};
}(div,i));
document.getElementById('memory_board').appendChild(div);
}
}
Check the working fiddle.
try to put it like this in external js file
$(document).ready(function(){
newBoard();
});
It looks that you need to call newBoard() method on onload event of memory_board div , You can do this in following ways:
<div id="memory_board" onload="javascript:newBoard()" ></div> // use onload event of memory_board
you can use onload function on the javascript. It will call the function when all the HTML tag is loaded on the screen.
I'm having some trouble trying to get a fairly simple popupper to work. The idea is that the parent should open a popup window and then append a div in it.
The relevant parts of the code:
parent.html:
var childWindow;
function togglePref() {
childWindow = window.open("popup.html", "prefPopup", "width=200,height=320");
}
function loadPopupElements() {
var prefElements = document.getElementById("prefBrd").cloneNode(true);
var childDoc = childWindow.document;
var childLink = document.createElement("link");
childLink.setAttribute("href", "pop.css");
childLink.setAttribute("rel", "stylesheet");
childLink.setAttribute("type", "text/css");
childDoc.head.appendChild(childLink);
childDoc.body.appendChild(prefElements);
}
popup.html:
<head>
</head>
<body onload="opener.loadPopupElements();">
</body>
This works fine with Safari and Chrome, but for some reason IE refuses to append anything.
Ok, I managed to work around the problem with a uglyish solution using innerHTML. Apparently, as Hemlock mentioned, IE doesn't support appending children from a another document. Some suggested to take a look at the importNode() method but I seemed to have no luck with it either.
So, the workaround goes as follows:
parent.html:
var childWindow;
function togglePref() {
childWindow = window.open("popup.html", "prefPopup", "width=200,height=320");
}
function loadPopupElements() {
var prefElements = document.getElementById("prefBrd");
var childDoc = childWindow.document;
childDoc.body.innerHTML = prefElements.innerHTML;
}
popup.html:
<head>
<link href="pop.css" rel="stylesheet" type="text/css">
</head>
<body onload="loadElements();">
</body>
<script type="text/javascript">
function loadElements() {
opener.loadPopupElements();
}
</script>
This seems quite a nasty way to go because in my case the #prefBrd contains some input elements with dynamically set values, so in order for the popup.html to grab them, it has to do a bit of iteration at the end of the loadElements() function, which wouldn't have been necessary using appendChild.
I'm working on a web page where I'm making an AJAX call that returns a chunk of HTML like:
<div>
<!-- some html -->
<script type="text/javascript">
/** some javascript */
</script>
</div>
I'm inserting the whole thing into the DOM, but the JavaScript isn't being run. Is there a way to run it?
Some details: I can't control what's in the script block (so I can't change it to a function that could be called), I just need the whole block to be executed. I can't call eval on the response because the JavaScript is within a larger block of HTML. I could do some kind of regex to separate out the JavaScript and then call eval on it, but that's pretty yucky. Anyone know a better way?
Script added by setting the innerHTML property of an element doesn't get executed. Try creating a new div, setting its innerHTML, then adding this new div to the DOM. For example:
<html>
<head>
<script type='text/javascript'>
function addScript()
{
var str = "<script>alert('i am here');<\/script>";
var newdiv = document.createElement('div');
newdiv.innerHTML = str;
document.getElementById('target').appendChild(newdiv);
}
</script>
</head>
<body>
<input type="button" value="add script" onclick="addScript()"/>
<div>hello world</div>
<div id="target"></div>
</body>
</html>
You don't have to use regex if you are using the response to fill a div or something. You can use getElementsByTagName.
div.innerHTML = response;
var scripts = div.getElementsByTagName('script');
for (var ix = 0; ix < scripts.length; ix++) {
eval(scripts[ix].text);
}
While the accepted answer from #Ed. does not work on current versions of Firefox, Google Chrome or Safari browsers I managed to adept his example in order to invoke dynamically added scripts.
The necessary changes are only in the way scripts are added to DOM. Instead of adding it as innerHTML the trick was to create a new script element and add the actual script content as innerHTML to the created element and then append the script element to the actual target.
<html>
<head>
<script type='text/javascript'>
function addScript()
{
var newdiv = document.createElement('div');
var p = document.createElement('p');
p.innerHTML = "Dynamically added text";
newdiv.appendChild(p);
var script = document.createElement('script');
script.innerHTML = "alert('i am here');";
newdiv.appendChild(script);
document.getElementById('target').appendChild(newdiv);
}
</script>
</head>
<body>
<input type="button" value="add script" onclick="addScript()"/>
<div>hello world</div>
<div id="target"></div>
</body>
</html>
This works for me on Firefox 42, Google Chrome 48 and Safari 9.0.3
An alternative is to not just dump the return from the Ajax call into the DOM using InnerHTML.
You can insert each node dynamically, and then the script will run.
Otherwise, the browser just assumes you are inserting a text node, and ignores the scripts.
Using Eval is rather evil, because it requires another instance of the Javascript VM to be fired up and JIT the passed string.
The best method would probably be to identify and eval the contents of the script block directly via the DOM.
I would be careful though.. if you are implementing this to overcome a limitation of some off site call you are opening up a security hole.
Whatever you implement could be exploited for XSS.
You can use one of the popular Ajax libraries that do this for you natively. I like Prototype. You can just add evalScripts:true as part of your Ajax call and it happens automagically.
For those who like to live dangerously:
// This is the HTML with script element(s) we want to inject
var newHtml = '<b>After!</b>\r\n<' +
'script>\r\nchangeColorEverySecond();\r\n</' +
'script>';
// Here, we separate the script tags from the non-script HTML
var parts = separateScriptElementsFromHtml(newHtml);
function separateScriptElementsFromHtml(fullHtmlString) {
var inner = [], outer = [], m;
while (m = /<script>([^<]*)<\/script>/gi.exec(fullHtmlString)) {
outer.push(fullHtmlString.substr(0, m.index));
inner.push(m[1]);
fullHtmlString = fullHtmlString.substr(m.index + m[0].length);
}
outer.push(fullHtmlString);
return {
html: outer.join('\r\n'),
js: inner.join('\r\n')
};
}
// In 2 seconds, inject the new HTML, and run the JS
setTimeout(function(){
document.getElementsByTagName('P')[0].innerHTML = parts.html;
eval(parts.js);
}, 2000);
// This is the function inside the script tag
function changeColorEverySecond() {
document.getElementsByTagName('p')[0].style.color = getRandomColor();
setTimeout(changeColorEverySecond, 1000);
}
// Here is a fun fun function copied from:
// https://stackoverflow.com/a/1484514/2413712
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
<p>Before</p>