How can I implement up and down voting?
Yes, JavaScript is involved. There are two parts: Hooking up a handler for the click events on the voting "buttons", and sending data to the server.
Hooking up the events is well-covered elsewhere and I won't go into it here. (For instance, I cover it in this answer.)
Sending the data to the server, you can use ajax. On any browser that isn't completely obsolete, you can use XMLHttpRequest
:
var xhr = new XMLHttpRequest();
xhr.open("POST", "/path/to/server/destination");
xhr.onreadystatechange = handleStateChange;
xhr.send("id=" + encodeURIComponent(id) +
"&user=" + encodeURIComponent(userId) +
"&vote=up");
function handleStateChange() {
if (xhr.readyState === 4) {
// POST complete
if (xhr.status === 200) {
// POST complete and we have response, check it
if (xhr.responseText !== "ok") { // Or whatever you want it to be
// Report problem
}
} else {
// Ajax failed, handle/report problem
}
}
}
On more up-to-date browsers you can use fetch
:
var body = new FormData();
body.append("id", id);
body.append("user", userId);
body.append("vote", "up");
fetch("/path/to/server/destination", {
method: "POST",
body: body
})
.then(function(res) {
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
return res.text(); // or `res.json()` if you return JSON
})
.then(function(data) {
if (data !== "ok") { // Or whatever
// Report problem
}
})
.catch(function(error) {
// Ajax failed, handle/report problem
});
Just for fun, here's a complete example:
HTML:
<div class="article" data-itemid="427">
<a href="voteup" class="vote up" >Up</a>
<a href="votedown" class="vote down">Down</a>
<!-- ...the contents of the item... -->
</div>
JavaScript:
document.addEventListener("click", function(event) {
// Regardless of the below, we handle the event, so "consume" it
event.stopPropagation();
event.preventDefault();
// Get the anchor element
var voteLink = event.target.closest("a.vote");
if (!voteLink) {
// Didn't find one, bail
return;
}
// See if the vote has already been done or is in progress
if (voteLink.classList.contains("done") || voteLink.classList.contains("inprogress")) {
// Ignore the click, possibly tell the user why
return;
}
// Get the vote type
var voteType = voteLink.classList.contains("up") ? "up" : "down";
// Get the item we"re voting on
var item = voteLink.closest(".article");
// Get its ID
var itemId = item.getAttribute("data-itemid");
// If we didn"t get an ID...
if (!itemId) {
// ...report error
return;
}
// Mark "in progress" and initiate the vote; action continues
// in our callbacks below
voteLink.classList.add("inprogress");
var body = new FormData();
body.append("itemId", itemId);
body.append("voteType", voteType);
fetch("savevote", {
method: "POST",
body: body
})
.then(function(res) {
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
return res.text(); // or `res.json()` if you return JSON
})
.then(function(data) {
if (data === "ok") { // Or whatever
voteLink.classList.add("done");
} else {
// Report an error to the user, the server couldn"t record the vote
}
})
.catch(function(error) {
// Ajax failed, handle/report problem
})
.finally(function() {
// Not in progress anymore
voteLink.classList.remove("inprogress");
});
});
Some notes:
- The code above is written in ES5 but you could use ES2015+ features in most modern browsers (or by transpiling with tools like Babel).
- I've put an
href
on the links (which StackOverflow doesn't have), so that if JavaScript is disabled, we can fall back to going to a page where we let the user vote using a form submission or something. Also, links withhref
are treated specially by browsers (tab targets, etc.), so this is useful for accessibility. (To really do that, I'd probably have to put the article ID in thehref
as well.) - I'm storing the ID of the item we're voting on in a
data-
attribute. - We find the item to vote on by locating the "closest" article to the button that was clicked. The DOM's
closest
function starts with the element and examines that element to see if it fits the given CSS selector, then if not looks at its parent, then its parent, etc., until it finds a match. So the vote buttons are associated with the article by containment; the article being voted on contains the voting buttons. - If you were rooting the event handler in an element deeper in the page (rather than the document level), you'd probably follow the
closest
check with acontains
check to ensure that the element the handler is attached to contains the element that was found (in case it was found in an ancestor element instead). That would be!voteLink || !this.contains(voteLink)
above (instead of just!voteLink
). - I'm using POST because the call changes server state, so GET is inappropriate
You can use jquery to do it. Just apply click eventlisteners on the up/down arrow which will send the data to your server using ajax.
The script on your server will validate the incoming data and update count in the DB. Then you can send a response back giving the updated count of ups/downs
Note : You'll also have to take into account that a user can only like or dislike once. You can either handle it at server side / or for simplicity store it in cookie.
Check out Ajax.