What I'm trying to do is use the querySelectorAll() function to create a loop where I can target each new <li/> element that I've created using the getInput() function that I created (which works fine). I wanted to use console.log() to test this, so that when I click on each <li/> item it confirms that I have done so in the console. This querySelectorAll() method is new to me and I'm also new at learning Javascript in general. So, any advice and explanation would be very helpful. thanks.
function getInput() {
var liValue = theForm.elements.input.value;
var liNew = document.createElement("li");
liNew.innerHTML += liValue;
var list = document.getElementById("ulList");
list.appendChild(liNew);
deleteLi;
}
function deleteLi() {
var handlers = document.querySelectorAll("li");
for (var i = 0; i < handlers.lenghth; i++) {
handlers[i].onclick = console.log("you got me");
}
}
Just add the click handler when you create the element. Running qSA as you have posted with select all li elements, even those you have already created.
function getInput() {
var liValue = theForm.elements.input.value;
var liNew = document.createElement("li");
liNew.onclick = function() { console.log("clicked!"); };
liNew.innerHTML += liValue;
var list = document.getElementById("ulList");
list.appendChild(liNew);
}
Related
JS Nooblord here, to give some context I have recently created my first JQuery based image slider to which I'm currently trying to generate a list of control buttons dynamically when the page loads.
I have succeeded thus far in creating the buttons but when it comes to writing the onclick function I'm having issues calling another function (with a parameter) inside a for loop.
I suck at explaining things but here is the code;
function addControls(){
var x = document.getElementById('slider').childElementCount;
for (var i = 0; i < x; i++) {
var ul = document.getElementById('slider-control');
var li = document.createElement("li");
var btn = document.createElement("Button");
btn.onclick = function() {
goto(i);
};
btn.appendChild(document.createTextNode(i + 1));
ul.appendChild(li);
li.appendChild(btn);
}
}
function goto(index){
alert(index);
}
Here is the JSFiddle preview.
What I expect is for each button to call the goto function with their respective position in the loop however every generated button with the onclick function uses the last index from the loop (4).
My initial thoughts are that the buttons are being rendered after the loops are finished and not within each iteration of the loop? also if anyone has any tips and alternatives for what I'm doing I would greatly appreciate that.
Thanks,
-Dodd
As commented on Mikelis Baltruks, you will have to use .bind.
You can use
goto.bind(null, i+1)
to map only index to it. If you wish to get the button as well, you can use
goto.bind(btn, i+1)
Sample JSFiddle
Bind
.bind is used to change the context of a function. Its syntax is
functionName.bind(context, argumentList);
This will create a reference of function with a newly binded context.
You can also use .apply for this task. Difference is, apply expects arguments as array and bind expect a comma separated list.
Note: both this function will just register events and not call it.
Reference
.bind
.apply
call() & apply() vs bind()
The problem is the reference to i.
for (var i = 0; i < x; i++) {
var btn = document.createElement("Button");
btn.onclick = function() {
goto(i);
// any variable reference will use the latest value
// so when `onclick` is actually run, the loop will have continued on to completion, with i == 4
};
}
You need a separate variable to reference for each onclick handler. You can do this by creating a closure:
function makeOnclick(i) {
// `i` is now a completely separate "variable",
// so it will not be updated while the loop continues running
return function() { goto(i); };
}
for (var i = 0; i < x; i++) {
var btn = document.createElement("Button");
btn.onclick = makeOnclick(i);
}
This can be done any number of ways, as others have shown. But this should explain why it's happening. Please ask any questions.
You need to create a closure in the loop, this should work:
var x = document.getElementById('slider').childElementCount;
for (var i = 0; i < x; i++) {
(function (i) {
var ul = document.getElementById('slider-control');
var li = document.createElement("li");
var btn = document.createElement("Button");
btn.onclick = function() {
goto(i);
};
btn.appendChild(document.createTextNode(i + 1));
ul.appendChild(li);
li.appendChild(btn);
})(i);
}
function goto(index) {
alert(index);
}
https://jsfiddle.net/g8qeq29e/6/
Or with ES6 let keyword;
function addControls(){
var x = document.getElementById('slider').childElementCount;
for (let i = 0; i < x; i++) {//change var to let here
var ul = document.getElementById('slider-control');
var li = document.createElement("li");
var btn = document.createElement("Button");
btn.onclick = function() {
goto(i);
};
btn.appendChild(document.createTextNode(i + 1));
ul.appendChild(li);
li.appendChild(btn);
}
}
function goto(index){
alert(index);
}
When I try the code referenced in SO #1, I get the console logging a blank string:
installChoices() {
var choices = this.game.page.options;
for (var i = 0; i < choices.length; i++) {
var choice = choices[i];
var choiceDiv = document.createElement("choice" + i);
choiceDiv.innerText = choice[0];
choiceDiv.onclick = function() {
console.log(this.id);
}
this.choicesContainer.appendChild(choiceDiv);
}
}
I want to bind to my class function clicked
installChoices() {
var choices = this.game.page.options;
for (var i = 0; i < choices.length; i++) {
var choice = choices[i];
var choiceDiv = document.createElement("choice" + i);
choiceDiv.innerText = choice[0];
choiceDiv.onclick = this.clicked;
this.choicesContainer.appendChild(choiceDiv);
}
}
clicked(e) {
console.log(e.parentNode); //this is undefined
console.log(e.srcElement);
}
But that shows undefined. When I log srcElement, I get the full element
<choice0>path 1</choice0>
I want to get just the div id when I click, so I can parse that and do logic.
I'd recommend the following approach, as it is the standard:
//assign the event
choiceDiv.addEventListener('click', clicked)
//define the listener
function clicked(event) {
console.log(event.currentTarget)
}
update:
I'm tempted to offer a fix to your code, because I don't think you're achieving what are you trying to actually do:
function installChoices() {
var choices = this.game.page.options;
for (var i = 0; i < choices.length; i++) {
var choice = choices[i];
var choiceDiv = document.createElement("div");
choiceDiv.id = "choice" + i;
choiceDiv.innerText = choice[0];
choiceDiv.addEventListener("click", clicked);
this.choicesContainer.appendChild(choiceDiv);
}
}
function clicked(ev) {
console.log(ev.currentTarget.id); //this will log "choice0"
}
Your "clicked" function are receiving an Event rather than a HTML Element. This is the default behavior when an onClick event triggered.
The click event have a srcElement property, indicating the source element the click event occurred upon. This event object have no parentNode property.
Use e.srcElement.parentNode instead.
BTW, in the SO #1 example, it assign "showIt(this)" to onClick, so browser pass "this", the target element rather than the event object, to the onClick function.
I am dynamically creating buttons in javascript, and each button is supposed to select their corresponding node in a jsTree. The problem is that all the buttons select the last node in their respective branch. The jsTree is working as it is supposed to, so that is not the issue.
The relevant code:
var children = node.children;
for (var i = 0; i < children.length; i++) {
var childNode = myTree.get_node(children[i]);
var myButton = document.createElement("div");
myButton.className = 'imageButton';
myButton.onclick = function () {
myTree.deselect_all([true]);
myTree.select_node(childNode);
};
When I am building the buttons, the childNode is the correct node.
The problem is when the onClick fires, the childNode is the the last element in children[]. Which is then selected.
Any suggestions?
I think the problem lies in closure. The link explains it very well. Anyway, I'd try this and see what happens:
function clickEvHandlerClosure(childnode) {
return function () {
myTree.deselect_all([true]);
myTree.select_node(childnode);
};
}
// your code
....
// In the loop, replace this:
//myButton.onclick = function () {
// myTree.deselect_all([true]);
// myTree.select_node(childNode);
// };
// for this:
myButton.onclick = clickEvHandlerClosure(childnode);
Could work.
I used the bind function to make it work.
myButton.onclick = function (index) {
var myTree = $('#jstree').jstree();
var node = myTree.get_selected(true)[0];
var children = node.children;
var childNode = myTree.get_node(children[index]);
myTree.deselect_all([true]);
myTree.select_node(childNode);
}.bind(this, i);
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
consider:
for (var i in somecollection){
var a = document.createElement('a');
a.onclick = new function(){callSomeMethod(somecollection[i]);};
...
}
At runtime, all 'a' elements wind up calling callSomeMethod with the same parameter value (the last element in 'somecollection'.
I have a hack of a solution as follows:
for (var i in somecollection){
var a = document.createElement('a');
var X = 'callSomeMethod(\''+somecollection[i]+'\');';
a.setAttribute('onclick', X);
...
}
But this forces me to exclude 'callSOmeMethod' from mangling/compression when I minify my JS files. How can I make each 'a' element's click handler callSomeMethod with a different parameter without hardcoding the function name in a string?
The closest my search found is the accepted answer in pass string parameter in an onclick function
but I do not know how to create a 'scope bubble' .
Thanks...
You could use the power of javascript ! juste add custom property to the object.
Here is a example:
var somecollection= [ 'a','b','c','d'];
function callSomeMethod() {
var i = this.__index; // retreive here your data
if (i) {
alert(somecollection[i]);
}
}
function init() {
for (var i in somecollection){
var a = document.createElement('a');
a.onclick = callSomeMethod;
a.innerHTML = "click for #" + i;
a.__index = i; // custom property to capture index or any data you want to pass
document.body.appendChild(a);
}
}
You can use a closure, it will capture the value of i
for (var i in somecollection){
var a = document.createElement('a');
a.onclick = (function(index) {
return function () {
callSomeMethod(someCollection[index])
};
})(i);
...
}
That way, the correct value of index will be available when the function is called, but it won't be called until the onClick event fires.
Interesting approach below. I also found the following which works exactly as I want so I thought to share here:
function attachSomeMethodClickHandler(a, value){
function functionX(){callSomeMethod(value);};
a.addEventListener('click', functionX);
}
:
:
for(var i in someCollection){
var a = document.createElement('a');
attachSomeMethodClickHandler(a, someCollection[i]);
:
}
Don't use inline bindings but instead try using event delegation:
Bind an event to the anchors parent and check the target once it's clicked,
this way, you're not limited to the amount of elements which are created,
and don't have to bind the event again if you'll create new ones later on.
Then pass the anchors index instead of a parameter.
var dataSource = ["dog", "cat", "horse"];
var container = document.getElementById("container");
function index(el) {
var parent = el.parentNode;
for(i = 0;i < parent.childNodes.length;i++) {
if(parent.childNodes[i] == el) {
return i;
}
}
}
container.addEventListener("click", function (e) {
e.preventDefault();
var idx = index(e.target);
alert("Index: " + idx + " value: " + dataSource[idx]);
});
for (i = 0; i < dataSource.length; i++) {
var data = dataSource[i];
var a = document.createElement("a");
var text = document.createTextNode(data);
a.href = "http://someurl.com?id=" + data;
a.appendChild(text);
container.appendChild(a);
}
Here's the fiddle: http://jsfiddle.net/RZw8e/2/
I am building a small app which captures mouse clicks. I wrote the prototype in jQuery but, since it is a small app focusing on speed, embedding jQuery to use just one function would be an overkill.
I tried to adapt this example from JavaScriptKit:
document.getElementById("alphanumeric").onkeypress=function(e){
//blah..blah..blah..
}
but it didn't work when I tried this:
document.getElementsByTagName("x").onclick
What am I doing wrong?
Say you have a list of p tags you would like to capture the click for the <p> tag:
var p = document.getElementsByTagName("p");
for (var i = 0; i < p.length; i++) {
p[i].onclick = function() {
alert("p is clicked and the id is " + this.id);
}
}
Check out an example here for more clarity:
http://jsbin.com/onaci/
In your example you are using getElementsByTagName() method, which returns you an array of DOM elements. You could iterate that array and assign the onclick handler to each element, for example:
var clickHandler = function() {
alert('clicked!');
}
var elements = document.getElementsByTagName('div'); // All divs
for (var i = 0; i < elements.length; i++) {
elements[i].onclick = clickHandler;
}
it looks a little bit like you miss more than just the click function of jQuery. You also miss jquery's selector engine, chaining, and automatic iteration across collections of objects. With a bit more effort you can minimally reproduce some of those things as well.
var myClickCapture = function (selector) {
var method, name,iterator;
if(selector.substr(0,1) === "#") {
method = "getElementById";
name = selector.substr(1);
iterator = function(fn) { fn(document[method](name)); };
} else {
method = "getElementsByTagName";
name = selector;
iterator = function(fn) {
var i,c = document[method](name);
for(i=0;i<c.length;i++){
fn(c[i]);
};
};
myClickCapture.click = function (fn){
iterator(function(e){
e.onclick=fn;
})
}
return myClickCapture;
}
I haven't tested the code, but in theory, it gets you something like this:
myClickCapture("x").click(function(e){ alert("element clicked") });
Hopefully this gives you a sense of the sorts of things jquery is doing under the covers.
document.getElementsByTagName("x")
returns an array of elements having the tagname 'x'.
You have to right event for each element in the returned array.