Create a Picture Competition Website in Express JS, MEVN

Search and Sort

Search and Sort
Search and Sort

Searching is required so users can search competition by name of either user. We are also adding hashtags to each competition for this purpose. Sorting is required so users can sort the competition by a number of votes and by time (latest to oldest and vice versa).

First, add the following 2 variables in “competitionApp” object inside “footer.ejs”:

sort: "",

And also in your “navApp” object add the following:

search: ""

Sorting

Go to your “home.ejs” and write the following code in the [sort goes here] section:

<div class="offset-md-6 col-md-3">
    <%- include ("includes/sort") %>
</div>

Now we need to create a new file named “sort.ejs” inside the “includes” folder. Following will be the content of this file:

<div class="form-group">
	<label>Sort</label>
	<select name="sort" class="form-control" v-model="sort" v-on:change="onSortChange">
		<option value="">None</option>
		<option value="highest_to_lowest">Highest to Lowest</option>
		<option value="lowest_to_highest">Lowest to Highest</option>
		<option value="oldest_to_newest">Oldest to Newest</option>
		<option value="newest_to_oldest">Newest to Oldest</option>
	</select>
</div>

<script>
	function onSortChange() {
		competitionApp.competitions.sort(function (a, b) {
			const totalAVoters = (a.user1.voters.length + a.user2.voters.length);
			const totalBVoters = (b.user1.voters.length + b.user2.voters.length);

			if (competitionApp.sort == "highest_to_lowest") {
				return (totalBVoters - totalAVoters);
			} else if (competitionApp.sort == "lowest_to_highest") {
				return (totalAVoters - totalBVoters);
			} else if (competitionApp.sort == "oldest_to_newest") {
				return (a.createdAt - b.createdAt);
			} else if (competitionApp.sort == "newest_to_oldest") {
				return (b.createdAt - a.createdAt);
			}
		});
	}
</script>

This will sort the local array. So the competitions you are seeing on the page will only sort them. It will not sort the entire competitions from the database, only the ones which are visible.

Search

We will create a simple search bar on the home page and when hit submits, will search the competition by user 1 or user 2 name and also from hashtags.

Go to your “header.ejs” and replace the [search goes here] section with the following code:

<li class="nav-item" style="margin-left: 10px;">
    <form method="GET" v-bind:action="baseUrl + '/search'">
        <div class="input-group mb-3" style="margin-bottom: 0px !important;">
            <input type="search" name="q" class="form-control" placeholder="Search" v-model="search" />
            <div class="input-group-append">
                <button class="btn btn-outline-secondary" type="submit">Search</button>
            </div>
        </div>
    </form>
</li>

Then we need to create a GET route in “server.js” that will display the search page.

app.route("/search")
    .get(function (request, result) {
        result.render("search");
    });

Then we need to create a “search.ejs” file in the views folder. Following will be the content of this file:

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

<div class="container margin-container" id="competitionApp">
	<%- include ("includes/competitions-slider") %>
	<%- include ("includes/load-more") %>
</div>

<script>
	function getSearchedData() {
		var btnLoadMore = document.getElementById("btn-loadmore");
		if (btnLoadMore != null) {
			btnLoadMore.setAttribute("disabled", "disabled");
			btnLoadMore.innerHTML = "Loading...";
		}

		var urlParams = new URLSearchParams(window.location.search);
		const q = urlParams.get("q");
		navApp.search = q;

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

		myApp.callAjax(navApp.baseUrl + "/search", formData, function (response) {
            // convert the JSON string into Javascript object
            var response = JSON.parse(response);
            // console.log(response);

            if (btnLoadMore != null) {
				btnLoadMore.removeAttribute("disabled");
				btnLoadMore.innerHTML = "Load More";
			}

            // if the user is created, then redirect to login
            if (response.status == "success") {
            	competitionApp.competitions = response.data;

            	// Incrementing the offset so you can get next records when that button is clicked
            	competitionApp.loadMore.startFrom = competitionApp.loadMore.startFrom + competitionApp.loadMore.limit;
            } else {
            	swal("Error", response.message, "error");
            }
		});
	}

	window.addEventListener("load", function () {
		getSearchedData();
	});
</script>

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

Once the page is fully loaded, it will call an AJAX to the server. So now we need to create a POST route in “server.js” that will handle this request.

app.route("/search")
    .get(function (request, result) {
        result.render("search");
    })
    .post(async function (request, result) {
        const q = request.fields.q;

        var data = await db.collection("competitions").find({
            $or: [{
                "user1.name": {
                    $regex: q,
                    $options: "$i"
                }
            }, {
                "user2.name": {
                    $regex: q,
                    $options: "$i"
                }
            }, {
                "tags": {
                    $regex: q,
                    $options: "$i"
                }
            }]
        })
            .sort({
                "createdAt": -1
            })
            .toArray();

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

Refresh the page now and you will see a search bar in the top navigation bar. Type anything and hit submit, it will redirect you to a new page. On this new page, you will see the competitions that have the user name matches with the search query. Or if the competition has those hashtags in it.