Home

Awesome

VueSimpleCalendar

Introduction

vue-simple-calendar is a flexible, themeable, lightweight calendar component for Vue that supports multi-day scheduled items.

Important: Version 6.0, released 2021-03-27, dropped IE11 support and was migrated to Vue 3 and Vite. Version 5 is still is available via npm if you need Vue 2 or IE11 support, but it will not be maintained going forward.

Demo

Here is a live demo page and the repo for it: https://www.tallent.us/vue-simple-calendar/ https://github.com/richardtallent/vue-simple-calendar-sample

Table of Contents

Features

There are other great calendar components out there, but most are either intended to be used as date pickers or had way too many features for me. I wanted something that would simply show a month as a grid and show scheduled activities (including multi-day items) on that grid. While the component goes beyond that simple use case, that is still the core focus.

What this component does not try to do:

Browser compatibility

This component is compatible with relatively modern browsers (i.e., not IE11). Please note that this component is designed first for desktop web applications, not mobile, so while the component may operate on a mobile device, the limited resolution may not allow for much in the way of useful functionality. Also, drag and drop only works on desktop browsers -- the drag events on touch devices are very different, I haven't had time to dig into them yet.

TypeScript Support

For some reason, it seems to be nearly impossible to get Vite to be configured to both emit .d.ts files on build and to allow use of npm run dev. The typescript2 plugin (recommended by some Vue experts) just seems to break HMR. So I've given up for now, until the Vite powers that be decide to make a functional template for libraries that builds typescript declarations. This has been the only real fly in the ointment for me migrating to Vite. If you have ideas, I'm all ears! In the meantime, I've decided I can no longer allow this issue to prevent me from releasing the new version.

Installation and Usage

(This assumes you already have a web application set up for using Vue. If you're starting a new project, look up the documentation for the Vue CLI, which will allow you to initialize a new project with webpack, etc.)

Install the component using npm:

npm i --save vue-simple-calendar

In your application, you will need to:

Tips:

display: flex;
flex-direction: column;
flex-grow: 1;

Here's a minimal application example for an empty calendar, styled with the default theme and the US holidays theme:

<template>
	<div id="app">
		<h1>My Calendar</h1>
		<CalendarView
			:show-date="showDate"
			class="theme-default holiday-us-traditional holiday-us-official">
			<template #header="{ headerProps }">
				<CalendarViewHeader
					@input="setShowDate" />
			</template>
		</CalendarView>
	</div>
</template>
<script>
	import { CalendarView, CalendarViewHeader } from "vue-simple-calendar"

	import "..relative-path-to-node_modules/vue-simple-calendar/dist/style.css"
	// The next two lines are optional themes
	import "..relative-path-to-node_modules/vue-simple-calendar/dist/css/default.css"
	import "..relative-path-to-node_modules/vue-simple-calendar/dist/css/holidays-us.css"

	export default {
		name: 'app',
		data: function() {
			return { showDate: new Date() }
		},
		components: {
			CalendarView,
			CalendarViewHeader,
		},
		methods: {
			setShowDate(d) {
				this.showDate = d;
			},
		}
	}
</script>
<style>
	#app {
		font-family: 'Avenir', Helvetica, Arial, sans-serif;
		color: #2c3e50;
		height: 67vh;
		width: 90vw;
		margin-left: auto;
		margin-right: auto;
	}
</style>

Props

The following properties are supported, by functional area:

Grid Display

Important: if you put user-provided content in titles, you should either sanitize the HTML they provide, or disable this flag to prevent XSS vulnerabilities.

Grid Selection

Note: Each date between selectionStart and selectionEnd (including them) has the aria-selected attribute. This is used in the default CSS theme to highlight these in yellow).

Header Display

Items (Scheduled events to show on the calendar)

Calendar Item Properties

Each item shown on the calendar can have the following properties. id and startDate are required, and title is strongly recommended.

Events

(Note: below, calendarItem refers to the normalized version of the calendar item involved in the activity. For more information, see the "item" slot below.)

You can create handlers for the following Vue events to add custom functionality (with their payload array):

Clicks

Date selection (day drag and drop)

Calendar Item Hover

Calendar Item Drag and Drop

Slots

Header

