1

I have multiple card generated from a loop which share the same functionality. One of them is to Like and Dislike buttons. It's someone's code, I was asked to remove the click event per card once the user click on Like/Dislike button. I don't know how to remove/disable the click event when one choice is picked as it might disable other cards buttons.

I updated the original code for more clarity

function contentLike_Dislike(contentid, islike) {
  if (islike === "true") {
    var likeP = $("#p-like-" + contentid).text();
    $("#p-like-" + contentid).text(parseInt(likeP) + 1);

    $(".fa-thumbs-up").click(function () {
      $(this).removeClass("far").addClass("fa"); 
    });
  } else {
    var dislikeP = $("#p-dislike-" + contentid).text();
    $("#p-dislike-" + contentid).text(parseInt(dislikeP) + 1);

    $(".fa-thumbs-down").click(function () {
      $(this).removeClass("far").addClass("fa"); 
    });
  }
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>

<div class="col-lg-6 d-flex justify-content-lg-end">
  <a href="javascript:void(0)"
     onclick="contentLike_Dislike('21785', 'true')"
     class="text-navy-custom font-r fs-small me-3 d-flex"
  >
    <i class="far align-self-center fa-thumbs-up me-2 align-self-center"
       aria-hidden="true">
    </i>
    <p id="p-like-21785"
       class="mb-0 align-self-center d-none"
       tabindex="0">
      0
    </p>
  </a>
  <a href="javascript:void(0)"
     onclick="contentLike_Dislike('21786', 'false')"
     class="text-navy-custom font-r fs-small me-3 d-flex"
  >
    <i class="far align-self-center fa-thumbs-down me-2 align-self-center"
       aria-hidden="true">
    </i>
    <p id="p-dislike-21786"
       class="mb-0 align-self-center d-none"
       tabindex="0"
    >
      0
    </p>
  </a>
</div>
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • 1
    Can you elaborate more on what you want to archive? – Selaka Nanayakkara Jul 19 '23 at 14:52
  • I want to disable both button's event if one event is triggered. So if I click Like, I will send the value to the server and disable the event on both buttons. Now these events are shared in many containers that have the like/dislike buttons – Neron Godfather Jul 19 '23 at 14:57
  • I don’t understand why removing the onclick attribute for an element would affect the onclick events for other elements. – A Haworth Jul 19 '23 at 14:58
  • @AHaworth so how can I get it right? – Neron Godfather Jul 19 '23 at 15:01
  • The event does not occur in multiple places. Just remove the onclick attribute on the element in question. – A Haworth Jul 19 '23 at 15:03
  • Cards are generated through a for loop and each contains the two buttons, it is not a static code where I can just remove the event. I need another event to remove events on both buttons per card – Neron Godfather Jul 19 '23 at 15:13
  • Are multiple cards displayed on the same page or is it just a single card per page? Your code as is doesn't seem to have a way to distinguish multiple cards. – imvain2 Jul 19 '23 at 15:14
  • @imvain2 yes, they are multiple card on the same page. The function also has an id as a parameter – Neron Godfather Jul 19 '23 at 15:24
  • You aren’t trying to remove an event. You are trying to remove an event handler which can be done in this case by removing the onclick attribute. – A Haworth Jul 19 '23 at 15:29
  • @AHaworth that exactly what I need to do but not sure how. Sorry I am learning as I go. – Neron Godfather Jul 19 '23 at 15:33
  • I’m stuck on an iOS device so cannot create a code snippet to help. Have you looked at how to set an attribute? – A Haworth Jul 19 '23 at 15:39
  • @AHaworth I know how to set an attribute. Do you think I should remove the onclick attribute inside the same function? – Neron Godfather Jul 19 '23 at 18:49
  • Isn’t that what you need (removing the onclick)? – A Haworth Jul 19 '23 at 22:59
  • 1
    @NeronGodfather ... The OP is encouraged to not only learn but also to [vote](https://stackoverflow.com/help/someone-answers) on or/and [accept](https://stackoverflow.com/help/accepted-answer) answers which are helpful to the OP. – Peter Seliger Aug 03 '23 at 08:16

3 Answers3

0

Normally I would recommend an overhaul that involved using an event handler instead of inline click handler, but it seems like there is a bunch of these buttons on the same page that will need to be fixed.

So here is a simple solution, have a variable outside of the function that is initially set to false. Then in your function, check to see if its false and at the end set it to true. I have console.logs just to show what is happening when clicked.

UPDATE: Now that the OP has updated their question and comments with more information, I have updated my answer by storing the IDs in an array.

let liked = []

function contentLike_Dislike(id,islike) {
  const hasLiked = liked.includes(id)
  if (islike === "true" && !hasLiked) console.log("like")
  else if (islike === "false" && !hasLiked) console.log("dislike")
  if(hasLiked)console.log("already liked")
  liked.push(id);
}
<button onclick="contentLike_Dislike(1,'true')">Like</button>
<button onclick="contentLike_Dislike(1,'false')">Dislike</button>
imvain2
  • 15,480
  • 1
  • 16
  • 21
  • By using a global variable you disable all of the like and dislike buttons on the page. – Eolo Jul 19 '23 at 15:30
  • @Eolo when I posted that, OP didn't mention that there were multiple cards with different buttons. They didn't even mention that the function also had an ID passed with it. – imvain2 Jul 19 '23 at 15:47
0

I propose an approach based on event delegation.

The advantage comes not only with getting rid of the OP's inline scripting, but also with the registering and handling the event at exactly a single root-element for each voting-set (or card-set) where each root-element does enclose the elements which actually do trigger the to be handled event. Thus, it also allows code-reuse for more than just one voting- or card-set.

A clean approach would allow a handler-function to retrieve all necessary information from the elements that are related to an event. One way of achieving it is the usage of both, some data-* global attributes and its HTMLElement counterpart, the dataset property.

Within the handler-function one also would remove this very handler itself from the element it has been before registered to.

The markup of the next provided implementation resembles the OP's latest changes on the OP's provided HTML-code in structure and functionality. Of cause class-names can be added again as needed; there is just no reason for introducing any kind of id attributes. The markup already before was structured enough in order to not rely on any ids.

function handleVote({ target, currentTarget }) {
  // - always assure the intended target especially in case
  //   of such a target does contain (nested) child elements.
  target = target.closest('[data-vote-value]');

  if (target) {
    // - in case of a valid, intended target remove the event
    //   handler immediately from the delegate/current target.
    currentTarget.removeEventListener('click', handleVote);

    const elmCount = target.querySelector('[data-vote-count]');
    const elmIcon = target.querySelector('.icon');

    // - read, sanitize and increment the count specific
    //   `dataset` value and update it wherever necessary.
    const count = parseInt(elmCount.dataset.voteCount ?? 0, 10) + 1;

    elmCount.dataset.voteCount = count;
    elmCount.textContent = count;

    // - update the icon specific class names accordingly.
    elmIcon.classList.add('fa');
    elmIcon.classList.remove('far');
  }
}

document
  .querySelectorAll('[data-voting]')
  .forEach(rootNode =>
    rootNode.addEventListener('click', handleVote)
  );
[data-voting] { margin: 10px; }
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>

<div data-voting>
  <button data-vote-value="like">
    <span>Like</span>
    <span data-vote-count="0">0</span>
    <i class="icon far fa-thumbs-up" aria-hidden="true"></i>
  </button>
  <button data-vote-value="dislike">
    <span>Dislike</span>
    <span data-vote-count="0">0</span>
    <i class="icon far fa-thumbs-down" aria-hidden="true"></i>
  </button>
</div>
<div data-voting>
  <button data-vote-value="like">
    <span>Like</span>
    <span data-vote-count="0">0</span>
    <i class="icon far fa-thumbs-up" aria-hidden="true"></i>
  </button>
  <button data-vote-value="dislike">
    <span>Dislike</span>
    <span data-vote-count="0">0</span>
    <i class="icon far fa-thumbs-down" aria-hidden="true"></i>
  </button>
</div>
<div data-voting>
  <button data-vote-value="like">
    <span>Like</span>
    <span data-vote-count="0">0</span>
    <i class="icon far fa-thumbs-up" aria-hidden="true"></i>
  </button>
  <button data-vote-value="dislike">
    <span>Dislike</span>
    <span data-vote-count="0">0</span>
    <i class="icon far fa-thumbs-down" aria-hidden="true"></i>
  </button>
</div>

A comparison of the above pure DOM-Api implementation with the below jQuery-based counterpart, hopefully leads to ones conclusion that jQuery is obsolete for just DOM-query and event-handling tasks ...

function handleVote({ target, currentTarget }) {
  const $target = $(target).closest('[data-vote-value]');

  if ($target.length >= 1) {
    $(currentTarget).off('click', handleVote);

    const $elmCount = $target.find('[data-vote-count]');
    const $elmIcon = $target.find('.icon');

    const count = parseInt($elmCount.data('voteCount') ?? 0, 10) + 1;

    $elmCount.data('voteCount', count)
    $elmCount.text(count);

    $elmIcon.addClass('fa').removeClass('far');
  }
}

$(() => {
  $('[data-voting]')
    .each((idx, rootNode) =>
      $(rootNode).on('click', handleVote)
    );
});
[data-voting] { margin: 10px; }
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>

<div data-voting>
  <button data-vote-value="like">
    <span>Like</span>
    <span data-vote-count="0">0</span>
    <i class="icon far fa-thumbs-up" aria-hidden="true"></i>
  </button>
  <button data-vote-value="dislike">
    <span>Dislike</span>
    <span data-vote-count="0">0</span>
    <i class="icon far fa-thumbs-down" aria-hidden="true"></i>
  </button>
</div>
<div data-voting>
  <button data-vote-value="like">
    <span>Like</span>
    <span data-vote-count="0">0</span>
    <i class="icon far fa-thumbs-up" aria-hidden="true"></i>
  </button>
  <button data-vote-value="dislike">
    <span>Dislike</span>
    <span data-vote-count="0">0</span>
    <i class="icon far fa-thumbs-down" aria-hidden="true"></i>
  </button>
</div>
<div data-voting>
  <button data-vote-value="like">
    <span>Like</span>
    <span data-vote-count="0">0</span>
    <i class="icon far fa-thumbs-up" aria-hidden="true"></i>
  </button>
  <button data-vote-value="dislike">
    <span>Dislike</span>
    <span data-vote-count="0">0</span>
    <i class="icon far fa-thumbs-down" aria-hidden="true"></i>
  </button>
</div>
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • 1
    That article by David Walsh is absolutely inaccurate - and not the way to tackle eventDelegation - which should follow this format: `Event.target.closest("someSelector")` - and your example suffers the same bug - in which to break the logic all it takes is to have a child element (as i.e: an icon) inside the button. How to properly handle delegation: [see this answer](https://stackoverflow.com/a/75007869/383904). Also, makes no sense to have a different ID for the "Dislike" button. – Roko C. Buljan Jul 19 '23 at 21:01
  • *1/2* Read: [this related answer](https://stackoverflow.com/a/14533243/383904) which I edited just now to add more details - so that I don't have to repeat myself every once in a while. *2/2* OK but why would the *Dislike*'s ID be different? Anyways... – Roko C. Buljan Jul 19 '23 at 23:04
  • (PS: Regarding MDN, I'll try to suggest, edit the article ASAP. Have seen over the years both partially good or wrong implementations and then people struggling with bugs as soon they slightly modify the markup. JavaScript code is not meant to break on such a natural and minor change. Also, going around that issue is confusing at best. Time to break this vicious circle of JS event delegation best practices.) – Roko C. Buljan Jul 19 '23 at 23:07
  • @RokoC.Buljan I tried to make the code shorter when I post. Yes, the IDs are the same. I updated my code with the original one. – Neron Godfather Jul 20 '23 at 07:24
  • @NeronGodfather ... I adapted the above example code according to your latest example code changes. – Peter Seliger Jul 20 '23 at 08:58
  • @RokoC.Buljan ... With the last edit I also applied the more failsafe solution of always assuring the correct target. – Peter Seliger Jul 20 '23 at 09:00
  • @PeterSeliger / RokoCBuljan, thank you all for the solutions provided. Somehow the solution that worked was by Eolo which did what I was looking for. But I really consider your solutions as they showed best practices. – Neron Godfather Jul 26 '23 at 08:15
-2

By using the parent element to select buttons, you can disable only those that are within the same parent. This is optimal for the situation where you have multiple cards on the same page.

function contentLike_Dislike(event, islike) {
    if (islike === 'true') {
      alert("like");
    } else {
      alert("dislike");
    }
    
    var buttons = event.target.parentNode.querySelectorAll("button");
    buttons.forEach((item) => {
        item.onclick = "";
    });
}
<div class="card">
  <button onclick="contentLike_Dislike(event, 'true')">Like</button>
  <button onclick="contentLike_Dislike(event, 'false')">Dislike</button>
</div>
Eolo
  • 69
  • 1
  • 7