Tristan
Mongo Database Setup
One of the things I wanted to learn in this project is to work with MongoDB and Mongoose. I took it upon myself to dive into the Mongo docs and setup the basics for what became the CMS system.
I used RoboMongo to have a great interface to work with the database.
This allowed me to update documents, edit values and check inconsistencies on the fly.
To attach the database to the main application, I created a schema for the reviews that would be uploaded to the database.
var reviewsSchema = mongoose.Schema({
user: {
name: String,
email: String,
postDate: String
},
review: {
seriesName: String,
region: String,
startYear: String,
endYear: String,
genre: Array,
platform: Array,
period: Array,
persona: Array,
hobby: Array,
mood: Array,
ageRestriction: String,
seasons: String,
episodes: String,
duration: String,
reviewTitle: String,
reviewBody: String,
reviewRating: String,
reviewIntro: String,
producers: String,
actors: String,
imdbRating: String,
trailerURL: String,
imgURL: String,
reviewPlot: String
},
comments: Array
});
CMS System
During this project I focused on several features for the application, one of which would turn out to become the upload CMS.
The main concern Vrij Nederland had was if they would be able to upload reviews onto the platform and the option to moderate/edit said reviews.
I took it upon myself to work on this requirement and I have created, what could be considered, a very basic CMS system.
This system is not visible to the public user of the Seriewijzer product, but is available to all reviewers Vrij Nederland employs.
I sought to apply the course material into all my work, but not all features lend themselves to implementing it.

Features
This upload system has some handy features for the user:
- Reviewers can fill out a form with all the necessary information (as requested by Vrij Nederland themselves).

- Reviews are saved into the global Mongo database and all of the uploaded information is used in the main application, making it an essential part of the app.
- Users can navigate to a main overview that displays all the available reviews, see when they were last posted/updated and update them if needed.
- When a review is updated, a real-time notification is sent (via websockets) to all users currently in the main overview.

Real-time Web
The implementation of the notifications is done via Websockets. For that, I used the industry-standard Socket.io library.
module.exports = function(io) {
io.on('connection', function(socket) {
socket.emit('connection');
console.log('[Server] Connected to client');
socket.on('new update', function () {
console.log('Notification received');
io.sockets.emit('update');
// io.broadcast.emit('notify');
})
});
return router;
};
The server listens for incoming connections from the client and then emits a 'connection' event.
This results in both the client and the server to log a message saying "Connected to client/server', indicating a successful connection.
When the connection is made, the client-side JS will catch the post-request sent out when the form is submitted.
elements.form.addEventListener('submit', function(e) {
e.preventDefault(); // Prevents the form from submitting
socket.emit('new update'); // Emit a new update event
app.showSuccessMessage(); // Show the success message
setTimeout(function() { // Set a short 1.5s delay on the form submit, to show the loader for some perceived performance
elements.form.submit();
}, 1500)
});
When the server receives the 'new update' event, it then broadcasts an event to all connected sockets (ie. all people using the client).
socket.on('new update', function () {
console.log('Notification received');
io.sockets.emit('update');
})
All clients receiving the 'update' event will show the notification in the browser.
socket.on('update', function() {
console.log('Review updated');
app.showNotification();
})
CSS to the Rescue & Browser Technologies
For the styling of the upload tool I used basic CSS styling in LESS format.
The overview is responsive and is tested on multiple modern browsers. All uses of flexbox have been checked for support:
@supports (display: flex) {
#review-form {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
}
When there is no flexbox support, a fallback to display:block or display:inline-block has been built in.
Regarding JavaScript features in older browsers, I used an IE-proof way to add or remove classes, namely; 'setAttribute'.
showSuccessMessage: function() {
if (elements.successMessage) {
setTimeout(function() {
elements.uploadButton.setAttribute('class', 'hidden');
elements.successMessage.removeAttribute('class', 'hidden');
}, 500);
}
}
// further code omitted
The great thing is that the notification system works in Internet Explorer 9 (and up) this way!
Web App from Scratch
The frontend code is built with the OOP-approach in mind.
By placing all code into an Immediately Invoked Function Expression, or IIFE, the scope is not global and therefore not accessible from outside. This increases security.
I always like to keep my element selectors outside of the main app object to keep it clean.
(function() {
'use strict';
var socket = io();
var elements = {
form: document.getElementById('review-form'),
uploadButton: document.getElementById('upload'),
successMessage: document.getElementById('success'),
notification: document.getElementById('notification')
}
var app = {
init: function() {
socket.on('connection', function() {
console.log('Connected to server!');
if (elements.form) {
elements.form.addEventListener('submit', function(e) {
e.preventDefault();
socket.emit('new update');
console.log('Form submitted');
app.showSuccessMessage();
setTimeout(function() {
elements.form.submit();
}, 1500)
});
};
socket.on('update', function() {
console.log('Review updated');
app.showNotification();
})
});
},
showSuccessMessage: function() {
if (elements.successMessage) {
setTimeout(function() {
elements.uploadButton.setAttribute('class', 'hidden');
elements.successMessage.removeAttribute('class', 'hidden');
}, 500);
}
},
showNotification: function() {
if (elements.notification) {
elements.notification.removeAttribute('class', 'hidden');
setTimeout(function() {
elements.notification.setAttribute('class', 'hidden');
}, 5000)
}
}
};
app.init();
}());