Last active
October 9, 2020 04:17
-
-
Save snydergd/146d4739527efe2b51fa1ae479689532 to your computer and use it in GitHub Desktop.
Minimal Calendar
This file contains hidden or 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
| .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; | |
| } |
This file contains hidden or 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
| 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">◀</button>\ | |
| <span class="minCal-yearSpan"></span>\ | |
| <button class="minCal-next" style="float: right">▶</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=\"#\">⬇</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