DEV Community

Cover image for Create a Responsive Calendar with Vanilla Javascript and CSS Grid
Amit Gupta
Amit Gupta

Posted on

Create a Responsive Calendar with Vanilla Javascript and CSS Grid

In this post, I attempt to show how to develop a calendar using vanilla Javascript and CSS Grid Layout. Following CodePen shows the final code.

We start with an HTML page that has a single <div> element.

<div data-app="calendar-app"></div>

We will use Javascript to build and render our calendar in this <div>.

In our Javascript file, we start by defining an array for the months and an array for days of the week.

var months = ['January','February','March','April','May','June','July','August','September','October','November','December']; 
var daysOfWeek = ['S','M','T','W','TH','F','SA'];

The application level state object stores the month and year being displayed. We initialize the state object with the current month and year.

var state = {
  month: new Date().getMonth(),
  year: new Date().getFullYear()
}

Now, let's take a look at datesForGrid function, which is the most important function of our application. We pass a year and month as arguments and the function returns an array of 42 entries to fill up the 7x6 date grid for the given year and month. month is 0-indexed.

At the beginning of the function, we declare an empty dates array to store the date entries.

//month is 0-indexed
function datesForGrid(year, month) {
  var dates= [];
  ...
}

firstDay stores the day of the week on which the month starts.

...
//Date.getDay() method is 0-indexed.
var firstDay = new Date(year, month).getDay();
...

If value of firstDay is 2, that means the month starts on a Tuesday (Sunday=0, Monday=1, and so on).

Next, we have to find out how many total days are in the month

...
var totalDaysInMonth = new Date(year, month + 1, 0).getDate();
...

By incrementing the the month value by 1 and passing 0 for the date argument in the Date() function, we get the last date of the month in question and hence the total number of days in the month.

