Create a Picture Competition Website in Express JS, MEVN

Vote on Competition

Votes
Votes

Now we need to have an ability for the users to vote on either user 1 or user 2. We will show a thumbs-up icon for both users. Users can vote on any one of the users. Once voted, he will not able to undo or vote for the other person too. So first we will show the vote button by checking if the logged-in user has already voted on that competition or not.

Show Vote Buttons

Create a new folder named “modules” at the root of your project. Inside this folder create a file named “functions.js”. In this file, we will create the functions which we will call multiple times in the app. Following will be the code of this file:

module.exports = {
	// check if the user has already voted on this competition
	// either on user 1 or on user 2
	competitionsWithUserHasVoted: function (competitions, user) {
		competitions.forEach(function (competition) {
	    	var hasVotedUser1 = false;
	    	var hasVotedUser2 = false;

	    	competition.user1.voters.forEach (function (voter) {
	    		if (voter._id.toString() == user._id.toString()) {
	    			hasVotedUser1 = true;
	    			return;
	    		}
	    	});
	    	competition.user2.voters.forEach (function (voter) {
	    		if (voter._id.toString() == user._id.toString()) {
	    			hasVotedUser2 = true;
	    			return;
	    		}
	    	});

	    	competition.hasVotedUser1 = hasVotedUser1;
	    	competition.hasVotedUser2 = hasVotedUser2;
	    });
	    return competitions;
	}
};

This will check if the user has already voted on this competition, either on user 1 or on user 2. Then we need to include this module at the top of our “server.js” file:

const functions = require("./modules/functions");

Then in your “server.js” we will call this function at the [check if user has already voted] section:

competitions = functions.competitionsWithUserHasVoted(competitions, user);

Now each competition object will have “hasVotedUser1” and “hasVotedUser2” boolean values that will be either true or false. Then in your “competitions-slider.ejs” replace the [votes buttons goes here] section with the following code:

<div style="left: 10px;" class="votes">
	<form method="POST" v-if="!competition.hasVotedUser1 && !competition.hasVotedUser2" v-bind:action="baseUrl + '/toggleVote'" v-on:submit.prevent="toggleVote">
		<input type="hidden" name="id" v-model="competition._id" />
		<input type="hidden" name="name" v-model="competition.user1.name" />
		<button type="submit" name="submit" class="btn-vote">
			<i class="fa fa-thumbs-up"></i>
		</button>
	</form>

	<div class="ribbon ribbon-top-left" v-if="competition.hasVotedUser1"><span>Voted</span></div>
</div>

<div style="right: 10px;" class="votes">
	<form method="POST" v-if="!competition.hasVotedUser1 && !competition.hasVotedUser2" v-bind:action="baseUrl + '/toggleVote'" v-on:submit.prevent="toggleVote">
		<input type="hidden" name="id" v-model="competition._id" />
		<input type="hidden" name="name" v-model="competition.user2.name" />
		<button type="submit" name="submit" class="btn-vote">
			<i class="fa fa-thumbs-up"></i>
		</button>
	</form>

	<div class="ribbon ribbon-top-right" v-if="competition.hasVotedUser2"><span>Voted</span></div>
</div>

This will show 2 votes button for each user separately. When they are clicked, they will call a function “toggleVote()”.

Save the Vote in Mongo DB

We need to create that function in our Javascript code to call an AJAX to the server to handle this request. As all the above HTML code is in “competitionApp” Vue JS app, so we need to create a method in “competitionApp” object in “footer.ejs” file:

