block by dehuszar 1c416b0703117ab440c6

Calendar Component (LESS, no framework)

Full Screen

script.js

var upcomingEventsFullCalendar = document.getElementsByClassName('upcoming-events')[0].getElementsByClassName('dates');

var calendar = {
	create: function create(target, dates, events, showNextPrevDates) {

		/*var defaults = {
			target: null, // needs to return an error if not entered
			dates: calendar.months.currentMonthDays,
			events: null, // optional
			showNextPrevDates: true
		};*/
		
		target[0].innerHTML = "";
		
		if (showNextPrevDates) {
			var prevMonthDays = calendar.months.previousMonthDays();
			var nextMonthDays = calendar.months.nextMonthDays();

			dates = prevMonthDays.concat(dates, nextMonthDays);
		}
	
		for (var i = 0; i < dates.length; i++) {
			// check events list for a matching date.
			// if there's a match, normalize the data 
			// and push into an `events: []` property
			var currentItem = moment(dates[i]._d).format("YYYY-MM-DD");
			var todaysEvents = [];
			var date;
			
			events.find(function(element, index, array) {
				if (element.date === this) {
					for (var i in element.events) {
						todaysEvents.push(element.events[i]);
					};
				}
			}, currentItem);
			
			date = calendar.dates.buildDate(dates[i], todaysEvents);
			$(target).parent().find(".calendar-header").html(moment(calendar.dates.today).format("MMMM, YYYY"));
			$(target).append(date);
		}
	},
	dates: {
		buildDate: function buildDate(obj, events) {
			// compile handlebars templates with merged date and event data
			var source = $("#date-template").html();
			var template = Handlebars.compile(source);
			var context = {};
			var html;
			var date = obj._d || moment(obj.date)._d;
			
			if (moment(date).month() < moment(calendar.dates.today).month()) {
				context.month = "prev-month";
			} else if (moment(date).month() === moment(calendar.dates.today).month()) {
				context.month = "";
			} else if (moment(date).month() > moment(calendar.dates.today).month()) {
				context.month = "next-month";
			}
			
			context.today = (moment(date).format("YYYY-MM-DD") ===
							 moment(calendar.dates.today).format("YYYY-MM-DD"));
			context.dateNum = moment(date).format("D");
			(events.length) ? context.hasEvents = true : context.hasEvents = false;
			
			if (context.hasEvents) {
				context.events = events;
			}
			
			html = template(context);
			
			return html;
		},
		currentDate: function currentDate() {
			return calendar.dates.today.getFullYear() + 
				'-' +
				('0' + (calendar.dates.today.getMonth()+1)).slice(-2) +
				'-' + ('0' + calendar.dates.today.getDate()).slice(-2);
		},
		today: new Date()
	},
	events: {
		list: [],
		getEvents: function getEvents(array) {
			// each obj in array must include
			// {"name": "holidays",
			// "url": "http://api.domain.com",
			// "data": {args},
			// "returnObj" String (optional)
			// "transform"} Function (optional) :: for altering odd events obj/arr to our internal standards
			// if the response returns the data behind a param, the data obj must
			// contain a attr "returnObj" which maps to the responses's param
			
			for (var i = 0; i < array.length; i++) {
				var current = array[i];

				$.ajax({
					"type": "POST",
					"url": current.url,
					"data": current.data,
					"success": function(data) {
						var events = data[current.returnObj];
						// parse returnObj contents, make sure dates are sorted and valid dates,
						// and map them to an internal standard format for display
						
						// TODO :: migrate this map into a named transform function for Holidays
						if (typeof events === "object") {
							var convertedObj = $.map(events, function(values, index) {
								for (var i in values) {
									values[i].allDay = true
								}
								
								return {
									date: index,
									events: values
								};
							});
							
							events = convertedObj;
						}
						
						for (var i in events) {
							calendar.events.list.push(events[i]);
						}
						
						// TODO :: put this in the getEvents promise `then()`
						calendar.create(upcomingEventsFullCalendar, 
										calendar.months.currentMonthDays(),
										calendar.events.list,
										true);
					}
				});
			}
			
			// If there's more than one source, merge and sort the events data responses together
			// i.e. array1.concat(array2, array3,..., arrayX)
		}
	},
	months: {
		currentMonthDays: function currentMonthDays() {
			var arr = [];

			for (var i = 1; i <= calendar.months.lastOfMonth().date(); i++) {
				arr.push(moment().date(i));
			}

			return arr;
		},
		firstOfMonth: moment().date(1),
		firstOfMonthWeekday: moment().date(1).weekday(),
		lastOfMonth: function lastOfMonth() {
			return moment().month(calendar.months.nextMonth).date(0);
		},
		nextMonth: moment().month() + 1,
		nextMonthDays: function nextMonthDays() {
			var arr = [];

			for (var i = 1; i <= (7 - calendar.months.nextMonthStartDay()); i++) {
				arr.push(moment().month(calendar.months.nextMonth).date(i));
			}

			return arr;
		},
		nextMonthStartDay: function nextMonthStartDay() {
			return moment().month(calendar.months.nextMonth).date(1).weekday();
		},
		previousMonthDays: function previousMonthDays() {
			var arr = [];
			
			for (var i = 0; i < calendar.months.firstOfMonthWeekday; i++) {
				arr.push(moment().date(1).weekday(i));
			}
			
			return arr;
		},
		prevMonthStartDay: moment().date(1).weekday(0)
	}
}

var dates = calendar.months.currentMonthDays();

// event sources
calendar.events.getEvents(
	[
		{
			"name": "holidays",
			"url": "http://holidayapi.com/v1/holidays",
			"data": {
				"country": "US",
				"year": 2015
			},
			"returnObj": "holidays"
		}
	]
);

index.html

<section class="upcoming-events">
	<header>
		<h1 class="calendar-header">
			<!-- calendar header goes here -->
		</h1>
		<ol class="days">
			<li class="day" data-letter="S" data-abbr="Sun">Sunday</li>
			<li class="day" data-letter="M" data-abbr="Mon">Monday</li>
			<li class="day" data-letter="T" data-abbr="Tue">Tuesday</li>
			<li class="day"  data-letter="W" data-abbr="Wed">Wednesday</li>
			<li class="day"  data-letter="T" data-abbr="Thu">Thursday</li>
			<li class="day"  data-letter="F" data-abbr="Fri">Friday</li>
			<li class="day"  data-letter="S" data-abbr="Sat">Saturday</li>
		</ol>
	</header>
	<ol class="dates">
		<!-- dates go here -->
	</ol>
</section>

<!-- this gets parsed by our calendarBuilder -->
<script id="date-template" type="text/x-handlebars-template">
	<li data-month="{{month}}" data-today="{{today}}" data-has-events="{{hasEvents}}">
		<div class="details">
			<span class="date">{{dateNum}}</span>
			{{#if hasEvents}}
				{{#each events as |event|}}
					<span class="event" {{#if event.allDay}}data-all-day-event="{{event.allDay}}"{{/if}}>
						{{event.name}}
					</span>
				{{/each}}
			{{/if}}
		</div>
	</li>
</script>

Calendar Component (LESS, no framework).markdown

Calendar Component (LESS, no framework)
---------------------------------------
This is an example of how LESS can be used to create a semantically remapped calendar component.

A [Pen](http://codepen.io/dehuszar/pen/Yywgqj) by [dehuszar](http://codepen.io/dehuszar) on [CodePen](http://codepen.io/).

[License](http://codepen.io/dehuszar/pen/Yywgqj/license).

scripts

<script src="//cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/18728/moment.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.0/handlebars.min.js"></script>

style.css

// layouts
@import (reference) "https://s3-us-west-2.amazonaws.com/s.cdpn.io/18728/calendar-agenda.less";
@import (reference) "https://s3-us-west-2.amazonaws.com/s.cdpn.io/18728/calendar-month-grid-full.less";

html {
	font-family: 'PT Sans Narrow', sans-serif;
}

.upcoming-events {
	
	.days li {
		display: inline-block;
		position: relative;
		visibility: hidden;
		
		&:after {
			content: attr(data-letter);
			display: inline-block;
			left: 0;
			position: absolute;
			visibility: visible;
			width: 100%;
		}
		
		@media (min-width: 800px) {
			
			&:after {
				content: attr(data-abbr);
			}
		}
		
		@media (min-width: 960px) {
			visibility: visible;
			
			&:after {
				content: ""
			}
		}
	}

	.dates li {
		position: relative;

		.details {
			left: 0;
			position: relative;
			height: 100%;
			top: 0;
			width: 100%;
			z-index: 1;
		}

		&:not([data-month="prev-month"]):not([data-month="next-month"]) .details {
			background: rgba(255, 255, 255, 0.75);
		}

		&:after {
			background: rgba(0, 0, 0, 0.25);
			content: "";
			position: absolute;
			height: 100%;
			left: 0;
			top: 0;
			width: 100%;
			z-index: 0;
		}

		&[data-has-events="true"]:not([data-month="prev-month"]):not([data-month="next-month"]):after {
			background: rgba(26, 146, 239, 0.75);
		}
		
		&[data-today="true"]:after {
			background: rgba(255, 255, 255, 1)
		}
	}
	
	// this agenda layout starts goes from 'mobile first' to 767px
	&:extend(.agenda all);
	
	// this grid layout div starts at 768px
	&:extend(.month-grid-full all);
}