We are also interested in the total number of days in the previous month (you'll soon see why).

...
var totalDaysInPrevMonth = new Date(year, month, 0).getDate();
...

Again, by passing the current month value and 0 for the date argument in the Date() function we get the last day of the previous month.

Now, we start populating the dates array.

First, we check how many dates from the previous month do we have to display in the calendar. For example, if the current month starts on a Tuesday, then we have to show 2 dates from the previous month for Sunday and Monday.

We start by adding entries for the previous month in the dates array.

...
// Days from prev month to show in the grid
for(var i = 1; i <= firstDay; i++) {
  var prevMonthDate = totalDaysInPrevMonth - firstDay + i;
  var key = new Date(state.year, state.month -1, prevMonthDate).toLocaleString();    
  dates.push({key: key, date: prevMonthDate, monthClass:'prev'});
}
...

An entry in dates array is an object. We store a key property which is a string representation of the date, date is the numerical date value, monthClass:'prev' indicates that the date belongs to the previous month.

var prevMonthDate = totalDaysInPrevMonth - firstDay + i computes the numerical date value to be stored in the date property of the object.
Let's say the current month is Sept 2020, which means the last month was Aug 2020. In that case, totalDaysInPrevMonth = 31.
Since Sept 1st, 2020 will be a Tuesday, so firstDay = 2. Therefore, prevMonthDate value for i = 1 will be 31 - 2 + 1 = 30.

So, for Sept 2020, the first two entries in the dates array will be as follows

//Example for September 2020
dates[0] = {key: '8/30/2020, 12:00:00 AM', date: 30, monthClass: 'prev'};
dates[1] = {key: '8/31/2020, 12:00:00 AM', date: 31, monthClass: 'prev'};

Next, we populate the dates array with entries for the current month

...
// Days of the current month to show in the grid
var today = new Date();
for(var i = 1; i <= totalDaysInMonth; i++) {
  var key = new Date(state.year, state.month, i).toLocaleString();
  if(i === today.getDate() && state.month === today.getMonth() && state.year === today.getFullYear()) {
    dates.push({key: key, date: i, monthClass: 'current', todayClass: 'today'});
  } else{ 
    dates.push({key: key, date: i, monthClass: 'current'});
  }
}
...

Here we add an additional property todayClass:'today' to the object for today's date.

So, for Sept 1, 2020 the dates array will look like this

//Example for September 2020
...
dates[2] = {key: '9/1/2020, 12:00:00 AM', date: 1, monthClass: 'current', todayClass:'current'};
dates[3] = {key: '9/2/2020, 12:00:00 AM', date: 2, monthClass: 'current'};
...

After adding all the entries for the month to be displayed, we check if there is still space left in the grid. If the size of dates array is less than 42, then we enter the dates for the next month in the dates array.

...
var gridsize = 42;
// If there is space left over in the grid, then show the dates for the next month
if(dates.length < gridsize) {
  var count = gridsize - dates.length;
  for(var i = 1; i <= count; i++) {
    var key = new Date(state.year, state.month + 1, i).toLocaleString();
    dates.push({key: key, date: i, monthClass:'next'});
  }
}
...

render() function is responsible for rendering the calendar on the webpage.

function render() {  
  var calendarApp = document.querySelector('[data-app=calendar-app]');
  // Building the calendar app HTML from the data
  calendarApp.innerHTML = `
    <div class="calendar-nav">
      <button id="prev-month">Previous</button>
      <h2>${months[state.month]} ${state.year}</h2>
      <button id="next-month">Next</button>
    </div>
    <div class='calendar-grid'>
      ${ daysOfWeek.map(day => `<div>${day}</div>` ).join('') }
      ${ datesForGrid(state.year, state.month).map(date => `<div id="${date.key}" class="${date.monthClass} ${date.todayClass ? date.todayClass : ''}">${date.date}</div>`).join('') }
    </div>
  `;
}

.calendar-grid css class defines a simple grid layout for the calendar. Each grid cell is a <div>. Following is an example of rendered HTML

<div class='calendar-nav'>
  <button id="prev-month">Previous</button>
  <h2>September 2020</h2>
  <button id="next-month">Next</button>
</div>
<div class='calendar-grid'>
  ...
  <div id="8/31/2020, 12:00:00 AM" class="prev">31<div>
  <div id="9/1/2020, 12:00:00 AM" class="current today">1<div>
  <div id="9/2/2020, 12:00:00 AM" class="current">2<div>
  <div id="9/3/2020, 12:00:00 AM" class="current">3<div>
  ...
  <div id="10/1/2020, 12:00:00 AM" class="next">1<div> 
  ...
</div>

showCalendar() function is the main entry point. It takes one numerical argument prevNextIndicator.

//valid prevNextIndicator values
-1 = previous month
0 = current month
1 = next month

showCalendar() function sets the state variable based on the value of prevNextIndicator and calls the render() function

function showCalendar(prevNextIndicator) {
  var date = new Date(state.year, state.month + prevNextIndicator);
  //Update the state
  state.year = date.getFullYear();
  state.month = date.getMonth();  
  render();
}

// Show the current month by default
showCalendar(0);

Finally, we add event listeners for the Previous (<) and Next (>) buttons.

document.addEventListener('click', function(ev) {
  if(ev.target.id === 'prev-month') {
    showCalendar(-1);
  }
  if(ev.target.id === 'next-month') {
    showCalendar(1);
  }
});

That's our calendar. With less than 100 lines of Javascript code and a few lines of CSS, we created a fully functional calendar.

Top comments (4)

Collapse
 
delmontee profile image
Delmontee

Is there a way to change the first day of the week to Monday?

Collapse
 
chovy profile image
chovy

I’ve seen this request too. Would be nice to support.

Collapse
 
delmontee profile image
Delmontee

Great post, thanks.
You forgot to include "return dates" at the bottom of your datesForGrid function (but remembered it in the codepen example). Without that we get a JS error

Collapse
 
chovy profile image
chovy

Thank you for this clean cut example. I’ll be using your example as a basis for my calendar web component library I’m working on at webcomponents.engineer