How to Build a Simple Advent Calendar in JavaScript

Advent is the period of waiting and preparing for Christmas which begins four Sundays before Christmas eve. The passing of Advent time is traditionally measured with the help of either an Advent Calendar, or an Advent Wreath. Although the beginning of Advent is not a fixed date, Advent Calendars usually begin on Dec 1.

Based on local customs, Advent Calendars can have different designs, but most frequently they take the form of large rectangular cards with 24 windows or doors marking the days between Dec 1 and 24. The doors hide messages, poems, prayers or little surprises.

In this post I will show you how to make an Advent Calendar in object-oriented JavaScript. As it’s made in vanilla JavaScript you can easily place the code into your own website.

JavaScript Calendar Design

Our Advent Calendar will have 24 doors on a Christmas-themed background image. Each door will hide a Christmas-related quote that will pop up in the form of an alert message when the user clicks on the door. The doors remain closed until the given day comes, as it is the case with real life Advent Calendar; the doors cannot be opened before the right day.

Doors that are already enabled will have a different border and background colour (white) than the disabled ones (lightgreen). We will use HTML5, CSS3, and JavaScript to prepare our Advent Calendar that looks something like this:

Advent Calendar Screenshot

Step 1 – Create File Structure & Resources

First of all, we need to choose a nice background image. I chose one with portrait orientation from Pixabay, so my calendar will contain 4 columns and 6 rows.

It is fine if you prefer a landscape orientation. Just change the positions of the doors in the JavaScript code, as you’ll have 6 columns and 4 rows. If you have your image, create a folder called /images, and save it.

This will be our only image resource for this project.

Advent Calendar Background

For the JavaScript files create a /scripts folder inside your root folder. Place two empty text files into it, and name them calendar.js and messages.js. Calendar.js will hold the functionality, while messages.js will contain the array of messages that pop up when the user “opens” (clicks on) the doors.

We will also need an HTML and a CSS file, so create two empty files inside your root folder and give them the names index.html and style.css.

When you are ready you have the resources and file structure you’ll need to accomplish this project, and your root folder looks something like this:

Advent Calendar File Structure

Step 2 – Create The HTML

The HTML code we use is pretty straightforward. The CSS stylesheet is linked in the <head> section, while the two JavaScript files are included in the bottom of the <body> section. The latter is necessary, because if we put the scripts into the <head> section, the code wouldn’t be executed, as it uses the elements of the loaded HTML page.

The Advent Calendar itself is placed inside the <article> semantic tag. We load the Christmas image as an <img> HTML element, and not as a CSS background property, because this way we can easily access it as an element of the DOM.

Below the image we place an empty unordered list with the “adventDoors” id selector that will be populated by the scripts. If the user doesn’t have JavaScript enabled in their browser, they will just see a simple Christmas image.

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Advent Calendar</title> <!-- Mobile-friendly viewport --> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Style sheet link --> <link href="style.css" rel="stylesheet" media="all"> </head> <body> <article> <h1>Advent Calendar</h1> <img src="advent-calendar-javascript/background.jpg" alt="Advent Calendar" id="adventCal"> <ul id="adventDoors"></ul> </article> <!-- Scripts --> <script src="scripts/messages.js"></script> <script src="scripts/calendar.js"></script> </body> </html>

Step 3 – Populate the “Messages” Array

We need 24 Christmas quotes to populate the “messages” array. I chose mine from GoodReads.

Open the scripts/messages.js file; we will place the quotes here to keep them separate from the functionality. The messages array is an array inside an array, each element of the outer array contains another array with two elements: a quote, and its author.

Populate the array with your favourite quotes according to the following syntax:

 var messages = [ ["Quote 1", "Author 1"], ["Quote 2", "Author 2"], ... ["Quote 24", "Author 24"] ];

Step 4 – Add Basic CSS Styles for the Doors

To create the necessary CSS styles for the doors we need to imagine the final design, as the doors themselves will be created with JavaScript in the following steps.

We need to create 4 columns and 6 rows of rectangles in proper alignment. For this, we will use the position: relative and the position: absolute CSS rules. As the exact position will change door by door, we will add the top, bottom, left, and right properties in the JavaScript, in the CSS we just need to add a relative position to the container (unordered list in the HTML), and absolute positions for the list elements (they will be added in the JS, too).

