function CalendarDate (existingLi) { if (existingLi) { this.li = existingLi; this.fromHTML(); } else this.li = document.createElement('li'); } CalendarDate.prototype = { lang : { de : { monthNames:['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'] }, en : { monthNames:['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] }, get : function (key) { if (!this.l) { this.l = navigator.language; if (!this[this.l]) this.l = 'en'; } return (this[this.l][key])?this[this.l][key]:key; } }, // public readonly li : null, start : null, end : null, sourceId : -1, completeDay : false, title : '', location : '', description : '', url : '', status : '', repeatMode : 0, repeatNr : 0, repeatEnd : null, parseRrule : /* local */ function (rrule) { if (rrule == '') return; rrule = rrule.split(';'); for (var k = 0; k < rrule.length; k++) { rrule[k] = rrule[k].toUpperCase().split('='); if (rrule[k].length == 1) continue; switch (rrule[k][0]) { case 'FREQ': if (rrule[k][1] == 'MONTHLY' && this.repeatMode == 0) this.repeatMode = 1; if (rrule[k][1] == 'MONTHLY' && this.repeatMode == -3) this.repeatMode = 3; if (rrule[k][1] == 'DAILY') this.repeatMode = 2; break; case 'UNTIL': if (rrule[k][1].length != 8) break; var y = parseInt(rrule[k][1].substring(0, rrule[k][1].length-4)); var m = parseInt(rrule[k][1].substring(rrule[k][1].length-4, rrule[k][1].length-2)); var d = parseInt(rrule[k][1].substring(rrule[k][1].length-2, rrule[k][1].length)); this.repeatEnd = new Date(y, m-1, d, 0, 0, 0, 0); break; case 'INTERVAL': if (this.repeatNr == 0) this.repeatNr = parseInt(rrule[k][1]); else this.repeatNr = -parseInt(rrule[k][1]); break; case 'BYDAY': if (this.repeatMode == 0) this.repeatMode = -3; // will be set to +3 when FREQ is monthly. else if (this.repeatMode == 1) this.repeatMode = 3; if (parseInt(rrule[k][1]) < 0) { if (this.repeatNr == 0) this.repeatNr = -Number.MAX_VALUE; // just remember to make value negative for INTERVAL parsing else this.repeatNr = -this.repeatNr; } break; } } if (this.repeatMode < 0) this.repeatMode = 0; // invalid combination of rules }, getMonthRepetitionI : /* private */ function (currentDateTime) { if (this.completeDay) var spl = new Array(this.start.getUTCFullYear(), this.start.getUTCMonth()+1, this.start.getUTCDate()); else var spl = new Array(this.start.getFullYear(), this.start.getMonth()+1, this.start.getDate()); var repI = spl[0]*32*12 + (spl[1]-1)*32 + spl[2]; var currI = currentDateTime.getFullYear()*32*12 + (currentDateTime.getMonth()-1)*32 + currentDateTime.getDate(); if (currI > repI) repI += Math.ceil((currI - repI + 0.0) / (this.repeatNr*32)) * this.repeatNr*32; return repI; }, lastDayOfMonth : /* private */ function (Y, m) { if (m == 4 || m == 6 || m == 9 || m == 11) return 30; else if (m == 2) { if ((Y % 4) == 0 && ((Y % 100) != 0 || (Y % 400) == 0)) return 29; else return 28; } else return 31; }, getNearestRepetition : /* local */ function (currentDateTime) { switch (this.repeatMode) { case 1: // every n month repeating event if (this.repeatNr <= 0) break; var repI = this.getMonthRepetitionI(currentDateTime); for (var i = 0; i < 100; i += this.repeatNr) { // if after adding 8 years, no valid date is found, it may never exist! var Y = Math.floor((repI >> 5) / 12); var m = (repI >> 5) % 12 + 1; var d = repI & 31; var checkdate = null; try { checkdate = new Date(Y, m-1, d, this.start.getHours(), this.start.getMinutes(), 0, 0); } catch (e) { checkdate = null; } if (checkdate && checkdate.getFullYear() == Y && checkdate.getMonth() == m-1 && checkdate.getDate() == d) { if (this.repeatEnd && checkdate > this.repeatEnd) break; if (checkdate >= currentDateTime) return checkdate; } repI += this.repeatNr*32; } break; case 2: // every n days repeating event if (this.repeatNr <= 0) break; var result = new Date(this.start); var th = result.getHours(); var tm = result.getMinutes(); result.setHours(12); // set to midday, to prevent day missmatch due to summer/winter time gap result.setMinutes(0); if (currentDateTime > this.start) { var dayDiff = Math.floor((currentDateTime - this.start) / (1000 * 3600 * 24)); result.setDate(result.getDate() + (Math.ceil((dayDiff+0.1) / this.repeatNr)*this.repeatNr)); } if (this.repeatEnd && result > this.repeatEnd) break; result.setHours(th); result.setMinutes(tm); return result; case 3: if (this.repeatNr == 0) break; var weekday = this.start.getDay(); if (this.repeatNr > 0) { var ld = 1; var weekNr = Math.floor((this.start.getDate()-1) / 7); } else { var ld = this.lastDayOfMonth(this.start.getFullYear(), this.start.getMonth()+1); var weekNr = Math.floor((ld - this.start.getDate()) / 7); } var repI = this.getMonthRepetitionI(currentDateTime); for (var i = 0; i < 100; i++) { var Y = Math.floor((repI >> 5) / 12); var m = (repI >> 5) % 12 + 1; if (this.repeatNr < 0) ld = this.lastDayOfMonth(Y, m); var result = new Date(Y, m-1, ld, this.start.getHours(), this.start.getMinutes(), 0, 0); if (this.repeatNr > 0) result.setDate(result.getDate() + (((weekday + 7 - result.getDay()) % 7) + weekNr * 7)); else result.setDate(result.getDate() - (weekNr * 7 + ((result.getDay() - weekday + 7) % 7))); if (this.repeatEnd && result > this.repeatEnd) break; if ((result >= currentDateTime) && (result.getMonth()+1 == m)) return result; repI += 32 * Math.abs(this.repeatNr); } break; } result = new Date(this.start); return result; }, fromHTML : /* private */ function () { if (this.start) throw 'Date already created!'; this.completeDay = true; var span = this.li.getElementsByTagName('span'); for (var j = 0; j < span.length; j++) { var className = ' '+span[j].className+' '; if (className.indexOf(' date ') != -1) { // parse date d = span[j].textContent.split('.'); if (d.length != 3) d.unshift('-1'); d[0] = Number.parseInt(d[0]); d[1] = Number.parseInt(d[1]); d[2] = Number.parseInt(d[2]); if (d[0] > 31) d.reverse(); this.start = new Date(d[2], d[1]-1, d[0], 0, 0, 0, 0); } else if (className.indexOf(' title ') != -1) this.title = span[j].textContent; else if (className.indexOf(' location ') != -1) this.location = span[j].textContent; else if (className.indexOf(' description ') != -1) this.description = span[j].textContent; } if (this.li.getElementsByTagName('a').length > 0) this.url = this.li.getElementsByTagName('a')[0].href; }, updateHTML : /* public */ function () { var m = this.start.getMinutes().toString(); if (m.length == 1) m = '0'+m; var a_begin = ''; var a_end = ''; var hostAndProtocol = window.location.href.substring(0, window.location.href.indexOf(window.location.hostname))+window.location.hostname; if (this.url) { var target = '_blank'; if (this.url.indexOf(hostAndProtocol) == 0) target = '_self'; a_begin = ''+"\n"; a_end = '<\/A>'+"\n"; } this.li.innerHTML = a_begin +'
'+"\n" +'
'+"\n" +' '+this.start.getDate()+'. '+this.lang.get('monthNames')[this.start.getMonth()]+' '+this.start.getFullYear()+'<\/SPAN>'+"\n" +((this.completeDay)?' '+this.start.getHours()+':'+m+'<\/SPAN>'+"\n":'') +' <\/DIV>'+"\n" +'
'+"\n" +' '+this.makeupText(this.title)+'<\/SPAN>'+"\n" +' '+this.makeupText(this.location)+'<\/SPAN>'+"\n" +' '+this.makeupText(this.description)+'<\/SPAN>'+"\n" +' <\/DIV>'+"\n" +'<\/DIV>'+"\n" +a_end; }, makeupText : /* private */ function (text) { if (!text) text = ''; text = text.replace(/&/g, '&'); text = text.replace(//g, '>'); text = text.replace(/\n/g, '
'); text = text.split(' '); for (var i = 0; i < text.length; i++) { if (text[i].substring(0, 8) == 'https:\/\/' || text[i].substring(0, 7) == 'http:\/\/') { if (i > 0 && text[i-1].substring(text[i-1].length-1, text[i-1].length) == ':') { text[i-1] = '
'+text[i-1].substring(0, text[i-1].length-1)+'<\/A>'; text[i] = ''; } else text[i] = ''+text[i]+'<\/A>'; } } return text.join(' '); } }; function ICSCalendar (calendarBox) { var _this = this; this.calendarBox = calendarBox; this.dates = new Array(); this.sources = new Array(); // find or create ul if (!this.calendarBox) throw 'No calendar element given!'; if (calendarBox.calendarObject) throw 'Calendar for given element already created!'; calendarBox.calendarObject = this; if (this.calendarBox.getElementsByTagName('ul').length > 0) this.ul = this.calendarBox.getElementsByTagName('ul')[0]; if (!this.ul) { this.ul = document.createElement('ul'); this.calendarBox.appendChild(this.ul); } else { for (var i = 0; i < this.ul.getElementsByTagName('li').length; i++) { var ndate = new CalendarDate(this.ul.getElementsByTagName('li')[i]); ndate.updateHTML(); this.dates.push(ndate); } this.ul.innerHTML = ''; } this.ul.className = 'dates'; this.filterBox = document.createElement('div'); this.filterBox.style.display = 'none'; this.filterLink = document.createElement('a'); this.filterLink.className = 'button'; this.filterLink.href = 'javascript: /* show filter */'; this.filterLink.textContent = this.lang.get('filterlink'); this.filterLink.cal = this; this.filterLink.addEventListener('click', function () { this.cal.filterBox.style.display = 'block'; this.cal.filterLink.style.display = 'none'; }, false); this.calendarBox.insertBefore(this.filterBox, this.ul); this.calendarBox.insertBefore(this.filterLink, this.ul); // interpret calendar settings var srcs = this.calendarBox.getElementsByTagName('source'); function getAttributeOrEmpty(srcEl, attr) { var r = srcEl.getAttribute(attr); if (!r) r = ''; return r; } for (var i = 0; i < srcs.length; i++) { var nsrc = { state : 0, // 0=never fetched 1=currently loading 2=successfully fetched, 3=error src : getAttributeOrEmpty(srcs[i], 'src'), generator : getAttributeOrEmpty(srcs[i], 'generator'), user : getAttributeOrEmpty(srcs[i], 'user'), password : getAttributeOrEmpty(srcs[i], 'password'), selected : { promoters : getAttributeOrEmpty(srcs[i], 'filterPromoters').split(','), categories : getAttributeOrEmpty(srcs[i], 'filterCategories').split(',') }, filterTypes : { // 0=no filter 1=whitelist 2=blacklist promoters : 1, categories : 1 } }; if (nsrc.src.indexOf('./') == 0) { var spl = window.location.href.split('?')[0].split('#')[0].split('/'); if (spl[spl.length-1].lastIndexOf('.htm') >= spl[spl.length-1].length-5 || spl[spl.length-1] == '') spl.pop(); nsrc.src = spl.join('/')+'/'+nsrc.src.substring(2); } for (var f in nsrc.selected) { if (nsrc.selected[f][0].indexOf('~') == 0) { nsrc.selected[f][0] = nsrc.selected[f][0].substring(1); nsrc.filterTypes[f] = 2; } while (nsrc.selected[f].length > 0 && nsrc.selected[f][0] == '') nsrc.selected[f].shift(); for (var j = 0; j < nsrc.selected[f].length; j++) nsrc.selected[f][j] = parseInt(nsrc.selected[f][j]); if (nsrc.selected[f].length == 0) nsrc.filterTypes[f] = 0; } this.sources.push(nsrc); if (nsrc.generator == 'phpBB-calendar') { // autodetect nsrc.src.lastIndexOf('\/app.php\/calendar\/') == nsrc.src.length - 18 if (!this.earlierDatesEl) { this.earlierDatesEl = document.createElement('a'); this.earlierDatesEl.href = 'javascript:'; this.earlierDatesEl.addEventListener('click', function () { _this.loadEarlierDates(); }); this.earlierDatesEl.className = 'earlierDatesLink button'; this.earlierDatesEl.textContent = this.lang.get('earlierDates'); this.calendarBox.appendChild(this.earlierDatesEl); } nsrc.shortSrc = nsrc.src; this.getFilterInfo(this.sources.length-1); this.updateUrl(this.sources.length-1, false); } else { // create sync link for fixed url ics this.getSimpleInfo(this.sources.length-1); } } this.update(); } ICSCalendar.prototype = { lang : { de : { filterError:'Fehler beim ermitteln der Filter-Möglichkeiten.', promoters:'Veranstalterinnen', categories:'Kategorien', update:'aktualisieren', all:'alle', whitelist:'nur die Folgenden...', blacklist:'alle außer...', synchint:'Synchronisieren Sie die folgende URL als iCalendar, um die Termine in andere Anwendungen zu übernehmen.', filterlink:'Filtern und Synchronisieren', timeRange:'Zeitfenster', timeRangeSince:'Alle ab # Tagen vor heute', timeRangeDates:'Von # bis #', earlierDates:String.fromCharCode(8595)+' frühere Termine '+String.fromCharCode(8595) }, en : { filterError:'Error getting filter information.', promoters:'Promoters', categories:'Categories', update:'update', all:'all', whitelist:'just the following...', blacklist:'all except of...', synchint:'Synchronize the following URL as a iCalendar, to get the dates into other applications.', filterlink:'filter and synchronize', timeRange:'time range', timeRangeSince:'All since # days before now', timeRangeDates:'From # til #', earlierDates:String.fromCharCode(8595)+' earlier dates '+String.fromCharCode(8595) }, get : function (key) { if (!this.l) { this.l = navigator.language; if (!this[this.l]) this.l = 'en'; } return (this[this.l][key])?this[this.l][key]:key; } }, // private calendarBox : null, ul : null, filterBox : null, filterLink : null, dates : null, sources : null, earlierDatesEl : null, loadEarlierDates : /* private */ function () { var _this = this; for (var sid = 0; sid < this.sources.length; sid++) { if (!this.sources[sid].earliestDate) continue; var to = this.sources[sid].earliestDate; var from = new Date(to); from.setDate(to.getDate() - parseInt(this.sources[sid].filterInfo.maxDays)); this.sources[sid].earliestDate = from; var src = this.sources[sid].src.split('?'); if (src.length == 1) src.push(); src[1] = src[1].split('&'); for (var i = src[1].length-1; i >= 0; i--) if (src[1][i].indexOf('from=') == 0 || src[1][i].indexOf('to=') == 0) src[1].splice(i, 1); src[1].push('from='+from.getFullYear()+'-'+(from.getMonth()+1)+'-'+from.getDate()); src[1].push('to='+to.getFullYear()+'-'+(to.getMonth()+1)+'-'+to.getDate()); src[1] = src[1].join('&'); this.sources[sid].src = src.join('?'); this.loadSource(sid, function (_sid) { _this.addDatesFromSrc(_sid); _this.sortAndPrintDates(); }); } }, filterHtmlVisualization : /* private */ function (sid, key) { function radioChange () { if (this.elementsDeactivate) for (var i = 0; i < this.elementsDeactivate.length; i++) for (var j = 0; j < this.elementsDeactivate[i].getElementsByTagName('input').length; j++) this.elementsDeactivate[i].getElementsByTagName('input')[j].disabled = this.checked; if (this.elementsActivate) for (var i = 0; i < this.elementsActivate.length; i++) for (var j = 0; j < this.elementsActivate[i].getElementsByTagName('input').length; j++) this.elementsActivate[i].getElementsByTagName('input')[j].disabled = !this.checked; } var filter = this.sources[sid].filterInfo[key]; var col = document.createElement('form'); col.setAttribute('filterKey', key); var b = document.createElement('b'); b.textContent = this.lang.get(key)+':'; col.appendChild(b); var radioRow = document.createElement('div'); radioRow.className = 'radioRow'; col.appendChild(radioRow); if (key == 'timeRange') { var l = document.createElement('label'); var radio = document.createElement('input'); radio.checked = true; radio.setAttribute('name', 'timeRangeType'); radio.setAttribute('type', 'radio'); l.appendChild(radio); var spl = this.lang.get('timeRangeSince').split('#'); spl.push(''); l.appendChild(document.createTextNode(spl[0])); l.style.marginRight = '0px'; radioRow.appendChild(l); var inp = document.createElement('input'); inp.setAttribute('type', 'number'); inp.setAttribute('name', 'days'); var maxDays = parseInt(this.sources[sid].filterInfo.maxDays); inp.setAttribute('max', maxDays); inp.value = Math.ceil(maxDays/2); inp.defaultValue = inp.value; this.sources[sid].earliestDate = new Date(); this.sources[sid].earliestDate.setDate(this.sources[sid].earliestDate.getDate() - parseInt(inp.value)); inp.style.width = '30px'; radioRow.appendChild(inp); l = document.createElement('label'); l.textContent = spl[1]; radioRow.appendChild(l); radioRow = document.createElement('div'); radioRow.className = 'radioRow'; l = document.createElement('label'); radio = document.createElement('input'); radio.checked = false; radio.setAttribute('name', 'timeRangeType'); radio.setAttribute('type', 'radio'); l.appendChild(radio); spl = this.lang.get('timeRangeDates').split('#'); spl.push(''); spl.push(''); l.appendChild(document.createTextNode(spl[0])); l.style.marginRight = '0px'; radioRow.appendChild(l); inp = document.createElement('input'); inp.setAttribute('type', 'date'); inp.setAttribute('name', 'dateFrom'); radioRow.appendChild(inp); l = document.createElement('label'); l.textContent = spl[1]; l.style.marginRight = '0px'; radioRow.appendChild(l); inp = document.createElement('input'); inp.setAttribute('type', 'date'); inp.setAttribute('name', 'dateTo'); radioRow.appendChild(inp); l = document.createElement('label'); l.textContent = spl[2]; radioRow.appendChild(l); col.appendChild(radioRow); } else { var list = document.createElement('div'); var l = document.createElement('label'); var radio = document.createElement('input'); radio.setAttribute('name', 'filtertype'); radio.checked = this.sources[sid].filterTypes[key] == 0; radio.setAttribute('type', 'radio'); radio.addEventListener('change', radioChange); radio.elementsDeactivate = [list]; l.appendChild(radio); l.appendChild(document.createTextNode(this.lang.get('all'))); radioRow.appendChild(l); l = document.createElement('label'); radio = document.createElement('input'); radio.checked = this.sources[sid].filterTypes[key] == 1; radio.setAttribute('name', 'filtertype'); radio.setAttribute('type', 'radio'); radio.addEventListener('change', radioChange); radio.elementsActivate = [list]; l.appendChild(radio); l.appendChild(document.createTextNode(this.lang.get('whitelist'))); radioRow.appendChild(l); l = document.createElement('label'); radio = document.createElement('input'); radio.checked = this.sources[sid].filterTypes[key] == 2; radio.setAttribute('name', 'filtertype'); radio.setAttribute('type', 'radio'); radio.addEventListener('change', radioChange); radio.elementsActivate = [list]; l.appendChild(radio); l.appendChild(document.createTextNode(this.lang.get('blacklist'))); radioRow.appendChild(l); col.appendChild(list); list.className = 'filterList'; for (var i = 0; i < filter.length; i++) { var label = document.createElement('label'); var cb = document.createElement('input'); cb.checked = this.sources[sid].selected[key].indexOf(filter[i].id) >= 0; cb.disabled = true; cb.type = 'checkbox'; cb.setAttribute('filterId', filter[i].id); label.appendChild(cb); label.appendChild(document.createTextNode(filter[i].title)); list.appendChild(label); } } return col; }, rmHTTP : /* private */ function (url) { if (url.toLowerCase().indexOf('http:\/\/') == 0) return url.substr(7); if (url.toLowerCase().indexOf('https:\/\/') == 0) return url.substr(8); return url; }, updateUrl : /* private */ function (sid, updateCalendar) { var url = this.sources[sid].shortSrc+'?action=ics'; if (this.sources[sid].user != '') url += '&user='+encodeURIComponent(this.sources[sid].user)+'&password='+encodeURIComponent(this.sources[sid].password); if (this.sources[sid].settingsEl) { var cols = this.sources[sid].settingsEl.getElementsByTagName('form'); for (var i = 0; i < cols.length; i++) { var filterKey = cols[i].getAttribute('filterKey'); if (filterKey == '') continue; if (filterKey == 'timeRange') { if (cols[i].getElementsByTagName('input')[0].checked) { this.sources[sid].earliestDate = new Date(); this.sources[sid].earliestDate.setDate(this.sources[sid].earliestDate.getDate() - parseInt(cols[i].days.value)); if (cols[i].days.defaultValue != cols[i].days.value) url += '&from=D'+(-parseInt(cols[i].days.value)); } else { this.sources[sid].earliestDate = null; if (cols[i].dateFrom.value != '') { url += '&from='+cols[i].dateFrom.value; this.sources[sid].earliestDate = cols[i].dateFrom.valueAsDate; } if (!this.sources[sid].earliestDate) { this.sources[sid].earliestDate = new Date(); this.sources[sid].earliestDate.setDate(this.sources[sid].earliestDate.getDate() - parseInt(cols[i].days.defaultValue)); } var dTo = null; if (cols[i].dateTo.value != '') { dTo = cols[i].dateTo.valueAsDate; url += '&to='+cols[i].dateTo.value; } if (!dTo) dTo = new Date(); var diff = (dTo - this.sources[sid].earliestDate) / (1000 * 60 * 60 * 24); if (diff > parseInt(cols[i].days.max)) { this.sources[sid].earliestDate = dTo; this.sources[sid].earliestDate.setDate(this.sources[sid].earliestDate.getDate() - parseInt(cols[i].days.max)); } } } else { if (cols[i].getElementsByTagName('input')[0].checked) this.sources[sid].filterTypes[filterKey] = 0; if (cols[i].getElementsByTagName('input')[1].checked) this.sources[sid].filterTypes[filterKey] = 1; if (cols[i].getElementsByTagName('input')[2].checked) this.sources[sid].filterTypes[filterKey] = 2; var filterList = null; for (var j = 0; j < cols[i].getElementsByTagName('div').length; j++) if ((' '+cols[i].getElementsByTagName('div')[j].className+' ').indexOf(' filterList ') != -1) { filterList = cols[i].getElementsByTagName('div')[j].getElementsByTagName('input'); break; } this.sources[sid].selected[filterKey] = new Array(); if (filterList) for (var j = 0; j < filterList.length; j++) if (filterList[j].checked) this.sources[sid].selected[filterKey].push(filterList[j].getAttribute('filterId')); } } } for (var f in this.sources[sid].selected) { if (this.sources[sid].filterTypes[f] == 0 || this.sources[sid].selected[f].length == 0) continue; url += '&'+f+'='; if (this.sources[sid].filterTypes[f] == 2) url += '~,'; url += this.sources[sid].selected[f].join(','); } this.sources[sid].syncUrl.textContent = url; this.sources[sid].syncUrl.href = 'webcal://'+this.rmHTTP(url); this.sources[sid].src = url; if (updateCalendar) this.update(); }, getSimpleInfo : /* private */ function (sid) { // creates a settings-block for simple ics-file sources this.sources[sid].settingsEl = document.createElement('div'); this.filterBox.appendChild(this.sources[sid].settingsEl); var syncUrl = document.createElement('a'); syncUrl.href = this.sources[sid].src; this.sources[sid].settingsEl.appendChild(syncUrl); var absUrl = syncUrl.href; // this way, an absoute path is guaranteed syncUrl.textContent = absUrl; absUrl = absUrl.split('\/\/'); if (absUrl.length > 1) absUrl[0] = 'webcal:'; syncUrl.href = absUrl.join('\/\/'); }, getFilterInfo : /* private */ function (sid) { // creates a settings-block for phpBB-calendar based sources var _this = this; this.sources[sid].settingsEl = document.createElement('div'); this.filterBox.appendChild(this.sources[sid].settingsEl); var syncUrl = document.createElement('a'); _this.sources[sid].syncUrl = syncUrl; var xmlHttp = new XMLHttpRequest(); xmlHttp.open('GET', this.sources[sid].shortSrc+'?action=filterinfo', true); xmlHttp.onreadystatechange = function () { if (xmlHttp.readyState != 4) return; try { _this.sources[sid].filterInfo = JSON.parse(xmlHttp.responseText); } catch (e) { _this.sources[sid].settingsEl.innerHTML = _this.lang.get('filterError'); return; } if (_this.sources[sid].filterInfo.promoters) _this.sources[sid].settingsEl.appendChild(_this.filterHtmlVisualization(sid, 'promoters')); if (_this.sources[sid].filterInfo.categories) _this.sources[sid].settingsEl.appendChild(_this.filterHtmlVisualization(sid, 'categories')); _this.sources[sid].settingsEl.appendChild(_this.filterHtmlVisualization(sid, 'timeRange')); var syncBox = document.createElement('div'); var updateBut = document.createElement('a'); updateBut.href = 'javascript: /* update */'; updateBut.className = 'button'; updateBut.textContent = _this.lang.get('update')+' -> '; updateBut.cal = _this; updateBut.sid = sid; updateBut.addEventListener('click', function () { this.cal.updateUrl(this.sid, true); }, false); var syncHint = document.createElement('p'); syncHint.innerHTML = _this.lang.get('synchint'); syncBox.appendChild(syncHint); syncBox.appendChild(updateBut); syncBox.appendChild(syncUrl); _this.sources[sid].settingsEl.appendChild(syncBox); }; xmlHttp.onerror = function () { _this.sources[sid].settingsEl.innerHTML = _this.lang.get('filterError'); }; try { xmlHttp.send(null); } catch (e) { this.sources[sid].settingsEl.innerHTML = this.lang.get('filterError'); } }, sortAndPrintDates : /* private */ function () { this.ul.innerHTML = ''; this.dates.sort(function (d1, d2) { return d2.start - d1.start; }); var now = new Date(); var reachedNow = false; for (var i = 0; i < this.dates.length; i++) { // gray out passed dates this.dates[i].li.className = ''; if (this.dates[i].start < now) { if (this.dates[i].end && this.dates[i].end > now) this.dates[i].li.className += 'current'; else { this.dates[i].li.className += 'passed'; if (!reachedNow && i > 0) this.dates[i].li.className += ' nowLine'; reachedNow = true; } } if (this.dates[i].status == 'cancelled') this.dates[i].li.className += ' cancelled'; this.ul.appendChild(this.dates[i].li); } }, icsToDate : /* private */ function (cal, eventID, endDate) { if (!endDate || !cal.VEVENT[eventID].DTEND) var d = cal.VEVENT[eventID].DTSTART.split('T'); else var d = cal.VEVENT[eventID].DTEND.split('T'); var a = new Array (0, 0, 0, 0, 0, 0, 0); a[0] = d[0].substring(0, d[0].length-4); a[1] = parseInt(d[0].substring(d[0].length-4, d[0].length-2))-1; a[2] = parseInt(d[0].substring(d[0].length-2, d[0].length)); if (d.length > 1) { a[3] = parseInt(d[1].substring(0, 2)); a[4] = parseInt(d[1].substring(2, 4)); a[5] = parseInt(d[1].substring(4, 6)); var date = new Date(Date.UTC(a[0], a[1], a[2], a[3], a[4], a[5], a[6])); } else var date = new Date(a[0], a[1], a[2], a[3], a[4], a[5], a[6]); // Events with complete day begin in the local timezone if (cal.VEVENT[eventID].DTSTART_TZID && cal.VTIMEZONE) for (var k = 0; k < cal.VTIMEZONE.length; k++) if (cal.VTIMEZONE[k].TZID == cal.VEVENT[eventID].DTSTART_TZID) { var sign = parseInt(cal.VTIMEZONE[k].STANDARD[0].TZOFFSETFROM.substring(0, 1)+'1'); var dh = parseInt(cal.VTIMEZONE[k].STANDARD[0].TZOFFSETFROM.substring(1, 3)); var dm = parseInt(cal.VTIMEZONE[k].STANDARD[0].TZOFFSETFROM.substring(3, 5)); date.setUTCHours(date.getUTCHours() - dh * sign); date.setUTCMinutes(date.getUTCMinutes() - dm * sign); break; } return date; }, interpretICS : /* private */ function (ics) { ics = ics.split("\r").join('').split("\n"); var i = 0; function readBlock () { var o = new Object(); var l = ''; // last variable name in o while (i < ics.length) { if (ics[i].charAt(0) == ' ' && l != '') { o[l] += ics[i].substring(1, ics[i].length).split("\\n").join("\n").split("\\,").join(",").split("\\;").join(";"); i++; continue; } var s = ics[i].split(':'); i++; if (s[0] == 'END') return o; else if (s[0] == 'BEGIN') { if (!o[s[1]]) o[s[1]] = new Array(); o[s[1]].push(readBlock()); } else { l = s[0].split(';'); for (var j = 1; j < l.length; j++) { l[j] = l[j].split('='); if (l[j].length == 2) o[l[0]+'_'+l[j][0]] = l[j][1]; } l = l[0]; s.shift(); o[l] = s.join(':').split("\\n").join("\n").split("\\,").join(",").split("\\;").join(";"); } } return o; } return readBlock(); }, loadSource : /* private */ function (sid, callback) { var _this = this; this.sources[sid].ics = null; // compose src var currentSrc = this.sources[sid].src; currentSrc += ((this.sources[sid].src.indexOf('?') >= 0)?'&':'?')+'nocache='+Math.trunc(Math.random()*10000); // get var xmlHttp = new XMLHttpRequest(); xmlHttp.open('GET', currentSrc, true); xmlHttp.onreadystatechange = function () { if (xmlHttp.readyState != 4) return; _this.sources[sid].ics = _this.interpretICS(xmlHttp.responseText); _this.sources[sid].state = 2; if (callback) callback(sid); }; xmlHttp.onerror = function () { _this.sources[sid].state = 3; if (callback) callback(sid); }; try { xmlHttp.send(null); } catch (e) { _this.sources[sid].state = 3; if (callback) callback(sid); } }, addDatesFromSrc : /* private */ function (sid) { if (!this.sources[sid].ics || !this.sources[sid].ics.VCALENDAR) return; for (var i = 0; i < this.sources[sid].ics.VCALENDAR.length; i++) if (this.sources[sid].ics.VCALENDAR[i].VEVENT) for (var j = 0; j < this.sources[sid].ics.VCALENDAR[i].VEVENT.length; j++) { if (!this.sources[sid].ics.VCALENDAR[i].VEVENT[j].DTSTART) continue; var date = new CalendarDate(); if (this.sources[sid].ics.VCALENDAR[i].VEVENT[j].RRULE) date.parseRrule(this.sources[sid].ics.VCALENDAR[i].VEVENT[j].RRULE); date.sourceId = sid; date.start = this.icsToDate(this.sources[sid].ics.VCALENDAR[i], j, false); date.end = this.icsToDate(this.sources[sid].ics.VCALENDAR[i], j, true); date.completeDay = this.sources[sid].ics.VCALENDAR[i].VEVENT[j].DTSTART.indexOf('T') >= 0; date.title = this.sources[sid].ics.VCALENDAR[i].VEVENT[j].SUMMARY; date.location = this.sources[sid].ics.VCALENDAR[i].VEVENT[j].LOCATION; date.description = this.sources[sid].ics.VCALENDAR[i].VEVENT[j].DESCRIPTION; date.url = this.sources[sid].ics.VCALENDAR[i].VEVENT[j].URL; date.status = this.sources[sid].ics.VCALENDAR[i].VEVENT[j].STATUS; if (!date.status) date.status = ''; else date.status = date.status.toLowerCase(); date.updateHTML(); this.dates.push(date); if (date.repeatMode > 0) { var duration = date.end.getTime() - date.start.getTime(); var prevRep = date.start; var nextRep = null; var nDate = null; do { nextRep = new Date(prevRep.getFullYear(), prevRep.getMonth(), prevRep.getDate(), 23, 59, 1, 0); nextRep = date.getNearestRepetition(nextRep); if (nextRep <= prevRep) break; prevRep = nextRep; nDate = new CalendarDate(); nDate.sourceId = date.sourceId; nDate.start = nextRep; nDate.end = new Date(nextRep); nDate.end.setMilliseconds(nDate.end.getMilliseconds() + duration); nDate.completeDay = date.completeDay; nDate.title = date.title; nDate.location = date.location; nDate.description = date.description; nDate.url = date.url; nDate.status = date.status; nDate.updateHTML(); this.dates.push(nDate); } while (nextRep <= new Date()); } } }, update : /* public */ function () { var _this = this; for (var i = 0; i < this.sources.length; i++) if (this.sources[i].state == 1) return; // a load request is already in work else this.sources[i].state = 1; // mark all sources as loading function sourcesLoad () { // wait until all sources are loaded for (var i = 0; i < _this.sources.length; i++) if (_this.sources[i].state == 1) return; // remove all existing dates, that belong to a successfully (re)loaded source for (var i = _this.dates.length-1; i >= 0; i--) if (_this.dates[i].sourceId >= 0 && _this.dates[i].sourceId < _this.sources.length && _this.sources[_this.dates[i].sourceId].state == 2) _this.dates.splice(i, 1); // import dates from sources for (var i = 0; i < _this.sources.length; i++) if (_this.sources[i].state == 2) _this.addDatesFromSrc(i); else { // TODO: Generate error message for load error } // update HTML _this.sortAndPrintDates(); } for (var i = 0; i < this.sources.length; i++) this.loadSource(i, sourcesLoad); sourcesLoad(); // if only dates from HTML exist } }; // auto-detect calendar in HTML (function () { var div = document.getElementsByTagName('div'); if (div.length > 0 && (' '+div[div.length-1].className+' ').indexOf(' calendar ') != -1) new ICSCalendar(div[div.length-1]); })();