Create a Picture Competition Website in Express JS, MEVN

Competition Detail Page

Now we need to create a page where we can display more details about a specific competition. For example, we can show a list of all voters on that page, all comments on competition, etc. First, go to your “competition-slider.ejs” and change line #44 from:

{{ competition.user1.name }} vs {{ competition.user2.name }}

To:

<a v-bind:href="baseUrl + '/competitionDetail/' + competition._id">
	{{ competition.user1.name }} vs {{ competition.user2.name }}
</a>

It will create a hyperlink to the competition detail route. It will also send the competition ID in the URL as a parameter. Now we need to create a GET route in “server.js” that will render the competition detail page:

app.route("/competitionDetail/:_id")
	.get(function (request, result) {
		const _id = request.params._id;
		result.render("competition-detail", {
			"_id": _id
		});
	});

It will get the competition ID from the URL and pass it on to the new page. Create a file named “competition-detail.ejs” inside your “views” folder. Following will be the code of this file:

<%- include ("includes/header") %>

<input type="hidden" id="_id" value="<%= _id %>" />

<div class="container margin-container" id="competitionDetailApp" style="margin-bottom: 50px;">
	<div class="row">
		<div class="col-md-12">
			<h1 class="text-center" v-if="competition != null">{{ competition.user1.name + " vs " + competition.user2.name }}</h1>
			<h1 class="text-center" v-if="competition == null">Loading...</h1>
		</div>
	</div>

	<div class="row" v-if="competition != null" style="margin-top: 40px; margin-bottom: 40px;">
        <div class="col-md-6">
            <table class="table table-bordered table-responsive">
                <tr>
                    <th class="text-center">
                        {{ competition.user1.name }}

                        ({{ competition.user1.voters.length }})
                    </th>
                </tr>

                <tr>
                    <td>
                        <img v-bind:src="baseUrl + '/' + competition.user1.picture" class="img-responsive" style="width: 100%;" />
                    </td>
                </tr>
            </table>
        </div>

        <div class="col-md-6">
            <table class="table table-bordered table-responsive">
                <tr>
                    <th class="text-center">
                        {{ competition.user2.name }}

                        ({{ competition.user2.voters.length }})
                    </th>
                </tr>

                <tr>
                    <td>
                        <img v-bind:src="baseUrl + '/' + competition.user2.picture" class="img-responsive" style="width: 100%;" />
                    </td>
                </tr>
            </table>
        </div>
    </div>

    <div class="row" v-if="competition != null">
    	<div class="col-md-12">
    		<p class="text-center" v-text="competition.tags"></p>
    	</div>
    </div>

    [all comments]

    [add a comment]
</div>

<script>
	var competitionDetailApp = new Vue({
		el: "#competitionDetailApp",
		data: {
			baseUrl: mainURL,
			id: document.getElementById("_id").value,
			competition: null
		},
		methods: {

			getCompetitionDetail: function () {
				var self = this;

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

				myApp.callAjax(this.baseUrl + "/competitionDetail/" + this._id, formData, function (response) {
                    // convert the JSON string into Javascript object
                    var response = JSON.parse(response);
                    // console.log(response);
 
                    // if the user is created, then redirect to login
                    if (response.status == "success") {
                    	self.competition = response.competition;
                    } else {
                    	swal("Error", response.message, "error");
                    }
				});
			}
		},
		mounted: function () {
			this.getCompetitionDetail();
		}
	});
</script>

<%- include ("includes/footer") %>

This will show both user’s names and pictures along with all the people who have voted for each user. This also shows the hashtags of competition (if there are any). This calls an AJAX to the server when the page loads. So we need to add a POST route too at our “/competitionDetail” route in “server.js”:

app.route("/competitionDetail/:_id")
	.get(function (request, result) {
		const _id = request.params._id;
		result.render("competition-detail", {
			"_id": _id
		});
	})
	.post(async function (request, result) {
		const accessToken = request.fields.accessToken;
		const _id = request.fields._id;

		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)
	    });
        // sort the comments in descending order,
        // so that the new comments appear at the top
        competition.comments = competition.comments.reverse();

	    result.json({
            "status": "success",
            "message": "Data has been fetched.",
            "competition": competition
        });
	});

This post route will first check if the user is logged in or not. Then it will fetch the competition data from Mongo DB. Refresh the page now and you will be able to see the competition details.