methods: {

	toggleVote: function () {
		var form = event.target;
		var self = this;

		// disable the submit button and show "Loading..." text
        form.submit.setAttribute("disabled", "disabled");
        form.submit.innerHTML = "<i class='fa fa-spinner fa-spin'></i>";

        var formData = new FormData(form);
        formData.append("accessToken", localStorage.getItem(accessTokenKey));

		myApp.callAjax(form.getAttribute("action"), formData, function (response) {
            // convert the JSON string into Javascript object
            var response = JSON.parse(response);

            // enable the submit button
            form.submit.removeAttribute("disabled");
            form.submit.innerHTML = "<i class='fa fa-thumbs-up'></i>";

            // if the user is created, then redirect to login
            if (response.status == "success") {
            	const updateVoteButton = true;
            	self.onToggleVoted(form.id.value, response, updateVoteButton);
            } else {
            	swal("Error", response.message, "error");
            }
		});
	},

	// update the local competition array to show or hide the vote button
	onToggleVoted: function(competitionId, response, updateVoteButton) {
		this.competitions.forEach(function (competition) {
			if (competition._id == competitionId) {
				if (updateVoteButton) {
					competition.hasVotedUser1 = response.hasVotedUser1;
					competition.hasVotedUser2 = response.hasVotedUser2;
				}

				if (response.hasVotedUser1) {
					competition.user1.voters.push(response.insertObject);
				}
				if (response.hasVotedUser2) {
					competition.user2.voters.push(response.insertObject);
				}
			}
		});
	},
},

If you run the project now, you will see 2 thumbs-up icons with each user. On clicking will call an AJAX to the server. Now we need to create an API to handle this request. First, we need to create a function in “functions.js” that will check if the user has already voted on this competition or not:

isAlreadyVoted: function (competition, user) {
	var isVoted = false;
	competition.user1.voters.forEach(function (voter) {
		if (voter._id.toString() == user._id.toString()) {
			isVoted = true;
			return;
		}
	});

	competition.user2.voters.forEach(function (voter) {
		if (voter._id.toString() == user._id.toString()) {
			isVoted = true;
			return;
		}
	});
	return isVoted;
},

Then in “server.js” file create a POST method that will add the data in the database.

app.post("/toggleVote", async function (request, result) {
	const accessToken = request.fields.accessToken;
	const id = request.fields.id;
	const name = request.fields.name;

	var user = await db.collection("users").findOne({
        "accessToken": accessToken
    });
    if (user == null) {
        result.json({
            "status": "error",
            "message": "User has been logged out. Please login again."
        });
        return false;
    }

    var competition = await db.collection("competitions").findOne({
        "_id": ObjectId(id)
    });
    if (competition == null) {
        result.json({
            "status": "error",
            "message": "Competition not found."
        });
        return false;
    }

    const isVoted = functions.isAlreadyVoted(competition, user);
    if (isVoted) {
    	result.json({
            "status": "error",
            "message": "You have already voted on this competition."
        });
        return false;
    }

    var insertObject = {
    	"_id": user._id,
    	"name": user.name,
    	"email": user.email
    };

    var hasVotedUser1 = false;
    var hasVotedUser2 = false;

    if (name == competition.user1.name) {
    	hasVotedUser1 = true;

    	await db.collection("competitions").findOneAndUpdate({
	    	"_id": ObjectId(id)
	    }, {
	    	$push: {
	    		"user1.voters": insertObject
	    	}
	    });
    }
    
    if (name == competition.user2.name) {
    	hasVotedUser2 = true;

    	await db.collection("competitions").findOneAndUpdate({
	    	"_id": ObjectId(id)
	    }, {
	    	$push: {
	    		"user2.voters": insertObject
	    	}
	    });
    }

    result.json({
        "status": "success",
        "message": "Action has been performed.",
        "hasVotedUser1": hasVotedUser1,
        "hasVotedUser2": hasVotedUser2,
        "insertObject": insertObject
    });
});

Refresh the page now and you will see thumb-up buttons. Clicking either of them will increment the voter’s count and hide the voter button immediately. After voting, you can refresh the page again to see the updated counter. To create a beautiful ribbon if the user has already voted, simply include the following CSS file in your “header.ejs”:

<link rel="stylesheet" type="text/css" href="/ribbons.css" />

Refresh the page now and you will see a beautiful ribbon if you have already voted on a competition.