The other important thing in the stylesheet file is to create a different design for the disabled and the enabled states. The .disabled selector will be added to the disabled by JavaScript.

For my demo Calendar I set a solid white border and white fonts for the enabled doors with a transparent white background on hovering; and a lightgreen border, and fonts, and a transparent lightgreen background for the disabled ones. If you don’t like this design you can change the colours and the styles according to your wish.

Place the following code (or your modified style rules) into your style.css file.

 ul#adventDoors { position: relative; list-style: none; padding: 0; margin: 0; } #adventDoors li { position: absolute; } #adventDoors li a { color: #fff; width: 100%; height: 100%; font-size: 24px; text-align: center; display: flex; flex-direction: column; justify-content: center; text-decoration: none; border: 1px #fff solid; } #adventDoors li a:not(.disabled):hover { color: #fff; background-color: transparent; background-color: rgba(255,255,255,0.15); } #adventDoors li a.disabled { border-color: #b6fe98; background-color: rgba(196,254,171,0.15); color: #b6fe98; cursor: default; }

Step 5 – Create The Global Variables

From this step on, we will only work with the scripts/calendar.js file, so now let’s open this up. Our Advent Calendar will use two global variables.

The myCal variable holds the Calendar image as a JS object. The image has already been added to the index.html file in Step 2. We will place the doors onto this image in Step 7. We capture the related HTML <img> element marked with the “adventCal” identifier by using the getElementById() DOM method. The myCal variable will be an HTMLImageElement DOM object.

The currentDate variable holds the current date so that our script can easily decide if a door should be enabled or disabled. To create currentDate we instantiate a new object of the Date JavaScript class.

Put the following code snippet into the top of your calendar.js file.

 var myCal = document.getElementById("adventCal"); var currentDate = new Date();

Step 6 – Create the “Door” Class

As we need 24 doors, the most straightforward way to do this is to create a “Door” class, and later instantiate it 24 times.

Our Door class has two parameters, calendar and day. For the calendar parameter we will need to pass the Christmas image that will function as the background. For the day parameter we will need to pass the current day of December in the form of an integer.

We will pass the exact values of the parameters in the last step (Step 8), during the instantiation of the 24 Door objects.

We will make 5 properties and 1 method for the Door class. In this Step we will only go through the 5 properties, and I will explain the content() method in the next Step.

The “width” & “height” properties

The width and height properties dynamically calculate the width and height of each individual door (that changes according to the width and height of the background image).

The 0.1 and 0.95 multipliers are in the equation to leave some space for the margins, between each doors, and around the sides of the Calendar, too.

The “adventMessage” property

The adventMessage property holds the content of the alert messages, namely the quotes and the matching authors that our messages.js file holds. The adventMessage property takes a quote and an author from the messages[] array, depending on the current date.

For Dec 1 the adventMessage property takes the first element of the outer array which is messages[0], as arrays are zero-based in JavaScript.

For the same reason, the quote for Dec 1 is positioned as messages[0][0] (first element of the inner array), and the matching author can be reached as messages[0][1] (second element of the inner array).

The “x”&”y” properties

