Skip to content

Instantly share code, notes, and snippets.

@snydergd
Last active October 9, 2020 04:17
Show Gist options
  • Select an option

  • Save snydergd/146d4739527efe2b51fa1ae479689532 to your computer and use it in GitHub Desktop.

Select an option

Save snydergd/146d4739527efe2b51fa1ae479689532 to your computer and use it in GitHub Desktop.
Minimal Calendar
.minCal {
display: inline-table;
padding: 0.5em;
text-align: center;
width: 100%;
border: 1px solid black;
padding: 0;
background-color:white;
color: black;
}
.minCal td {
cursor: pointer;
}
.minCal h2 {
margin-left: 0.5em;
font-size: 14pt;
color: black;
}
.minCal-head {
font-size: 1.2em;
}
.minCal-today {
background-color: lightgreen;
}
.minCal td span {
display: table;
width: 100%;
}
.minCal-event span {
font-weight: bold;
border-bottom: 2px solid blue;
border-top: 2px solid blue;
}
.minCal-body {
border-collapse: collapse;
height: 10em;
border-bottom: 1px solid black;
}
.minCal-body-heading {
background-color: darkgrey;
}
.minCal-body-heading th,
.minCal-body-heading td {
border: 1px solid black;
}
.minCal-detail {
text-align: left;
}
.minCal button {
font-size: 1em;
width: 2em;
height: 100%;
}
.minCal-detail-heading,
.minCal-eventDetail p {
padding: 0.2em;
}
.minCal-detail-heading {
background-color: lightgrey;
font-size: 1.2em;
}
function loadTsvData(url) {
function map(data, callback) {
var ret = [];
var i;
var entry;
for (i = 0; i < data.length; i++) {
entry = callback(data[i]);
if (entry) {
ret.push(entry);
}
}
return ret;
}
return new Promise(function(resolve, reject) {
jQuery.ajax({
url: url,
success: function(data) {
data = map(data.split("\r\n"), function(line) {
return line.split("\t");
});
resolve(
map(data.splice(1, data.length), function(x) {
var r = {};
var i;
for (i = 0; i < x.length; i++) {
r[data[0][i]] = x[i];
}
return r;
})
);
}
});
});
}
/**
* Create a jQuery element containing a calendar.
*
* @param {object} $ - jQuery object used to create and find elements.
* @param {array} data - Array of objects which contains properties "name", "description", "start", "end"
* @return {object} jQuery element containing a calendar.
*
* @example
*
* var myEl = calendar($, [{"name": "test event", "start": "05/02/2020", "description": "a really general event", "end": "05/02/2020"}]);
* myEl.appendTo($("body"));
*/
function calendar($, data) {
var el = $(
'<div class="minCal">\
<div class="minCal-head" style="text-align: center">\
<button class="minCal-previous" style="float:left">&#x25C0;</button>\
<span class="minCal-yearSpan"></span>\
<button class="minCal-next" style="float: right">&#x25B6;</button>\
</div>\
<div class="minCal-body"></div>\
<div class="minCal-detail"></div>\
</div>'
);
var myEvents = {};
var i, j;
function strDate(date) {
function zeroPad(x) {
return (x < 10 ? "0" : "") + x;
}
if (typeof date == "string") {
var parts = date.split("/");
return new Date(
parseInt(parts[2]),
parseInt(parts[0]) - 1,
parseInt(parts[1])
);
} else {
return (
zeroPad(date.getMonth() + 1) +
"/" +
zeroPad(date.getDate()) +
"/" +
date.getFullYear()
);
}
}
for (i = 0; i < data.length; i++) {
var start, endTime;
try {
start = strDate(data[i].start);
} catch (e) {
console.log("Could not parse start date for calendar event", data[i], e);
continue;
}
try {
endTime = strDate(data[i].end).getTime();
} catch (e) {
console.log(
"End time could not be retrieved for calendar event",
data[i],
e
);
endTime = start.getTime() + 1;
}
if (!endTime) {
endTime = start.getTime() + 1;
}
for (
j = start;
j.getTime() <= endTime;
j = new Date(j.getFullYear(), j.getMonth(), j.getDate() + 1)
) {
if (!myEvents[strDate(j)]) {
myEvents[strDate(j)] = [];
}
myEvents[strDate(j)].push(data[i]);
}
}
function drawMonth(date) {
var i, j;
var month = date.getMonth();
var year = date.getFullYear();
var monthName = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
][new Date(year, month, 1).getMonth()];
var table = $('<table class="minCal-body" style="width:100%"></table>');
table.append(
'<tr class="minCal-body-heading"><th>S</th><th>M</th><th>T</th><th>W</th><th>R</th><th>F</th><th>S</th></tr>'
);
var row = $("<tr></tr>");
var first = new Date(year, month, 1).getDay();
if (first) row.append('<td colspan="' + first + '"></td>');
for (i = 0; i < new Date(year, month + 1, 0).getDate(); i++) {
if ((i + first) % 7 == 0) {
table.append(row);
row = $("<tr></tr>");
}
var sdate = strDate(new Date(year, month, i + 1));
var title = [];
var cell = $("<td></td>");
cell.append("<span>" + (i + 1) + "</span>");
cell.data("date", sdate);
if (myEvents[sdate]) {
cell.addClass("minCal-event");
for (j = 0; j < myEvents[sdate].length; j++) {
title.push(myEvents[sdate][j].name);
}
}
if (sdate == strDate(new Date())) {
cell.addClass("minCal-today");
title.splice(0, 0, "Today");
}
if (title.length) cell.attr("title", title.join("; "));
row.append(cell);
}
var remainder = (7 - new Date(year, month, i + 1).getDay()) % 7;
if (remainder) row.append('<td colspan="' + remainder + '"></td>');
table.append(row);
el.find(".minCal-body").replaceWith(table);
el.find(".minCal-yearSpan").text(monthName + " " + year);
}
var monthDate = new Date();
drawMonth(monthDate);
el.on("click", "button", function(e) {
if ($(this).hasClass("minCal-next")) {
drawMonth(
(monthDate = new Date(
monthDate.getFullYear(),
monthDate.getMonth() + 1
))
);
} else if ($(this).hasClass("minCal-previous")) {
drawMonth(
(monthDate = new Date(
monthDate.getFullYear(),
monthDate.getMonth() - 1
))
);
}
});
var details = el.find(".minCal-detail");
function selectDate(date) {
if (!(date in myEvents)) return;
var data = myEvents[date];
details.text("");
details.append($("<h2></h2>").text("Events for " + date + ":"));
var i;
for (i = 0; i < data.length; i++) {
var myEventStuff = $("<div></div>");
var itemData = data[i];
myEventStuff.addClass("minCal-eventDetail");
myEventStuff.append(
$("<div></div>")
.text(itemData.name)
.addClass("minCal-detail-heading")
);
myEventStuff.append($("<p></p>").html(itemData.description));
details.append(myEventStuff);
}
}
selectDate(strDate(new Date()));
el.on("click", ".minCal-event", function(e) {
selectDate($(this).data("date"));
e.stopPropagation();
});
el.on("click", function(e) {
if ($(e.target).closest(details).length == 0) details.text("");
});
return el;
}
/**
* Create a promise for a jQuery element containing a download link for an iCal file of data.
*
* @param {object} $ - jQuery object used to create and find elements.
* @param {array} data - Array of objects which contains properties "name", "description", "start", "end"
* @return {object} A promise for a link element that downloads ical of the data provided.
*
* @example
*
* var myEl = iCalDownloadLink($, [{"name": "test event", "start": "05/02/2020", "description": "a really general event", "end": "05/02/2020"}]);
* myEl.appendTo($("body"));
*/
function iCalDownloadLink($, data) {
// ics library: https://github.com/nwcell/ics.js/blob/dfec67f37a3c267b3f97dd229c9b6a3521222794/ics.min.js
/*! ics.js Wed Aug 20 2014 17:23:02 */
var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})}
var ics=function(e,t){"use strict";{if(!(navigator.userAgent.indexOf("MSIE")>-1&&-1==navigator.userAgent.indexOf("MSIE 10"))){void 0===e&&(e="default"),void 0===t&&(t="Calendar");var r=-1!==navigator.appVersion.indexOf("Win")?"\r\n":"\n",n=[],i=["BEGIN:VCALENDAR","PRODID:"+t,"VERSION:2.0"].join(r),o=r+"END:VCALENDAR",a=["SU","MO","TU","WE","TH","FR","SA"];return{events:function(){return n},calendar:function(){return i+r+n.join(r)+o},addEvent:function(t,i,o,l,u,s){if(void 0===t||void 0===i||void 0===o||void 0===l||void 0===u)return!1;if(s&&!s.rrule){if("YEARLY"!==s.freq&&"MONTHLY"!==s.freq&&"WEEKLY"!==s.freq&&"DAILY"!==s.freq)throw"Recurrence rrule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'";if(s.until&&isNaN(Date.parse(s.until)))throw"Recurrence rrule 'until' must be a valid date string";if(s.interval&&isNaN(parseInt(s.interval)))throw"Recurrence rrule 'interval' must be an integer";if(s.count&&isNaN(parseInt(s.count)))throw"Recurrence rrule 'count' must be an integer";if(void 0!==s.byday){if("[object Array]"!==Object.prototype.toString.call(s.byday))throw"Recurrence rrule 'byday' must be an array";if(s.byday.length>7)throw"Recurrence rrule 'byday' array must not be longer than the 7 days in a week";s.byday=s.byday.filter(function(e,t){return s.byday.indexOf(e)==t});for(var c in s.byday)if(a.indexOf(s.byday[c])<0)throw"Recurrence rrule 'byday' values must include only the following: 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'"}}var g=new Date(l),d=new Date(u),f=new Date,S=("0000"+g.getFullYear().toString()).slice(-4),E=("00"+(g.getMonth()+1).toString()).slice(-2),v=("00"+g.getDate().toString()).slice(-2),y=("00"+g.getHours().toString()).slice(-2),A=("00"+g.getMinutes().toString()).slice(-2),T=("00"+g.getSeconds().toString()).slice(-2),b=("0000"+d.getFullYear().toString()).slice(-4),D=("00"+(d.getMonth()+1).toString()).slice(-2),N=("00"+d.getDate().toString()).slice(-2),h=("00"+d.getHours().toString()).slice(-2),I=("00"+d.getMinutes().toString()).slice(-2),R=("00"+d.getMinutes().toString()).slice(-2),M=("0000"+f.getFullYear().toString()).slice(-4),w=("00"+(f.getMonth()+1).toString()).slice(-2),L=("00"+f.getDate().toString()).slice(-2),O=("00"+f.getHours().toString()).slice(-2),p=("00"+f.getMinutes().toString()).slice(-2),Y=("00"+f.getMinutes().toString()).slice(-2),U="",V="";y+A+T+h+I+R!=0&&(U="T"+y+A+T,V="T"+h+I+R);var B,C=S+E+v+U,j=b+D+N+V,m=M+w+L+("T"+O+p+Y);if(s)if(s.rrule)B=s.rrule;else{if(B="rrule:FREQ="+s.freq,s.until){var x=new Date(Date.parse(s.until)).toISOString();B+=";UNTIL="+x.substring(0,x.length-13).replace(/[-]/g,"")+"000000Z"}s.interval&&(B+=";INTERVAL="+s.interval),s.count&&(B+=";COUNT="+s.count),s.byday&&s.byday.length>0&&(B+=";BYDAY="+s.byday.join(","))}(new Date).toISOString();var H=["BEGIN:VEVENT","UID:"+n.length+"@"+e,"CLASS:PUBLIC","DESCRIPTION:"+i,"DTSTAMP;VALUE=DATE-TIME:"+m,"DTSTART;VALUE=DATE-TIME:"+C,"DTEND;VALUE=DATE-TIME:"+j,"LOCATION:"+o,"SUMMARY;LANGUAGE=en-us:"+t,"TRANSP:TRANSPARENT","END:VEVENT"];return B&&H.splice(4,0,B),H=H.join(r),n.push(H),H},download:function(e,t){if(n.length<1)return!1;t=void 0!==t?t:".ics",e=void 0!==e?e:"calendar";var a,l=i+r+n.join(r)+o;if(-1===navigator.userAgent.indexOf("MSIE 10"))a=new Blob([l]);else{var u=new BlobBuilder;u.append(l),a=u.getBlob("text/x-vCalendar;charset="+document.characterSet)}return saveAs(a,e+t),l},build:function(){return!(n.length<1)&&i+r+n.join(r)+o}}}console.log("Unsupported Browser")}};
var cal = ics();
var i;
for (i = 0; i < data.length; i++) {
cal.addEvent(data[i].name, $("<span></span>").html(data[i].description).text(), "", data[i].start, data[i].end);
}
var el = $("<a href=\"#\">&#x2b07;</a>");
el.on("click", function(e) {
cal.download("marbcEvents");
});
window.caldata = cal;
return el;
}
function getCampCalendar(jq, url) {
return loadTsvData(
url
).then(function(data) {
var $ = jq || jQuery;
var ret = $("<div></div>");
calendar($, data).appendTo(ret);
iCalDownloadLink($, data).text("Download Calendar as iCal").appendTo(ret);
return ret;
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment