This is an example of how LESS can be used to create a semantically remapped calendar component.
Last active
December 9, 2015 20:26
-
-
Save dehuszar/1c416b0703117ab440c6 to your computer and use it in GitHub Desktop.
Calendar Component (LESS, no framework)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | |
} | |
] | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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