Building an extendable calendar in Vue.js -Part 1: State Model & Basic View

Later posts in this series:
Part 1 Updated: Displaying dates not in month
Part 2: Date picker using ‘Extends’
UPDATE: Introducing V-Calendar, the plugin this series inspired!

In this series of tutorials, we’ll construct a lightweight, extendable calendar component in Vue.js. We’ll cover step by step how to implement the date logic as well as introduce some Vue specific features to support multiple interface designs. The date logic is surprisingly simple…probably more simple than you would initially think, and hopefully you will be introduced to some ideas on how to design components that are built for future reuse and extendability.

The requirements for this tutorial include the following:

  1. Vue.js!
  2. No dependencies (except Vue, of course).
  3. Single date, multiple date and date range selection modes.
  4. Extendable with sensible defaults. There are some great packages out there for calendars, but I haven’t seen one that I quite like that I could see myself using in a deverse set of design contexts. This goal specifically will take the most effort but it will strengthen our skills in designing extensible components using HTML, Javascript, and CSS.

This complete tutorial will be broken down into 3 parts:

  1. Design the state model that provides the foundation for an extendable calendar component, as well as providing a sensible default user interface with navigation controls.
  2. Support date selection modes.
  3. Refactor to support extendiblity of the UI, including component composition and prop functions.

State Model

First, we can declare a few constant arrays outside the scope of the vue instance. Don’t worry about trying to completely understand their purpose yet as they will become apparent throughout this tutorial.

Otherwise, the UI is completely derived from 2 data fields.

Note: If you are using the now re-introduced prop sync modifiers (v2.3.0+) you could convert month and year to props and keep them in sync with parent components via update events. For this example, however, we’ll focus on pre-2.3.0 versions of Vue.

From these data fields, we can then start deriving our computed properties.

Quick reminder if you are new to Vue: computed properties are defined as functions that are intelligently executed by Vue on-demand. Vue keeps track of the functions’ dependencies and only recalculates their associated properties when their dependencies change. The takeaway here is that there is no reason to worry about unnecessary recalculations at runtime.

Let’s touch on a couple of these computed fields.

firstWeekdayInMonth: Months start on different weekdays. However, since our calendar is going to start displaying day cells from Sunday for every month (even if it is not in the current month), we need this number to know when to start keeping track of the actual month days.

daysInMonth: Number of the days in the current month. Pretty self-explanatory. Note, though, that we use some additional logic to account for leap years.

The last computed field efficiently computes the state needed to display the calendar days.

Take a moment to review the computed properties above to get a good feel for what we are trying to accomplish. Our user interface needs to display cells from the first day of the first week to the last day of the last week of the month. This includes cells for days that do not necessarily fall within the current month. In the end, all the state data for each day that could possibly be useful to our UI has been assigned.

And this is really the beauty of Vue.js, in that we can declaratively decide how our state model should appear to the view. The cognitive weight of tracking dependencies is lifted off of our shoulders and the process of how state gets intelligently updated is inherently simplified.

In this specific case, it’s not difficult to see that a change in the month or year should trigger a recalculation of the calendar data. But as more complex situations arise, Vue’s ability to abstract the synchronization of the view with its state starts to really pay off.

Before moving on the the interface, let’s implement some Vue helper methods that allow us to navigate forwards and backwards in single month and year increments. Because we’ve already done the hard work in declaring our state, this is easy-peasy.

Whew! We’re done with the state.


Interface

Now we can start reaping the benefits of architecting a simple and comprehensive state model.

Let’s just take a moment to enjoy this declarative goodness.

Ok. Well…that was nice. Here’s the SASS.

Feel free to modify to taste, but don’t get too far with customization.

In upcoming tutorials, we’ll add support for selecting single dates, multiple dates, and date ranges, as well refactor a bit to make this calendar extendable from a user interface perspective.