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 id
s.
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>