The basics of a HTML calendar

2:53 PM 10/7/2018

There are already dozens of calendars out there on web, which usually are used as date-picker widget. And nowadays modern browsers just use built-in date-picker for any <input type="date"> elements. So we're not going to build a date-picker here.

We are going to instead build a non-popup (always shown), table-like and responsive calendar. Something like the one below, to be filled-in by users just like a real, printed wall calendar:

Sun Mon Tue Wed Thu Fri Sat

The Save and Load buttons you see on the top of calendar are just dummies: when clicked, they don't do anything. But you get the idea, javascript click handlers can be installed on them to perform save to / load from database server, or even to / from local devices nowadays, as local files.

HTML

A <table> seems to be the perfect HTML element to represent a calendar, but after a few days using it I decide to use <div>s and <span>s instead.

<div id="test">
	<div>
		<input name="year" type="number" value="2000">
		
		<select name="month">
			<option value="0">Jan</option>
			<option value="1">Feb</option>
			<option value="2">Mar</option>
			<option value="3">Apr</option>
			<option value="4">May</option>
			<option value="5">Jun</option>
			<option value="6">Jul</option>
			<option value="7">Aug</option>
			<option value="8">Sep</option>
			<option value="9">Oct</option>
			<option value="10">Nov</option>
			<option value="11">Dec</option>
		</select>
		
		<button type="button">Save</button>
		
		<button type="button">Load</button>
	</div><!-- #test.div:nth-child(1)-->
	
	<div>
		<span>Sun</span>
		<span>Mon</span>
		<span>Tue</span>
		<span>Wed</span>
		<span>Thu</span>
		<span>Fri</span>
		<span>Sat</span>
	</div><!-- #test.div:nth-child(2)-->
	
	<div>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
		<span contenteditable="true" data-date=""></span>
	</div><!-- #test.div:nth-child(3)-->
</div><!-- #test -->
			

CSS

#test{
	max-width: 100%;
}

#test div:first-child{
	text-align: center;
}
#test div:first-child [name="year"]{
	width: 4em;
}

#test div:nth-child(2) span{
	display: inline-block;
	width: calc(14.28% - 4px);
	text-align: center;
}

#test div:nth-child(3) span{
	display: inline-block;
	width: calc(14.28% - 10px);/* = calc((100 / 7)% - (4px + horiz paddings + border widths) */
	padding: 0 2px;
	overflow: auto;
	vertical-align: text-top;
	height: 12vw;
	border: 1px solid black;
	margin-top: 0.2em;
}
#test div:nth-child(3) span:before{
	content: attr(data-date);
	position: absolute;
	color: lightblue;
	width: 1.2em;/* don't set top left, adjust horiz position with this */
	text-align: center;
	font-size: 10vw;
	z-index: -1;/*date number sent back to behind */
}
#test div:nth-child(3) span.today:before{
	color: pink;
}
			

javascript

'use strict';
+function(){
	function setupDates(){
		//get the sunday before date 1 of selected year and month.
		date.setFullYear(hYear.value, hMonth.value, 1);
		date.setTime(date.getTime() - date.getDay() * msOfaDay);

		//the date number of today
		var today = new Date(),
			iToday = -1;

		//print dates of selected month
		for(let i = 0; i < hDates.length; i++){
			hDates[i].className = hDates[i].className.replace(/\btoday\b/g, '');//clear today class to all dates
			if(date.getMonth() === Number(hMonth.value)){
				hDates[i].setAttribute('data-date', date.getDate());
				if(date.getDate() === today.getDate())
					iToday = i;
			}
			else
				hDates[i].setAttribute('data-date', '');
			date.setTime(date.getTime() + msOfaDay);//procces next day
		}
		
		//make today different than other days
		if(today.getFullYear() === Number(hYear.value) && 
			today.getMonth() === Number(hMonth.value) && 
			hDates[iToday].className.search(/\btoday\b/g) === -1)
			hDates[iToday].className = hDates[iToday].className.trim() + ' today';
	}//end setupDates()
	
	var hTest = document.getElementById('test'),
		hYear = hTest.firstElementChild.querySelector('[name="year"]'), 
		hMonth = hTest.firstElementChild.querySelector('[name="month"]'),
		hDates = hTest.children[2].getElementsByTagName('span'),
		msOfaDay = 1000 * 60 * 60 * 24,//ms of a day
		date = new Date();
		
	//install input change event handler on year input
	hYear.addEventListener('input', setupDates);
	
	//install input change event handler on month input
	hMonth.addEventListener('change', setupDates);
	
	//initialize selected year and month as current year and month
	hYear.value = date.getFullYear();
	hMonth.value = date.getMonth();
	
	//intialize dates based on selected year and month
	setupDates();
}();
			

Comments