The named slot header contains the component you want to use as the calendar's header. Generally, this would show the current date range, and allow the user to navigate back and forth through time. If you don't provide a component for this slot, there will be no header, just the calendar grid. This component comes with a nice default header component, CalendarViewHeader, which you can either use directly, or use as a template for creating your own. (Prior to 4.0, if you didn't provide a component, a default header would be shown. That is no longer the case for reasons explained in the CHANGELOG.)

<CalendarView :show-date="myShowDate">
    <template #header="{ headerProps }"
		<CalendarViewHeader :header-props @input="setMyShowDate" />
	</template>
</CalendarView>

The parent CalendarView passes a property called headerProps to the header component. This property includes all of these values (basically, anything you would normally need to render a calendar header):

Since CalendarView has some logic around whether the user should be able to navigate to the past or the future, some of these dates will be null if the corresponding action is disabled.

The developer implementing her own header simply needs to create a header component that, like the default header component:

Note above that both previousPeriod and previousFullPeriod are provided, as well as nextPeriod and nextFullPeriod. The reason for this distinction is to allow the developer to decide how the calendar's Previous and Next buttons should move through time if displayPeriodCount is greater than 1. My personal preference is to move forward and backward by one week / month, allowing the user to pinpoint the exact date window they wish to see. But in some applications (a "quarterly" calendar, for example, or a calendar with set two-week periods), it may be better for the buttons to jump backward and forward based on the displayPeriodCount setting. This gives the developer both options -- just choose which interaction you want to use, and use those dates to set the new showDate.

Day Header

The optional named slot day-header replaces the default div.day elements that appear in the column headers for each day of the week. If all you need to do is change how the names are shown, it's probably better to override the locale and/or weekdayNameFormat property. This slot is intended for situations where you need to override the markup within each header cell. For example, if you want each day of the week to be clickable.

This slot passes two scoped variables: index, 0-7, and label, the text it would have used in the header based on the current locale and weekdayNameFormat.

Day Content

The optional named slot day-content allows you to provide your own contents within the date cell. The day of the month (and, sometimes, the month name) is rendered in a separate (sibling) element with the class cv-day-number, so you should use CSS to hide this class if you want your slot to be the only content in the cell. Note that items are rendered above the individual date cells, so your slot content will appear below any items on that day.

This slot passes one scoped variable: day, the date associated with the cell.

Item

The optional named slot item replaces the div.item for each item (not just the contents of the items element, the entire element). Use this if you want to override entirely how items are rendered. For example, on a small mobile device, you may want to show just a thin stripe, dots, or icons to indicate items, without titles or times. This slot passes three scoped variables:

Note that item is a version of the calendar item normalized to be shown on that week's row, it's not the bare item pulled from the items prop. This customized version parses and defaults the startDate and endDate, defaults missing id to a random number, defaults a blank title to "Untitled", and adds a number of classes values based on the position and role of the item as shown for that week (whether it continues from the previous week, etc.). The original item is passed back as item.originalItem.

Week Number

The optional named slot week-number replaces the content shown in the "week number" column. By default, this shows the week number of that week within its year. This is computed using the ISO 8601 logic, where "week 1" of a year is the week containing the first Thursday of January. This has no relation to your startingDayOfWeek property. If there is a partial week before the first week of the year, it is numbered 0 (if you move back to December of the prior year, that same week would be week 52 or 53 of that year).

Using the week-number slot, you can use this column to display anything -- a number, icons, text, etc. This slow passes three scoped variables:

Using CSS, you can define your own width, colors, etc., and you can move the column to display after the days rather than before.

Note that this column "belongs" to the week, so if the week scrolls due to the number of items that week, so will the contents of this column.

Customizing the Look and Feel

In addition to slots, this component is designed to allow for significant customization of the look and feel solely through CSS. Here's the structure of the markup generated by the component (combined with the default header component). Each line represents a Vue SLOT (all caps) or an HTML element (first word on the line). Indentation represents the hierarchy. Each word after the first word is a class applied to the element. Elements or classes in (parenthesis) are conditional. Loops (i.e., v-for) are shown in [brackets].

Note that the items are not child nodes of the days, they are children of the week and positioned above the days. This allows items to span multiple days.

div cv-wrapper locale-X yYYYY mMM (past|future) period-X periodCount-X wrap-item-title-on-hover
	HEADER
		div cv-header
			div cv-header-nav
				button previousYear
				button previousPeriod
				button nextPeriod
				button nextYear
				button currentPeriod
			div periodLabel
				div startMonth
				div startDay
				div startYear
				div endMonth
				div endDay
				div endYear
	div cv-header-days
		div cv-header-day dowX [x7]
	div cv-weeks
		div cv-week weekX wsYYYY-MM-DD [x # weeks in visible period]
			(div cv-weeknumber)
				span
			dv-weekdays
				div cv-day dowX dYYYY-MM-DD dMM-DD dDD wmX (past|today|future|last|outsideOfMonth|lastInstance|selectionStart|selectionEnd) [x 7]
					div cv-day-number
						div cv-fom-name
					DAYCONTENT
				ITEM
					div cv-item offsetX spanX (continued|toBeContinued|hasUrl|hasItems) [x # of items]
						span startTime (hasEndTime)
						span endTime (hasStartTime)

where:

Calendar classes

locale-X

Two locale classes are added--one for the user's full locale, the other for just the first two letters (the language), both in lowercase. You could use this information to hide or show specific holidays, or to localize text using CSS content. Example:

locale-en locale-en-us

yYYYY

The full year of the starting period of the current view.

mMM

The month (01-12) of the starting period of the current view.

(future|past)

When the period shown does not include today's date (local time), one of these classes is added. By default, this shows and hides and determines the label for the button that returns the view to the current period.

period-X

The current displayPeriodUom value (year, month, or week).

periodCount-X

The current displayPeriodCount value (a number, usually 1).

Week classes

weekX

Each week is numbered, starting with the first week of the visible period. (This is not the same as the value in the optional "week number" column.)

wsYYYY-MM-DD

Each week also has a class representing the date of the Sunday starting that week. This could be used to style entire weeks that have some special importance.

wrap-item-title-on-hover

If an item title is truncated, this enables an optional behavior that will wrap the item to show the entire title when the user hovers over it.

Day classes

cv-day-number

The day of the month for each cell. Parent of cv-fom-name.

cv-fom-name

On the 1st, includes the month name depending on the period and monthNameOn1st prop.

dowX

This class is for the day of the week, ranging from 0 to 6. This allows you to easily style certain days of the week (say, weekend days) differently from other days. The same class is also added to the weekday headers.

dYYYY-MM-DD / dMM-DD / dDD

Each day in the grid is given three special classes -- one for the month and day (01/01-12/31), one for the day (01-31), and one for the entire ISO 8601 date. This allows easy styling of holidays and other special days using CSS alone (rather than using calendar items).

The demo calendar has some examples of using these to add holiday emoji beside the day numbers. Example:

d2017-05-23 d05-23 d23

instanceX

The instance of the weekday within the day's month. For example, the class instance1 is added to the first Sunday, Monday, etc. of the month. Note that since this is relative to the day, not the "main" month being shown, days visible from the previous or next month will also have these classes, relative to their own month.

This makes it easy to style, say, the first and third Friday of each month. The demo app has some custom styles using this feature to add emoji to Labor Day and Thanksgiving.

lastInstance

Added to the last instance of a particular day of the week within its month (as above, relative to the day in question, which means the last day of the month previous, if in view, will also have this class. The example application uses this to show an emoji for Memorial Day (the last Monday of May in the US).

outsideOfMonth

This class is added to each day falling outside of the month being shown. By default, these days are greyed out.

past

This class is added to days before the current date (local time).

today

This class is added to the current date (local time), if visible on the current view.

future

This class is added to days after the current date (local time).

last

This class is added to the last day of its month.

selectionStart

This class is added to the day specified by the selectionStart prop.

selectionEnd

This class is added to the day specified by the selectionEnd prop.

Calendar Item classes

offsetX

This class on an item represents the day of the week when the item starts on this week. If an item spans more than one week, the offset for the second week, etc. would be offset0 (Sunday).

spanX

This class on an item represents the width of the item display that week, in days. For example, if an item spans from a Thursday to the next Wednesday, it would have span3 on the first week and span4 on the second week.

continued

This is added to an item when it is continuing from a previous week. By default, this turns off the rounded corners on the left side of the item box and adds a grey right-arrow before the title.

toBeContinued

This is added to an item when it is spills over into the following week. By default, this turns off the rounded corners on the right side of the item box and adds a grey right-arrow after the title.

hasUrl

This is added to an item when it has a url attribute (i.e., it is a hyperlink). By default, this is used to add a hovering underscore to item titles that are hyperlinked.

isHovered

This is added for all item elements whose id matches the id of the item being hovered. (This allows proper hover styling--when items wrap to more than one week, they are represented by more than one element, so a standard :hover selector will only select the element being hovered, not the entire item.) Note that there is no default styling for this, it is solely provided so you can choose to style hovered items if you wish.

I'm open to other suggestions, provided they are easily calculated and there's some reasonable use case for having them.

startTime / endTime

These classes are applied to the start and end time of an item, respectively.

Future Plans

Contributions

Inspiration

This project was inspired by Monthly.js, a JQuery-based control I've contributed to. Unfortunately, I wasn't able to port the code and still do things the Vue / Vanilla JS way, but I did borrow some of the concepts from that component.

FAQ

(Ok, not really frequently-asked, but just random stuff.)

Why Vue?

Because Vue is awesome. I've been using it since early 2017 in production, and I've migrated my applications from ExtJS, JQuery, and Riot to Vue components. If you prefer React or Angular, it should be reasonably easy to port it. If you do, please let me know, maybe we can coordinate on feature upgrades!

Can you add feature "X"?

Maybe. Depends if it fits the core functionality of viewing a calendar grid. I don't want to create something that replicates all possible calendar views, and definitely don't want to add functionality for creating or editing calendar items (that should be handled by the application/component hosting the view).

Why not use moment.js?

Moment.js is great, but I would only need a tiny fraction of its capabilities, and for simplicity, I wanted to not have any dependencies (other than Vue of course).

Why is the style so "plain"?

The baseline style (what you get with no external CSS files imported) is intended to be as bare as possible while still providing full functionality and legibility. The idea here is to make integrating this component into your own theme as easy as possible by inheriting what it can from your parent application and minimizing the places where you may need to override its choices.

The default theme stylesheet builds on this baseline to provide a restrained, clean, simple theme for the calendar, and is useful if you don't intend to create your own theme. You can include it from dist/css/default.css. The sample app uses this stylesheet.

A third stylesheet, dist/css/holidays-us.css, shows how simple it is to use CSS to style specific days using CSS selectors (it adds emoji icons for various holidays).

What styles can I not override?

Build Setup

# install dependencies
npm install

# build for production with minification
npm run bundle