Skip to content

Instantly share code, notes, and snippets.

@dehuszar
Last active December 9, 2015 20:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dehuszar/1c416b0703117ab440c6 to your computer and use it in GitHub Desktop.
Save dehuszar/1c416b0703117ab440c6 to your computer and use it in GitHub Desktop.
Calendar Component (LESS, no framework)

Calendar Component (LESS, no framework)

This is an example of how LESS can be used to create a semantically remapped calendar component.

A Pen by dehuszar on CodePen.

License.

<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>
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"
}
]
);
<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>
// 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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment