JSFiddle - React, Tailwind, and code Playground

by Alexandru Mihai

HTML

<div class="timetable"></div>

CSS

.timetable{box-sizing:border-box}.timetable::after{clear:both;content:"";display:table}.timetable *,.timetable *:before,.timetable *:after{box-sizing:inherit}.timetable ul,.timetable li{margin:0;padding:0;list-style-type:none}.timetable aside,.timetable section{float:left}.timetable aside{width:25%;padding:0!important;margin-top:46px;border-right:5px solid transparent}.timetable aside li{padding:0 15px;background-color:#EFEFEF;line-height:46px}.timetable aside li:not(:last-of-type){border-bottom:1px solid #fff}.timetable aside .row-heading{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;word-wrap:normal}.timetable ul{list-style-type:none;margin:0}.timetable aside li,.timetable time li{height:46px}.timetable section{width:75%;padding:0!important;overflow-x:scroll;-webkit-overflow-scrolling:touch}.timetable section time{white-space:nowrap;display:inline-block}.timetable section header{height:46px;transform-style:preserve-3d;font-size:0}.timetable section header::after{clear:both;content:"";display:table}.timetable section header li{display:inline-block;font-size:1rem;overflow:visible;width:0;line-height:46px;display:inline-block;position:relative}.timetable section header li:not(:last-of-type){width:96px}.timetable section header li .time-label{display:block;position:absolute;left:0}.timetable section header li:not(:first-of-type) .time-label{transform:translateX(-50%)}.timetable section header li:last-of-type .time-label{transform:translateX(-100%)}@media only screen and (max-width:960px){.timetable aside{width:40%}.timetable section{width:60%}}.timetable ul.room-timeline{border-left:none;position:relative;overflow:hidden}.timetable ul.room-timeline li{position:relative;background-color:#F4F4F4}.timetable ul.room-timeline li:nth-of-type(odd){background-color:#FDFDFD}.timetable ul.room-timeline li:first-of-type{border-top:1px solid #E5E5E5}.timetable ul.room-timeline li:last-of-type{border-bottom:1px solid...

JavaScript

/*jshint -W079*/

'use strict';

var Timetable = function() {
	this.scope = {
		hourStart: 9,
		hourEnd: 17
	};
	this.locations = [];
	this.events = [];
};

Timetable.Renderer = function(tt) {
	if (!(tt instanceof Timetable)) {
		throw new Error('Initialize renderer using a Timetable');
	}
	this.timetable = tt;
};

(function() {
	function isValidHourRange(start, end) {
		return isValidHour(start) && isValidHour(end);
	}
	function isValidHour(number) {
		return isInt(number) && isInHourRange(number);
	}
	function isInt(number) {
		return number === parseInt(number, 10);
	}
	function isInHourRange(number) {
		return number >= 0 && number < 24;
	}
	function locationExistsIn(loc, locs) {
		return locs.indexOf(loc) !== -1;
	}
	function isValidTimeRange(start, end) {
		var correctTypes = start instanceof Date && end instanceof Date;
		var correctOrder = start < end;
		return correctTypes && correctOrder;
	}
	function getDurationHours(startHour, endHour) {
		return endHour >= startHour ? endHour - startHour : 24 + endHour - startHour;
	}

	Timetable.prototype = {
		setScope: function(start, end) {
			if (isValidHourRange(start, end)) {
				this.scope.hourStart = start;
				this.scope.hourEnd = end;
			} else {
				throw new RangeError('Timetable scope should consist of (start, end) in whole hours from 0 to 23');
			}

			return this;
		},
		addLocations: function(newLocations) {
			function hasProperFormat() {
				return newLocations instanceof Array;
			}

			var existingLocations = this.locations;

			if (hasProperFormat()) {
				newLocations.forEach(function(loc) {
					if (!locationExistsIn(loc, existingLocations)) {
						existingLocations.push(loc);
					} else {
						throw new Error('Location already exists');
					}
				});
			} else {
				throw new Error('Tried to add locations in wrong format');
			}

			return this;
		},
		addEvent: function(name, location, start, end, options) {
			if (!locationExistsIn(location, this.locations)) {
				throw new...