I have many these links in my template
Add
And need click to the link and get certain data-slug. I try use JS and get all links so.
var add_to_cart = document.getElementsByClassName('add_to_cart');
After I try for
for(var i = 0; i < add_to_cart.length; i++) {
product_slug = add_to_cart[i].getAttribute('data-slug')
add_to_cart[i].onclick = function() {
console.log(product_slug)
}
}
And after click always console.log have last data-slug latest link in my template. How to fix it and get the data-slug of the item I clicked on. Help me, please.
Your for loop does not work because the value of product_slug is set when the loop runs.
Because product_slug is a var you overwrite it each time the loop runs. And when the click listener is called product_slug will have the same value it had when the loop ran for the last time.
var creates a variable with function scope, it is defined in the scope of the function.
let or const creates a variable with block scope, it is defined in the scope of the block (inside the curly braces { }).
Read more about scope.
So you want to use let.
for(var i = 0; i < add_to_cart.length; i++) {
let product_slug = add_to_cart[i].getAttribute('data-slug')
add_to_cart[i].onclick = function() {
console.log(product_slug)
}
}
You are defining a variable that is written n times during a for loop, and n events that will display the value. That's why you have the same output each time.
What you can do is get the current target of your onclick event, and then display it's data-slug attribute
your for loop would look like this :
var add_to_cart = document.getElementsByClassName('add_to_cart');
for(var i = 0; i < add_to_cart.length; i++) {
add_to_cart[i].onclick = function(evt) {
console.log(evt.target.getAttribute('data-slug'));
}
}
You want to use the target of the click. That's the .target attribute of the first argument to the event handler.
For example, the following code assigns an event handler to all the add_to_cart elements on the page and logs the slug to the console when they're clicked:
let add_to_cart_buttons = document.querySelectorAll('.add_to_cart');
add_to_cart_buttons.forEach(function(node) {
node.addEventListener('click', (e) => console.log(e.target.dataset.slug));
});
EDIT: Apologies, I misread your second code block when I first wrote this. Your use of document.getElementsByClassName is correct.
Related
In my Ember app, I have the following code to remove checked rows dynamically
removeRow: function(row){
// Some logic to remove one row at a time
var numberContainers = this.get('containers').length;
for (var i = 0; i < numberContainers; i++){
}
}
this.get('containers').forEach(function(container){
if (container.applicable === true){
var row = {};
self.send("removeRow", row);
}
})
Now user can select multiple rows & try removing them. With the above code, the "removeRow" action is invoked only once i.e. the forEach loop somehow gets broken or control does not come back again after the "removeRow" action is invoked once.
How can I handle this scenario ?
A couple of things were mentioned in comments. You have some different smells and errors:
While you're iterating on an array, you are trying to modify it.
Instead of calling a function of the component, you are sending an action to call it.
It is not clear var row = {}; self.send("removeRow", row); If a container is suitable why we are removing a newly created row object?
Anyway, my suggestions are:
Separate the array modification and iteration
Define removeRow as a function, if you need to use it as an action handler also define an action handler and delegate the whole responsibilty to the function.
Here is a sample code:
removeRow: function(row){
// Some logic to remove one row at a time
var numberContainers = this.get('containers').length;
for (var i = 0; i < numberContainers; i++){
}
}
otherFunction(){
let applicables = this.get('containers').filterBy('applicable', true);
applicables.forEach(a=>{let row={};this.removeRow(row);});
}
actions:{
removeRow(row){
this.removeRow(row);
}
}
I'm a javascript/dom noob coming from mainly Python and Clojure, and the semantics of referring to things in the dom are really confusing me.
Take the following extract from some code which I just wrote into a chrome extension to identify a subset of pages matching criteria defined in testdownloadpage and then hang eventlisteners on pdf download links to intercept and scrape them.:
function hanglisteners() {
if (testdownloadpage(url)) {
var links = document.getElementsByTagName("a");
for (i = 0, len = links.length; i < len; i++) {
var l = links[i];
if (testpdf(l.href)) {
l.addEventListener("click", function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
getlink(this.href);
}, false);
};
};
};
};
Originally, I had the call to getlink at the bottom reading getlink(l.href), and I thought (evidently naively) that what would happen with that code would be that it would loop through every matching link, and slap a listener on each that called getlink on the url for that link. But that didn't work at all. I tried replacing l.href with this.href just as a guess, and it started working.
I'm not sure why this.href worked when l.href did not. My best guess is that the javascript interpreter doesn't evaluate l.href in an addEventListener call until some point later, when l has changed to something else(??). But I'm not sure why that should be, or how to know when javascript does evaluate arguments to a function call and when it doesn't...
And now I'm worrying about the higher-up call to testpdf(l.href). That function is meant to check to make sure a link is a pdf download link before hanging the listener on it. But is that going to evaluate l.href within the loop, and hence correctly evaluate each link? Or is that also going to evaluate at some point after the loop, and should I be using this.href instead?
Can some kind soul please explain the underlying semantics to me, so that I don't have to guess whether referring to the loop variable or referring to this is correct? Thanks!
EDIT/ADDITION:
The consensus seems to be that my problem is a (clearly well-known) issue where inner functions in loops are victims of scope leaks s.t. when they're called, unless the inner function closes over all the variables it uses, they end up bound to the last element of the loop. But: why does this code then work?
var links = document.getElementsByTagName("a");
for (i = 0, len = links.length; i < len; i++) {
let a = links[i];
a.addEventListener("click", function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
console.log(a.href);
});
};
<html>
<head>
<title>silly test</title>
</head>
<body>
<p>
Link 1
link2
link 3
</p>
</body>
</html>
Based on those answers, I'd expect clicking on every link to log "link 3," but they actually log the correct/expected naive results...
The problem arises because the loop variable has already changed by the time the listener actually fires. You should be able to see this with the example below.
function setup(){
var root = document.getElementById('root');
var info = document.createElement('div');
for (var i = 0; i < 5; i++){
// create a button
var btn = document.createElement('button');
// set up the parameters
btn.innerHTML = i
btn.addEventListener('click', function(event){
info.innerHTML = ('i = ' + i + ', but the button name is ' + event.target.innerHTML);
})
// add the button to the dom
root.appendChild(btn)
}
var info = document.createElement('div');
info.innerHTML = 'The Value of i is ' + i
root.appendChild(info)
}
// run our setup function
setup();
<div id="root">
</div>
so what you need to do is save off a copy of l for later use. addEventListener is automatically storing the element in this for exactly this reason. Another very good way to do this, however if you don't want to mess with this for some reason is to use a generator function (a function that creates a function).
for instance:
function clickListener(i, info){
return function(event){
info.innerHTML = ('i = ' + i + ', but the button name is ' + event.target.innerHTML);
}
}
This way we can put the variables i and info into scope for the listener function which is called later without the values changing.
putting this code into our example above, we get the following snippet
function clickListener(i, info){
return function(event){
info.innerHTML = ('i = ' + i + ', but the button name is ' + event.target.innerHTML);
}
}
function setup(){
var root = document.getElementById('root');
var info = document.createElement('div');
for (var i = 0; i < 5; i++){
// create a button
var btn = document.createElement('button');
// set up the parameters
btn.innerHTML = i
// use the generator to get a click handler
btn.addEventListener('click', clickListener(i, info))
// add the button to the dom
root.appendChild(btn)
}
info.innerHTML = 'The Value of i is ' + i
root.appendChild(info)
}
// run our setup function
setup();
<div id="root">
</div>
EDIT: Answering revised question
In your second iteration of code, you use the let keyword introduced with ES6. This keyword is specifically designed to give javascript variables more traditional block scope. In our case it makes the variable scoped to the specific iteration of the loop. A similar example can be seen on MDN. If you can support ES6 in your application/build system, using let is a great way to solve the problem.
When you're in the function of event listeners, this is bound to the DOM element for which the event listener is for. Imagine the following code:
var links = document.getElementsByTagName("a");
for (i = 0, len = links.length; i < len; i++) {
let a = links[i];
a.addEventListener("click", function(e) {
console.log(this === a); // => true
})
}
The two would be identical, so you can use either whenever
I am very new to programming and I am wondering if anyone can help me with this.
I am trying to make a pop up page.
I set variables for each click area which I set each area with div and placed with css.
Also for each pop up image which I put div id on each image on html and set display = "none" on css.
I want to make a function that shows one image on touchend and hide other images at the same time.
Could you help me with my code?
var pop = new Array("pop1","pop2","pop3","pop4","pop5","pop6");
var clickArea = new Array("click1","click2","click3","click4","click5","click6");
function diplay(click,show,hide){
click.addEventListner("touchend",function(){
show.style.display = "block";
hide.style.display = "none";
});
};
display("click[0]","pop[0]","pop[1,2,3,4,5]");
There are a few different issues with your code.
You used strings instead of the actual code structure references while calling display. I see that you mean for these to reference the element ids, but you must first get the element with document.getElementById(...) or jQuery's $("#...").
In the pop and clickArea arrays, you used strings, which do not have the .style object. You need to reference the elements themselves.
Your code structure is not designed to handle arrays.
You need to define the addEventListener before you need the function handler to be called. You do not want this every time.
The click argument in the display function is redundant, as it is never called.
You are using jQuery. You should have stated this! (but you're forgiven) :)
You can't reach into arrays with the syntax arrayName[#,#,#].
You misspelled "display". Whoops!
The arrays are redundant, since the code needed to be restructured.
First, in order to address Point #4, we need this code to run when the DOM has finished loading:
var clickArea = new Array("click1","click2","click3","click4","click5","click6");
clickArea.each(function(id){
$("#"+id)[0].addEventListener("touchend", display);
});
Next, we need to fix the issues with your code. They're explained above.
var pop = new Array("pop1","pop2","pop3","pop4","pop5","pop6");
function display(event){
var indx = Number(event.target.id.split(/\D/i).join(""));
$("#pop"+indx)[0].style.display = "block";
pop.each(function(ide) {
if (ide.split(/\D/i).join("") != indx-1) {
$("#"+ide)[0].style.display = "none";
}
});
};
Otherwise, great job! All of us started out like this, and believe in you! Keep it up!
P.S. You can set arrays like this [ ? , ? , ? , ? ] instead of this new Array( ? , ? , ? , ? ).
Here is an example using for loops instead of methods of Arrays etc
Start off by defining everything you can
var popup_id = ["pop1", "pop2", "pop3", "pop4", "pop5", "pop6"],
popup_elm = [], // for referencing the elements later
area_id = ["click1", "click2", "click3", "click4", "click5", "click6"],
area_elm = [], // for referencing the elements later
i; // for the for -- don't forget to var everything you use
// a function to hide all popups
function hideAll() {
var i; // it's own var means it doesn't change anything outside the function
for (i = 0; i < popup_elm.length; ++i) {
popup_elm.style.display = 'none';
}
}
// a function to attach listeners
function listenTouch(area, popup) {
area.addEventListener('touchend', function () {
hideAll();
popup.style.display = 'block';
});
// we did this in it's own function to give us a "closure"
}
Finally we are ready do begin linking it all to the DOM, I'm assuming the following code is executed after the elements exist in the browser
// setup - get Elements from ids, attach listeners
for (i = 0; i < popup_id.length; ++i) {
popup_elm[i] = document.getElementById(popup_id[i]);
area_elm[i] = document.getElementById(area_id[i]);
listenTouch(area_elm[i], popup_elm[i]);
}
You cannot treat strings as html elements.
Assuming there are elements with click area ids in the page, you may do something like (once the document is ready).
var popEls = pop.map(function (id) { return document.getElementById(id) });
clickArea.forEach(function (id) {
var clickAreaEl = document.getElementById(id);
clickAreaEl.addEventListener('click', onClickAreaClick);
});
function onClickAreaClick() {
var clickAreaNum = +this.id.match(/\d+$/)[0],
popIndex = clickAreaNum - 1;
popEls.forEach(function (popEl) {
popEl.style.display = 'none';
});
popEls[popIndex].style.display = 'block';
}
function loadGroups() {
new Ajax.Request("https://www.xyz.com/groups.json", {
method: 'get',
onSuccess : function(transport) {
var response = JSON.parse(transport.responseText);
for (var i=0; i<response.length; i++) {
var hostname = response[i];
var tr = new Element("tr", { id: hostname });
var link = new Element("a");
link.setAttribute("href", id=hostname);
link.innerHTML = hostname;
# PROBLEM
# This onClick always sends response[-1] instead of response[i]
link.onclick = function() { loadHosts(hostname); return false; };
var td1 = new Element("td", { className: hostname });
link.update(hostname);
td1.appendChild(link);
tr.appendChild(td1);
table.appendChild(tr);
}
}
});
}
response = ['john', 'carl', 'jia', 'alex']
My goal is in this case 4 links should be displayed on the page and whenever a link is clicked the corresponding value should be passed to function - loadHosts. But the problem is always the last value gets sent as parameter to loadHosts function. The display of links is fine it is just the click on these links passing always the last element of array. I also tried loadHosts(link.href) , loadHosts(link.id)
Your problem is with the way closures work, you have a click handler inside a for loop that is using a variable defined inside the for loop. By the time the click handler runs, your variable has a different value than when you attached the handler because the loop has finished and it now has a different value.
Try something like this:
for (var i=0; i<response.length; i++) {
var hostname = response[i];
//...
link.onclick = (function(hn) {
return function() { loadHosts(hn); return false; };
})(hostname);
}
This uses a immediately invoked function expression (IIFE) to copy the value of hostname to the a new variable hn during the loop. Now when the handler is executed, it will be bound to the correct value.
Here's a blog post that explains the problem (and solution).
Also note that for loops don't define a variable scope. Variables defined in your for loop belong to the enclosing function scope. So hostname exists outside of your for loop which is why it holds the value of the last cycle through the loop in your click handler rather than the value when you attached the handler. It also explains why link.href doesn't work either. The variable link is attached to the enclosing function scope too rather than limited to the loop.
you should really go with the solution of #MattBurland but if you like, you could also tie the variable to the object itself.
link.hostname = response[i];
link.onclick = function() { loadHosts(this.hostname); return false; };
see simple example here: http://jsfiddle.net/xeyqs/
I am writing an extension for Google Chrome in HTML/Javascript. I am trying to use a global variable to pass information between two functions, however even if I assign my variable in one function it hasn't changed when I read it from the other function.
var type = 0; //define global variable
window.onload=function(){onCreated()}; //set onCreated function to run after loading HTML
function onCreated()
{
chrome.history.search({'text': ''},function(historyItems){gotHistory(historyItems)});//search for historyItems and then pass them to the gotHistory function
}
function gotHistory(historyItems)
{
var idcount=0;//used to increment the ids of each new element added
for(var count=0; count < historyItems.length; count++)//go through each history item
{
chrome.history.getVisits({'url':historyItems[count].url}, function(visitItems){gotVisits(visitItems)}); //search for visitItems for the url and pass the results to gotVisists function (atm all this function does is assign the global variable to =3)
var body = document.getElementById("outputid");//find the body of the HTML
var newt = document.createElement("p");//create a new element
newt.setAttribute("id","url"+idcount);//give it a unique id
newt.innerHTML = historyItems[count].title;//set the text to say the title of the url
if(type != 0)//if the other function was successful, type=3 and the text should be green
{
newt.style.color="green";
}
body.appendChild(newt);//add the new element to the body
idcount++;
}
}
function gotVisits(visitItems)
{
//assign the global variable to be 3
type = 3;
}
But, the elements are NEVER green. They should always be green. This means that in the function gotVisits, type is not being successfully assigned to 3.
Can anyone explain what is going on here?
Cheers,
Matt
p.s I realise the gotVisits function is useless here really, but I'm using it to demonstrate a point. In reality I will use it to pass back useful information to
Rather than:
var type = 0;
Try:
window.type = 0;
Optionally you can also use a closure such as:
(function() {
var type = 0;
var type = 0; //define global variable
window.onload=function(){onCreated()}; //set onCreated function to run after loading HTML
function onCreated()
{
chrome.history.search({'text': ''},function(historyItems){gotHistory(historyItems)});//search for historyItems and then pass them to the gotHistory function
}
function gotHistory(historyItems)
{
var idcount=0;//used to increment the ids of each new element added
for(var count=0; count < historyItems.length; count++)//go through each history item
{
chrome.history.getVisits({'url':historyItems[count].url}, function(visitItems){gotVisits(visitItems)}); //search for visitItems for the url and pass the results to gotVisists function (atm all this function does is assign the global variable to =3)
var body = document.getElementById("outputid");//find the body of the HTML
var newt = document.createElement("p");//create a new element
newt.setAttribute("id","url"+idcount);//give it a unique id
newt.innerHTML = historyItems[count].title;//set the text to say the title of the url
if(type != 0)//if the other function was successful, type=3 and the text should be green
{
newt.style.color="green";
}
body.appendChild(newt);//add the new element to the body
idcount++;
}
}
function gotVisits(visitItems)
{
//assign the global variable to be 3
type = 3;
}
})();
This saves you from polluting the window object, which you should avoid doing anyhow and allows the inner functions access to the type variable.
It should do what you want.
Also the outer function wrapper for your window.onload is redundant, just do:
window.onload = onCreated;
It looks like chrome.history.getVisits executes the callback asynchronously, so you first try to check that variable, and it gets updated later. You can verify this with a pair of console.log messages.
Move the rest of the code inside the callback, so it gets executed at the right time.