How to build a datepicker with Angular, Bulma and Moment.js (part 2)

Giovanni Chiodi
5 min readApr 8, 2018

--

This is part 2 of a “live” tutorial, you wont be able to follow it unless you have gone through part 1. (part 3 is here)

Welcome back. It’s a quiet and sunny Sunday here in Nemi, nothing better to do than to listen to a conducive tune and continue building the datepicker.

This is what we’ve got so far:

Before we go any further, I need to make the existing code a bit more rational, like so:

Rename variables and functions:

  • viewDate -> navDate
  • changeViewDate -> changeNavMonth
  • canChange -> canChangeNavMonth

changeNaveMonth now takes only one argument:

changeNavMonth(num: number){
if(this.canChangeNavMonth(num)){
this.navDate.add(num, 'month');
}
}

canChangeNavMonth also takes one argument only:

canChangeNavMonth(num: number): boolean{
const clonedDate = moment(this.navDate);
clonedDate.add(num, 'month');
const minDate = moment().add(-1, ‘month’);
const maxDate = moment().add(1, ‘year’);
return clonedDate.isBetween(minDate, maxDate);
}

Was this necessary? No, but it makes it all more readable. Now, let’s disable the chevron button if the date is not within the range we specified. All we need to do is to add this kind of directive to our buttons:

[disabled] = “!canChangeNavMonth(-1)” 

Wrapping up, these are the new datepicker.component.ts and datepicker.component.html:

Much better. And this is what we achieved:

Now, it’s time to make that little weekdays header dynamic too.

5 — That little weekdays header

It turns out, each locale has its own official first day of the week. In UK, it’s Sunday. In Italy, where I’m now, it’s Monday. The sequence is represented by numbers from 0 to 6, both in JS dates and in Moment.js objects. In Moment.js a weekday can be referred to by its number, in a locale-aware fashion, with the weekday() method. So a British Sunday or an Italian Monday (depending on locale) would be:

moment().weekday(0);

To display it in a readable format, with only three letters just as we need:

moment().weekday(0).format(‘ddd’);

This will display “Sun” (‘en’ locale) or “lun” (‘it’ locale; yep, does not capitalize it in this locale). To display the header all we have to do is to iterate that line of code over an array of numbers from 0 to 6. Really. So in our component we can declare an empty array of strings representing our header (weekDaysHeaderArr) and populate in on ngOnInit with a simple makeHeader function:

And finally replace the hardcoded header with an ngFor in our datepicker.component.html:

We did it! Here’s the ‘it’ locale:

Unfortunately it seems there isn’t a built-in capitalize pipe in Angular. We’ll think about it later. Good job so far!!

6 — The actual… dates!

We could do all sorts of convoluted ngFor — ngIf — ngSwitch in our template to correctly display those little numbers. But I prefer confine the logic in the ts file and pass to the template a simple array to iterate through.

Our goal is to create an array which contains 0s for any empty grid space, and the day number for any day of the month. In addition, I also want to know if the date is available or not for selection. Makes sense? Say the month we’re displaying has 30 days and starts on a Tuesday and we’re using the ‘en’ locale, the array should be something like:

[{value: 0, available: false}, {value: 0, available: false}, {value: 1, available: true}, {value: 2, available: true},..., {value: 30, available: true}, {value: 0, available: false}, {value: 0, available: false}]

For any given month, the length of this array would be equal to: the number days in the month + the number of days between the first day of the week according to the locale we’re on and the first day of the month + the number of days between the last day of the month and the last day of the week in the locale.

Chill, we’re using Moment.js. To grab the date representing the first day of the month we’re displaying (remember always to clone a date before manipulating it, if you don’t intend to change it):

const firstDayDate = moment(navDate);
firstDayDate.startOf('month');

To know its weekday number, hence how many empty cells we need to print before printing numbers:

const initialEmptyCells = firstDayDate.weekday();

Similarly, we can calculate how many empty cells the array contains at the end (this time checking how many cells we need to go from there to the end of the week):

const lastDayDate = moment(navDate);
lastDayDate.endOf('month');
const lastEmptyCells = 6 - lastDayDate.weekday();

To know the total number of days in the month we’re displaying:

const daysInMonth = navDate.daysInMonth();

And there you go, the array we want to build has the following length:

const arrayLength = initialEmptyCells + lastEmptyCells + daysInMonth;

Now that we know it’s length we can start building it with a for loop:

let gridArr: Array<number> = [];
for(let i = 0; i< arrayLength; i++){
let obj = {};
if(i<initialEmptyCells || i>initialEmptyCells + daysInMonth -1){
obj.value = 0;
obj.available = false;
} else {
obj.value = i - initialEmptyCells +1;
obj.available = isAvailable(i - initialEmptyCells +1);
}
gridArr.push(obj);
}

Where isAvailable is a function that will take a number and check if that day is available or not. For now, it will just return true all the times except for the number 5, just to see how a disabled button looks:

isAvailable(num: number){
if(num === 5){return false};
else {return true};
}

Damn, now I need to actually write the code… give me a sec…

So we declare a gridArr variable to contain our array, and a makeGrid function which will create it depending on the current date we’re displaying:

We will call the function on ngOnInit and each time we update the navDate.

Now we can finally get rid of the hardcoded grid in our template, looping through our gridArr and adding the “is-disabled” class if the day is not available:

And here’s the result (remember, we disabled each 5th just for the sake of it):

Good job!! Now we just need to grab the date when we select it. But for that we need… part 3!!

--

--