layout | title |
---|---|
default |
Calendar |
// Get body of request
res.text().then((str) => {
try {
// Parse with ical.js
let parsed = ICAL.parse(str);
// Helpers for month text
const monthText = document.querySelector('.render-range');
const updateMonthText = () => {
monthText.innerText = new Intl.DateTimeFormat('en-US', { month: 'long', year: 'numeric' }).format(calendar.getDate().toDate());
}
// Initialize calendar
let calendar = new tui.Calendar('#calendar', {
defaultView: 'month',
isReadOnly: true,
usageStatistics: false,
useCreationPopup: false,
useDetailPopup: true
});
// Bind buttons to calendar
document.querySelector('[data-action="move-today"]').addEventListener('click', () => { calendar.today(); updateMonthText(); });
document.querySelector('[data-action="move-next"]').addEventListener('click', () => { calendar.next(); updateMonthText(); });
document.querySelector('[data-action="move-prev"]').addEventListener('click', () => { calendar.prev(); updateMonthText(); });
updateMonthText();
// Array of events to eventually be loaded into the calendar
let evtArr = [];
// Iterate through events from iCal feed
parsed[2].forEach((i, no) => {
// Define base parameters
let obj = {
bgColor: 'black',
body: '',
category: 'time',
color: 'white',
id: `${no}`,
isReadOnly: true,
isVisible: true
};
// Define vars for special cases
let rrule = false;
let url = '';
// Iterate through parameters and map to object properties
// (Convert iCal property names to tui.calendar property names)
i[1].forEach((j) => {
switch (j[0]) {
case 'summary':
obj.title = j[3];
break;
case 'description':
obj.body = j[3];
break;
case 'dtstart':
// ical.js adds empty time strings if an all-day event
// strip these and set all-day properties instead if needed
if(j[3].endsWith('T::')) {
obj.category = 'allday';
obj.isAllDay = true;
obj.start = j[3].replace('T::','');
} else {
obj.start = j[3];
}
break;
case 'dtend':
// Strip empty time strings if needed (see dtstart comments)
obj.end = j[3].replace('T::','');
break;
case 'location':
obj.location = j[3];
break;
case 'rrule':
// RRULE will get interpreted from object later
// Just set special case var to trigger parsing later
rrule = true;
break;
case 'url':
url = j[3];
break;
}
});
// If has URL, append to body (since no field for in details popup)
if(url !== '') {
obj.body += `${(obj.body === '' ? '' : '<br>')}<a href="${url}">${url}</a>`;
}
// tui.calendar can't handle RRULEs (recurrence rules)
// So, if an RRULE is set, use ical.js to expand occurances manually
if(rrule) {
// Create ical.js component from jCal output of parse
let comp = new ICAL.Component(i);
// Figure out the duration of the event (since can't directly expand start & end at the same time)
let duration = ICAL.Time.fromString(obj.end).subtractDate(ICAL.Time.fromString(obj.start));
// Create RRULE Expansion with ical.js
// (Creates an interable)
let expand = new ICAL.RecurExpansion({
component: comp,
dtstart: comp.getFirstPropertyValue('dtstart')
});
// Counter to prevent infinite iteration of RRULE (since by spec is allowed)
let count = 0;
// next defined here b/c of block scoping
let next;
// Iterate through occurances
// Arbitrary limit of 25 is for infinite iteration prevention
while(count < 25 && (next = expand.next())) {
// Increment infinite iteration prevention counter
count++;
// Duplicate event with RRULE
let o = {};
Object.assign(o, obj);
// Give unique ID
o.id += `::RRULE-n${count}`;
// Set start to this occurance from RRULE expansion
o.start = next.toString();
// Reconstruct end from duration and set
let end = ICAL.Time.fromJSDate(next.toJSDate());
end.addDuration(duration);
o.end = end.toString();
// Add to events array
evtArr.push(o);
}
} else {
// If no RRULE, directly add to events array
evtArr.push(obj);
}
});
// Add events in array to calendar
calendar.createSchedules(evtArr);
} catch(err) {
// Error on parsing fail
document.querySelector('#calendar').innerText = `Could not parse: ${err}`;
return;
}
}).catch(() => {
// Error on load fail
document.querySelector('#calendar').innerText = 'Could not load feed (no body)';
return;
});
}); </script>