7

I have multiple buttons (generated by php) for a shopping cart application:

<button class="addtocart" id="<?php echo $code; ?>"> 
    <span id="addtocartbutton">Add to cart</span>
</button>

I want to update my cart using a function:

function AddtoCart() {
    alert("Added!");
}

Later, I want to find the id ($code) created by the button which called it (not sure how to do that also, but maybe that's another question). And so I tried this:

document.getElementsByClassName("addtocart").addEventListener("click", AddtoCart());

But it doesn't work. It was working using an onclick, but I understand that the right way to do it by creating an EventListener. Also, I cannot use the on() function in jQuery, because I am forced to use jQuery Version 1.6 which does not have it.

I have looked at https://stackoverflow.com/a/25387857/989468 and I can't really assign it to the parent which is a p tag, because I obviously don't want the other elements in the p tag to be assigned this function.

Community
  • 1
  • 1
Chiwda
  • 1,233
  • 7
  • 30
  • 52

5 Answers5

3

While the answers given are correct, there is another way: Event Delegation

Attach the listener to a SINGLE thing, in this case the document body and then check to see what element was actually clicked on:

Warning: Typed on the fly: Untested

// Only needed *once* and items may be added or removed on the fly without
// having to add/remove event listeners.
document.body.addEventListener("click", addtoCart);

function addtoCart(event) {
    var target = event.target;

    while(target) {
      if (target.classList.contains('addtocart')) {
        break;
      }
      // Note: May want parentElement here instead.
      target = target.parentNode;
    }

    if (!target) {
       return;
    }

    var id = target.dataset.id;
    alert(id + " added!");
}
Community
  • 1
  • 1
Jeremy J Starcher
  • 23,369
  • 6
  • 54
  • 74
  • This looks very promising. If only I could understand it! :-) For example, why do you need the while? And why would there not be a target to the event (ref: if (!target)). Why not use "this"? – Chiwda Jan 31 '16 at 15:33
  • Read the link I included in the post, but short answer: Because `this` won't point to the element that you are expecting. IIRC, it points to the element that you actually clicked on, which may be *inside* the element you're actually checking for. I'm not able to test it right now, but you can add a `console.log` and watch what the value of `this` turns out to be. Delegation is a little harder to wrap one's head around, I'll admit, but if you're doing any kind of dynamic updating of the content, it is far better than adding/remove specific handlers. – Jeremy J Starcher Jan 31 '16 at 15:38
  • it gives an error, if you click anywhere on the page – Gavriel Jan 31 '16 at 16:09
  • It works with dataset removed, so _var id = target.id;_. When I did a watch in Firebug for target, it showed id but not dataset as a child expression. – Chiwda Feb 01 '16 at 16:33
  • The use of `dataset` came from the original question. This is a newish feature not present in all all browsers. `target.id` is a very different value from `target.dataset.id` https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset – Jeremy J Starcher Feb 01 '16 at 17:44
  • I tried it in a blank page and it works, but when I put it in my web page with other stuff in it, I get an error. On the click, it stops at the first line of the functions and the Firebug console shows _"ReferenceError: event is not defined"_ I tried changing the name of the function in case there was a function jQuery or jQuery-UI that conflicts, but that didn't help. Please Help! – Chiwda Feb 02 '16 at 09:59
  • 1
    I can't see your code... makes it hard to do anything buy guess, but this might help you: http://stackoverflow.com/questions/20522887/referenceerror-event-is-not-defined-error-in-firefox – Jeremy J Starcher Feb 02 '16 at 11:16
  • Perfect! I need to add the parameter to the function. I wonder why it worked in the other php file without it though. Here's the final code, you can post it as your answer: document.body.addEventListener("click", atoc); function atoc(e) { var target = e.target; while(target) { if (target.classList.contains('addtocart')) { break; } target = target.parentNode; } if (!target) { return; } var id = target.id; alert("id: " + id); } I'll mark it as the answer once you do that. Thanx much! – Chiwda Feb 02 '16 at 12:25
2

You should attach click event to every element with class addtocart, since getElementsByClassName() return an array of all objects with given class name so you could use for to loop through everyone of them and associate it with function you want to trigger on click (in my example this function called my_function), check example bellow :

var class_names= document.getElementsByClassName("addtocart");

for (var i = 0; i < class_names.length; i++) {
    class_names[i].addEventListener('click', my_function, false);
}

Hope this helps.


function my_function() {
     alert(this.id);
};

var class_names= document.getElementsByClassName("addtocart");