Properties x and y hold the proper positions of each door that we will use in the next step to set the top and left CSS properties. These will complement the position: relative and position: absolute CSS rules that we set in Step 4 for the door container (ul#adventDoors), and the doors themselves (#adventDoors li). The calculations may seem somewhat intimidating, but let’s go quickly through them.

The x property will be used by the left CSS positioning property in the next Step (Step 7). It determines in pixels where an individual Door needs to be placed on the x-axis.

It takes the width of the background image, and it leaves a little margin for it (4%). Then with the help of the remainder operator, it assesses in which column it will be placed (remember, there will be 4 columns), and finally it adds a little (10%) margin to each individual Door by using a 1.1 multiplier.

The same way, the y property will be used by the top CSS positioning property, and likewise it determines in pixels where an individual Door needs to be placed on the y-axis.

We take the height of the background image with the help of the height property of the passed in calendar parameter (that holds a DOM-object), and leave a 4% margin around the vertical sides of the Calendar.

Then, with the help of the Math.floor() method we calculate in which row a given Door object will be (there will be 6 rows).

Finally we add a 10% margin for each Door object by multiplying its height using a 1.1 multiplier.

 function Door(calendar, day) { this.width = ((calendar.width - 0.1 * calendar.width) / 4) * 0.95; this.height = ((calendar.height - 0.1 * calendar.height) / 6) * 0.95; this.adventMessage = 'Day ' + day + ' of Advent\n\n' + '"' + messages[day - 1][0] + '"\n\n\t' + 'by ' + messages[day - 1][1] + '\n\n'; this.x = ( 0.04 * calendar.width + ((day - 1) % 4) * (1.1 * this.width) ); this.y = - ( 0.96 * calendar.height - Math.floor((day - 1) / 4) * (1.1 * this.height) ); this.content = function() { ... }; }

Step 7 – Populate the “Content()” Method

It is the content() method that will display our doors in the browser. First of all, we create a new DOM node with the help of the variable node that will create the <li> elements inside our currently empty unordered list (ul#adventDoors) in the HTML file.

As the Door class will be instantiated 24 times in a for loop in the next Step (Step 8), this means that we will have 24 <li> elements, one li for each door. In the next line we append the new node to the #adventDoors unordered list as a child element by using the appendChild() DOM method.

The node.id property in the next line adds a unique id selector to each list element (door). We will need this to be able to properly loop through the days in the next step (Step 8). This way Door 1 will have an id=”door1″, Door 2 will have an id=”door2″ selector, etc.

The node.style.cssText property in the next line adds some CSS rules to each list element (door) with the help of the style=”…” HTML attribute that’s used to include inline CSS in HTML files. The node.style.cssText property uses the properties of the Door class that we set in the previous step (Step 6).

To make our Door object clickable we also need to add an <a> tag inside the list elements. We achieve this with the help of the innerNode variable that we bind as a child element to the appropriate list element identified by the id=”door[i]” selector (with the [i] being the day number), by using the appendChild() DOM method just like before.

The innerHTML property in the next line displays the current day (1-24) on top of the door in the browser, and we also add a href=”#” attribute to our anchor tags by means of the href DOM property.

Finally, in the if-else statement, we evaluate if a Door object should be enabled or disabled. First, we examine if we are in the 12th month of the year (December) by using the getMonth() method of the Date object. We need to add 1, because getMonth() is zero-based (January is month 0, etc.).

After this, we check if the current date held in the currentDate global variable we set in Step 5 is less than the day that the current Door object represents.

If it’s not December, or the day represented by the given Door is bigger than the current date, the Door should be disabled, in any other cases it needs to be enabled so that users can click on it, and see the related Advent message.

If the Door is disabled, we add a class=”disabled” selector to the given anchor tag with the help of the className property. Remember, we’ve already styled the .disabled class with CSS in Step 4. We also need to set the onclick HTML event attribute to return false.

If the Door is in enabled state, we add the adventMessage property to an alert message, and place it inside the onclick HTML event attribute.

 this.content = function() { var node = document.createElement("li"); document.getElementById("adventDoors").appendChild(node); node.id = "door" + day; node.style.cssText = "width: " + this.width + "px; height: " + this.height + "px; top: " + this.y + "px; left: " + this.x + "px;"; var innerNode = document.createElement("a"); document.getElementById("door" + day).appendChild(innerNode); innerNode.innerHTML = day; innerNode.href = "#"; if( ( currentDate.getMonth() + 1 ) < 12 || currentDate.getDate() < day ) { innerNode.className = "disabled"; innerNode.onclick = function() { return false; } } else { var adventMessage = this.adventMessage; innerNode.onclick = function() { alert(adventMessage); return false; } } };

Step 8 – Initialize the “Door” Objects

Finally, we need to initialize the Door class 24 times.

To do so, we use an Immediately Invoked Function Expression that is quite useful here, because we don’t need a variable as we only want to execute the code inside the function once.

We create a doors[] array that will hold the 24 Door objects. We loop through the days from 1 to 24 (they will be the 0-23th elements of the doors[] array, as arrays are zero-based), and finally return the whole doors[] array including the 24 Door objects to display them in the browser.

 (function() { var doors = []; for(var i = 0; i < 24; i++) { doors[i] = new Door(myCal, i + 1); doors[i].content(); } return doors; })();