for (var i = 0; i < class_names.length; i++) {
    class_names[i].addEventListener('click', my_function, false);
}
<button class="addtocart" id="id_1">button 1</button>
<button class="addtocart" id="id_2">button 2</button>
<button class="addtocart" id="id_3">button 3</button>
<button class="addtocart" id="id_3">button 4</button>
Zakaria Acharki
  • 66,747
  • 15
  • 75
  • 101
  • I saw an answer similar to this, but the problem is that the ids are generated after the fact, i.e. when the page is loaded. So, I can't really associate a function with each in advance. – Chiwda Jan 31 '16 at 12:10
  • Yes i see, that why I'm using the `class` name in my answer to associate the click event, so don't matter if the ids generated automatically if the class name is there. – Zakaria Acharki Jan 31 '16 at 12:32
  • I like your solution, but I feel it shares once fundamental flaw with the other answers below. I really think it is inefficient code to have it loop through all the items in the array every time someone clicks an item in order to add it to the cart. Imagine a case where there are hundreds of items shown - that's a lot of overhead for each click! I would rather go back to the old and supposedly bad way of pointing to a function in the onclick html for the button. As in – Chiwda Jan 31 '16 at 14:46
  • Normally you should loop just for the first time, after that every time you add new element you could attach event to it directly, anyway do what you feel it's true, good luck, happy coding. – Zakaria Acharki Jan 31 '16 at 14:59
2

I'll show some of the errors you had in your code, then I'll show you how can you improve it so that you can achieve what you want, and I also show that it works with buttons dynamically added later:

First and foremost, you need to pass the function reference (it's name) to the addEventListener! You have called the function, and passed whatever it returned. Instead of:

document.getElementsByClassName("addtocart").addEventListener("click", AddtoCart());

It should've been:

document.getElementsByClassName("addtocart").addEventListener("click", AddtoCart);

Second: document.getElementsByClassName("addtocart") returns a NodeList, you can't operate on it, you need to operate on it's elements: document.getElementsByClassName("addtocart")[0], [1],....

Third, I would suggest you to use the data-... html attribute:

<button class="addtocart" id="addtocart" data-foo="<? echo $code; ?>">

This way you can pass even more data. Now you can get the $code as:

document.getElementById('addtocart').dataset.foo

// el: the button element
function AddtoCart(el) {
    // this is the id:
    var id = el.id;

    // and this is an example data attribute. You can have as many as you wish:
    var foo = el.dataset.foo;
    alert(id + " (" + foo + ") added!");
}

// Try add a div or something around the area where all the buttons
// will be placed. Even those that will be added dynamically.
// This optimizes it a lib, as every click inside that div will trigger
// onButtonClick()
document.getElementById("buttons").addEventListener("click", onButtonClick);

// this shows that even works when you dynamically add a button later
document.getElementById('add').onclick = addButton;
function addButton() {
    var s = document.createElement("span");
    s.text = "Add to cart";
    var b = document.createElement("button");
    b.innerHTML = 'Third <span class="addtocartbutton">Add to cart</span>';
    b.className = "addtocart";
    b.id="third";
    b.dataset.foo="trio";
    // note the new button has the same html structure, class
    // and it's added under #buttons div!
    document.getElementById("buttons").appendChild(b);
}


// this will gett triggered on every click on #buttons
function onButtonClick(event) {
    var el = event.target;
    if (el && el.parentNode && el.parentNode.classList.contains('addtocart')) {
      // call your original handler and pass the button that has the
      // id and the other datasets
      AddtoCart(el.parentNode);
    }
}
<div id="buttons">
  <button class="addtocart" id="first" data-foo="uno">First <span class="addtocartbutton">Add to cart</span></button>
  <button class="addtocart" id="second" data-foo="duo">Second <span class="addtocartbutton">Add to cart</span></button>
</div>


<button id="add">Add new button</button>
Gavriel
  • 18,880
  • 12
  • 68
  • 105
1
<html>
<head>
<script>
window.onload=function{
    var btn = document.getElementsByName("addtocartbtn")[0];
    btn.addEventListener("click", AddtoCart());
}

function AddtoCart() {
    alert("Added!");
}
</script>
</head>
<body >
<button class="addtocart" name ="addtocartbtn" id="<?php echo $code; ?>" > <span id="addtocartbutton">Add to cart</span></button>

</body>
</html>
Vasim Shaikh
  • 4,485
  • 2
  • 23
  • 52
1

Actually class in Javascript is for multiple selection you should provide index like an array.

 <button class="addtocart"> <span id="addtocartbutton">Add to cart</span></button>
        <script type="text/javascript">
        document.getElementsByClassName("addtocart")[0].addEventListener("click", AddtoCart);        
        function AddtoCart() {
            alert("Added!");
        }
        </script>

Also your second parameter was wrong don't use parentheses.

Applying parentheses means it will call the function automatically when loaded, and will not call the function after that.

Niklesh Raut
  • 34,013
  • 16
  • 75
  • 109
  • using parenthesis is not `wrong`. It means the function will be executed inline. You need to know when to use them and when not to use them. There are use cases where you need the inline invocation. :) – AdityaParab Jan 31 '16 at 11:47
  • @AdityaParab Yeah, You are right, applying parenthesis is not wrong but in this its wrong because I thought adding product in cart will done after clicking on add to cart not automatically. Although I have updated my ans and have added more info regarding parenthesis . – Niklesh Raut Jan 31 '16 at 11:59