1 /* JavaScript for SOGoCalendar */
5 var listFilter = 'view_today';
7 var listOfSelection = null;
8 var selectedCalendarCell;
9 var calendarColorIndex = null;
11 var showCompletedTasks = 0;
14 var currentView = "weekview";
16 var cachedDateSelectors = new Array();
18 var contactSelectorAction = 'calendars-contacts';
20 var eventsToDelete = new Array();
21 var calendarsOfEventsToDelete = new Array();
23 var usersRightsWindowHeight = 250;
24 var usersRightsWindowWidth = 502;
26 function newEvent(sender, type) {
27 var day = sender.readAttribute("day");
30 var hour = sender.readAttribute("hour");
31 var folder = getSelectedFolder();
32 var folderID = folder.readAttribute("id");
33 var roles = folder.readAttribute("roles");
35 roles = roles.split(",")
36 if ($(roles).indexOf("PublicModifier") < 0)
37 folderID = "/personal";
39 var urlstr = ApplicationBaseURL + folderID + "/new" + type;
40 var params = new Array();
42 params.push("day=" + day);
44 params.push("hm=" + hour);
45 if (params.length > 0)
46 urlstr += "?" + params.join("&");
47 window.open(urlstr, "", "width=490,height=470,resizable=0");
49 return false; /* stop following the link */
52 function getSelectedFolder() {
54 var list = $("calendarList");
55 var nodes = list.getSelectedRows();
59 folder = list.down("li");
64 function onMenuNewEventClick(event) {
65 newEvent(this, "event");
68 function onMenuNewTaskClick(event) {
69 newEvent(this, "task");
72 function _editEventId(id, calendar) {
73 var urlstr = ApplicationBaseURL + "/" + calendar + "/" + id + "/edit";
74 var targetname = "SOGo_edit_" + id;
75 var win = window.open(urlstr, "_blank",
76 "width=490,height=470,resizable=0");
80 function editEvent() {
81 if (listOfSelection) {
82 var nodes = listOfSelection.getSelectedRows();
84 if (nodes.length == 0) {
85 window.alert(labels["Please select an event or a task."]);
89 for (var i = 0; i < nodes.length; i++)
90 _editEventId(nodes[i].getAttribute("id"),
92 } else if (selectedCalendarCell) {
93 _editEventId(selectedCalendarCell[0].cname,
94 selectedCalendarCell[0].calendar);
96 window.alert(labels["Please select an event or a task."]);
99 return false; /* stop following the link */
102 function _batchDeleteEvents() {
103 var events = eventsToDelete.shift();
104 var calendar = calendarsOfEventsToDelete.shift();
105 var urlstr = (ApplicationBaseURL + "/" + calendar
106 + "/batchDelete?ids=" + events.join('/'));
107 document.deleteEventAjaxRequest = triggerAjaxRequest(urlstr,
112 function deleteEvent() {
113 if (listOfSelection) {
114 var nodes = listOfSelection.getSelectedRows();
116 if (nodes.length > 0) {
118 if (listOfSelection == $("tasksList"))
119 label = labels["taskDeleteConfirmation"];
121 label = labels["eventDeleteConfirmation"];
123 if (confirm(label)) {
124 if (document.deleteEventAjaxRequest) {
125 document.deleteEventAjaxRequest.aborted = true;
126 document.deleteEventAjaxRequest.abort();
128 var sortedNodes = new Array();
129 var calendars = new Array();
131 for (var i = 0; i < nodes.length; i++) {
132 var calendar = nodes[i].calendar;
133 if (!sortedNodes[calendar]) {
134 sortedNodes[calendar] = new Array();
135 calendars.push(calendar);
137 sortedNodes[calendar].push(nodes[i].cname);
139 for (var i = 0; i < calendars.length; i++) {
140 calendarsOfEventsToDelete.push(calendars[i]);
141 eventsToDelete.push(sortedNodes[calendars[i]]);
143 _batchDeleteEvents();
146 window.alert(labels["Please select an event or a task."]);
149 else if (selectedCalendarCell) {
150 var label = labels["eventDeleteConfirmation"];
151 if (confirm(label)) {
152 if (document.deleteEventAjaxRequest) {
153 document.deleteEventAjaxRequest.aborted = true;
154 document.deleteEventAjaxRequest.abort();
156 eventsToDelete.push([selectedCalendarCell[0].cname]);
157 calendarsOfEventsToDelete.push(selectedCalendarCell[0].calendar);
158 _batchDeleteEvents();
162 window.alert(labels["Please select an event or a task."]);
167 function modifyEvent(sender, modification) {
168 var currentLocation = '' + window.location;
169 var arr = currentLocation.split("/");
170 arr[arr.length-1] = modification;
172 document.modifyEventAjaxRequest = triggerAjaxRequest(arr.join("/"),
179 function closeInvitationWindow() {
180 var closeDiv = document.createElement("div");
181 document.body.appendChild(closeDiv);
182 closeDiv.addClassName("javascriptPopupBackground");
184 var closePseudoWin = document.createElement("div");
185 document.body.appendChild(closePseudoWin);
186 closePseudoWin.addClassName("javascriptMessagePseudoTopWindow");
187 closePseudoWin.style.top = "0px;";
188 closePseudoWin.style.left = "0px;";
189 closePseudoWin.style.right = "0px;";
190 closePseudoWin.appendChild(document.createTextNode(labels["closeThisWindowMessage"]));
192 var calLink = document.createElement("a");
193 closePseudoWin.appendChild(calLink);
194 calLink.href = ApplicationBaseURL;
195 calLink.appendChild(document.createTextNode(labels["Calendar"].toLowerCase()));
198 function modifyEventCallback(http) {
199 if (http.readyState == 4) {
200 if (http.status == 200) {
201 var mailInvitation = queryParameters["mail-invitation"];
202 if (mailInvitation && mailInvitation.toLowerCase() == "yes")
203 closeInvitationWindow();
205 window.opener.setTimeout("refreshEventsAndDisplay();", 100);
206 window.setTimeout("window.close();", 100);
210 // log("showing alert...");
211 window.alert(labels["eventPartStatModificationError"]);
213 document.modifyEventAjaxRequest = null;
217 function deleteEventCallback(http) {
218 if (http.readyState == 4) {
219 if (isHttpStatus204(http.status)) {
220 var nodes = http.callbackData;
221 for (var i = 0; i < nodes.length; i++) {
222 var node = $(nodes[i]);
224 node.parentNode.removeChild(node);
226 if (eventsToDelete.length)
227 _batchDeleteEvents();
229 document.deleteEventAjaxRequest = null;
232 changeCalendarDisplay();
236 log ("deleteEventCallback Ajax error");
240 function editDoubleClickedEvent(event) {
241 _editEventId(this.cname, this.calendar);
243 preventDefault(event);
244 event.cancelBubble = true;
247 function onSelectAll() {
248 var list = $("eventsList");
249 list.selectRowsMatchingClass("eventRow");
254 function onDaySelect(node) {
255 var day = node.getAttribute('day');
256 var needRefresh = (listFilter == 'view_selectedday'
257 && day != currentDay);
259 var td = $(node).getParentWithTagName("td");
260 var table = $(td).getParentWithTagName("table");
262 // log ("table.selected: " + table.selected);
264 if (document.selectedDate)
265 document.selectedDate.deselect();
268 document.selectedDate = td;
270 changeCalendarDisplay( { "day": day } );
277 function onDateSelectorGotoMonth(event) {
278 var day = this.getAttribute("date");
280 changeDateSelectorDisplay(day, true);
285 function onCalendarGotoDay(node) {
286 var day = node.getAttribute("date");
288 changeDateSelectorDisplay(day);
289 changeCalendarDisplay( { "day": day } );
294 function gotoToday() {
295 changeDateSelectorDisplay('');
296 changeCalendarDisplay();
301 function setDateSelectorContent(content) {
302 var div = $("dateSelectorView");
304 div.innerHTML = content;
305 if (currentDay.length > 0)
306 restoreCurrentDaySelection(div);
308 initDateSelectorEvents();
311 function dateSelectorCallback(http) {
312 if (http.readyState == 4
313 && http.status == 200) {
314 document.dateSelectorAjaxRequest = null;
315 var content = http.responseText;
316 setDateSelectorContent(content);
317 cachedDateSelectors[http.callbackData] = content;
320 log ("dateSelectorCallback Ajax error");
323 function eventsListCallback(http) {
324 if (http.readyState == 4
325 && http.status == 200) {
326 var div = $("eventsListView");
328 document.eventsListAjaxRequest = null;
329 var table = $("eventsList");
330 var params = parseQueryParameters(http.callbackData);
331 sortKey = params["sort"];
332 sortOrder = params["desc"];
333 lastClickedRow = -1; // from generic.js
335 if (http.responseText.length > 0) {
336 var data = http.responseText.evalJSON(true);
337 for (var i = 0; i < data.length; i++) {
338 var row = document.createElement("tr");
339 table.tBodies[0].appendChild(row);
340 $(row).addClassName("eventRow");
341 row.setAttribute("id", escape(data[i][0]));
342 row.cname = escape(data[i][0]);
343 row.calendar = data[i][1];
345 var startDate = new Date();
346 startDate.setTime(data[i][4] * 1000);
347 row.day = startDate.getDayString();
348 row.hour = startDate.getHourString();
349 Event.observe(row, "click",
350 onEventClick.bindAsEventListener(row));
351 Event.observe(row, "dblclick",
352 editDoubleClickedEvent.bindAsEventListener(row));
353 Event.observe(row, "contextmenu",
354 onEventContextMenu.bindAsEventListener(row));
356 var td = document.createElement("td");
358 Event.observe(td, "mousedown", listRowMouseDownHandler, true);
359 td.appendChild(document.createTextNode(data[i][3]));
361 td = document.createElement("td");
363 Event.observe(td, "mousedown", listRowMouseDownHandler, true);
364 td.appendChild(document.createTextNode(data[i][9]));
366 td = document.createElement("td");
368 Event.observe(td, "mousedown", listRowMouseDownHandler, true);
369 td.appendChild(document.createTextNode(data[i][10]));
371 td = document.createElement("td");
373 Event.observe(td, "mousedown", listRowMouseDownHandler, true);
374 td.appendChild(document.createTextNode(data[i][6]));
379 log ("eventsListCallback Ajax error");
382 function tasksListCallback(http) {
383 var div = $("tasksListView");
385 if (http.readyState == 4
386 && http.status == 200) {
387 document.tasksListAjaxRequest = null;
388 var list = $("tasksList");
390 if (http.responseText.length > 0) {
391 var data = http.responseText.evalJSON(true);
393 for (var i = 0; i < data.length; i++) {
394 //log(i + " = " + data[i][3]);
395 var listItem = document.createElement("li");
396 list.appendChild(listItem);
397 Event.observe(listItem, "mousedown", listRowMouseDownHandler);
398 Event.observe(listItem, "click", onRowClick);
399 Event.observe(listItem, "dblclick",
400 editDoubleClickedEvent.bindAsEventListener(listItem));
401 listItem.setAttribute("id", data[i][0]);
402 $(listItem).addClassName(data[i][5]);
403 listItem.calendar = data[i][1];
404 $(listItem).addClassName("calendarFolder" + data[i][1]);
405 listItem.cname = escape(data[i][0]);
406 var input = document.createElement("input");
407 input.setAttribute("type", "checkbox");
408 listItem.appendChild(input);
409 Event.observe(input, "click", updateTaskStatus.bindAsEventListener(input), true);
410 input.setAttribute("value", "1");
412 input.setAttribute("checked", "checked");
413 $(input).addClassName("checkBox");
414 listItem.appendChild(document.createTextNode(data[i][3]));
417 list.scrollTop = list.previousScroll;
419 if (http.callbackData) {
420 var selectedNodesId = http.callbackData;
421 for (var i = 0; i < selectedNodesId.length; i++) {
422 // log(selectedNodesId[i] + " (" + i + ") is selected");
423 $(selectedNodesId[i]).select();
427 log ("tasksListCallback: no data");
431 log ("tasksListCallback Ajax error");
434 function restoreCurrentDaySelection(div) {
435 var elements = $(div).getElementsByTagName("a");
438 while (!day && i < elements.length)
440 day = elements[i].day;
445 && day.substr(0, 6) == currentDay.substr(0, 6)) {
446 for (i = 0; i < elements.length; i++) {
447 day = elements[i].day;
448 if (day && day == currentDay) {
449 var td = $(elements[i]).getParentWithTagName("td");
450 if (document.selectedDate)
451 document.selectedDate.deselect();
453 document.selectedDate = td;
459 function changeDateSelectorDisplay(day, keepCurrentDay) {
460 var url = ApplicationBaseURL + "dateselector";
462 url += "?day=" + day;
464 if (day != currentDay) {
468 var month = day.substr(0, 6);
469 if (cachedDateSelectors[month]) {
470 // log ("restoring cached selector for month: " + month);
471 setDateSelectorContent(cachedDateSelectors[month]);
474 // log ("loading selector for month: " + month);
475 if (document.dateSelectorAjaxRequest) {
476 document.dateSelectorAjaxRequest.aborted = true;
477 document.dateSelectorAjaxRequest.abort();
479 document.dateSelectorAjaxRequest
480 = triggerAjaxRequest(url,
481 dateSelectorCallback,
487 function changeCalendarDisplay(data, newView) {
488 var url = ApplicationBaseURL + ((newView) ? newView : currentView);
490 var scrollEvent = null;
494 scrollEvent = data['scrollEvent'];
502 var divs = $$('div.day[day='+day+']');
504 // Don't reload the view if the event is present in current view
506 // Deselect previous day
507 var selectedDivs = $$('div.day.selectedDay');
508 selectedDivs.each(function(div) {
509 div.removeClassName('selectedDay');
513 divs.each(function(div) {
514 div.addClassName('selectedDay');
517 // Deselect day in date selector
518 if (document.selectedDate)
519 document.selectedDate.deselect();
521 // Select day in date selector
522 var selectedLink = $$('table#dateSelectorTable a[day='+day+']');
523 if (selectedLink.length > 0) {
524 selectedCell = selectedLink[0].up(1);
525 selectedCell.select();
526 document.selectedDate = selectedCell;
530 scrollDayView(scrollEvent);
535 url += "?day=" + day;
538 // log ("switching to view: " + newView);
539 // log ("changeCalendarDisplay: " + url);
541 selectedCalendarCell = null;
543 if (document.dayDisplayAjaxRequest) {
544 // log ("aborting day ajaxrq");
545 document.dayDisplayAjaxRequest.aborted = true;
546 document.dayDisplayAjaxRequest.abort();
548 document.dayDisplayAjaxRequest
549 = triggerAjaxRequest(url, calendarDisplayCallback,
552 "scrollEvent": scrollEvent });
557 function _ensureView(view) {
558 if (currentView != view)
559 changeCalendarDisplay(null, view);
564 function onDayOverview() {
565 return _ensureView("dayview");
568 function onMulticolumnDayOverview() {
569 return _ensureView("multicolumndayview");
572 function onWeekOverview() {
573 return _ensureView("weekview");
576 function onMonthOverview() {
577 return _ensureView("monthview");
580 function scrollDayView(scrollEvent) {
583 // Select event in calendar view
585 divs = $$("div#calendarContent div." + eventClass(scrollEvent));
586 selectCalendarEvent(divs[0]);
589 // Don't scroll if in month view
590 if (currentView == "monthview")
594 var daysView = $("daysView");
596 $(daysView.childNodesWithTag("div")[0]).childNodesWithTag("div");
599 divs = $$("div#calendarContent div." + eventClass(scrollEvent));
600 var classes = $w(divs[0].className);
601 for (var i = 0; i < classes.length; i++) {
602 if (classes[i].startsWith("starts")) {
603 var starts = Math.floor(parseInt(classes[i].substr(6)) / 4);
604 offset = hours[starts].offsetTop;
610 offset = hours[8].offsetTop;
612 daysView.scrollTop = offset - 5;
615 function onClickableCellsDblClick(event) {
616 newEvent(this, 'event');
618 event.cancelBubble = true;
619 event.returnValue = false;
622 function refreshCalendarEvents(scrollEvent) {
623 var todayDate = new Date();
626 if (currentView == "dayview") {
630 sd = todayDate.getDayString();
633 else if (currentView == "weekview") {
636 startDate = currentDay.asDate();
638 startDate = todayDate;
639 startDate = startDate.beginOfWeek();
640 sd = startDate.getDayString();
641 var endDate = new Date();
642 endDate.setTime(startDate.getTime());
644 ed = endDate.getDayString();
649 monthDate = currentDay.asDate();
651 monthDate = todayDate;
652 monthDate.setDate(1);
653 sd = monthDate.beginOfWeek().getDayString();
655 var lastMonthDate = new Date();
656 lastMonthDate.setTime(monthDate.getTime());
657 lastMonthDate.setMonth(monthDate.getMonth() + 1);
658 lastMonthDate.addDays(-1);
659 ed = lastMonthDate.endOfWeek().getDayString();
661 if (document.refreshCalendarEventsAjaxRequest) {
662 document.refreshCalendarEventsAjaxRequest.aborted = true;
663 document.refreshCalendarEventsAjaxRequest.abort();
665 var url = ApplicationBaseURL + "eventslist?sd=" + sd + "&ed=" + ed;
666 document.refreshCalendarEventsAjaxRequest
667 = triggerAjaxRequest(url, refreshCalendarEventsCallback,
668 {"startDate": sd, "endDate": ed,
669 "scrollEvent": scrollEvent});
672 function refreshCalendarEventsCallback(http) {
673 if (http.readyState == 4
674 && http.status == 200) {
676 if (http.responseText.length > 0) {
677 var data = http.responseText.evalJSON(true);
678 // log("refresh calendar events: " + data.length);
679 for (var i = 0; i < data.length; i++)
680 drawCalendarEvent(data[i],
681 http.callbackData["startDate"],
682 http.callbackData["endDate"]);
684 scrollDayView(http.callbackData["scrollEvent"]);
687 log("AJAX error when refreshing calendar events");
690 function drawCalendarEvent(eventData, sd, ed) {
691 var viewStartDate = sd.asDate();
692 var viewEndDate = ed.asDate();
694 var startDate = new Date();
695 startDate.setTime(eventData[4] * 1000);
696 var endDate = new Date();
697 endDate.setTime(eventData[5] * 1000);
699 // log ("s: " + startDate + "; e: " + endDate);
701 var days = startDate.daysUpTo(endDate);
704 if (currentView == "monthview"
705 && (eventData[7] == 0))
706 title = startDate.getDisplayHoursString() + " " + eventData[3];
708 title = eventData[3];
710 // log("title: " + title);
711 // log("viewS: " + viewStartDate);
712 var startHour = null;
715 var siblings = new Array();
716 for (var i = 0; i < days.length; i++)
717 if (days[i].earlierDate(viewStartDate) == viewStartDate
718 && days[i].laterDate(viewEndDate) == viewEndDate) {
721 // log("day: " + days[i]);
723 var quarters = (startDate.getUTCHours() * 4
724 + Math.floor(startDate.getUTCMinutes() / 15));
726 startHour = startDate.getDisplayHoursString();
727 endHour = endDate.getDisplayHoursString();
734 if (i == days.length - 1) {
735 var quarters = (endDate.getUTCHours() * 4
736 + Math.ceil(endDate.getUTCMinutes() / 15));
741 lasts = ends - starts;
745 var eventDiv = newEventDIV(eventData[0], eventData[1], starts, lasts,
747 siblings.push(eventDiv);
748 eventDiv.siblings = siblings;
749 var dayString = days[i].getDayString();
750 // log("day: " + dayString);
751 var parentDiv = null;
752 if (currentView == "monthview") {
753 var dayDivs = $("monthDaysView").childNodesWithTag("div");
755 while (!parentDiv && j < dayDivs.length) {
756 if (dayDivs[j].getAttribute("day") == dayString)
757 parentDiv = dayDivs[j];
763 if (eventData[7] == 0) {
764 var daysView = $("daysView");
765 var eventsDiv = $(daysView).childNodesWithTag("div")[1];
766 var dayDivs = $(eventsDiv).childNodesWithTag("div");
768 while (!parentDiv && j < dayDivs.length) {
769 if (dayDivs[j].getAttribute("day") == dayString)
770 parentDiv = dayDivs[j].childNodesWithTag("div")[0];
776 var header = $("calendarHeader");
777 var daysDiv = $(header).childNodesWithTag("div")[1];
778 var dayDivs = $(daysDiv).childNodesWithTag("div");
780 while (!parentDiv && j < dayDivs.length) {
781 if (dayDivs[j].getAttribute("day") == dayString)
782 parentDiv = dayDivs[j];
789 parentDiv.appendChild(eventDiv);
793 function eventClass(cname) {
794 return escape(cname.replace(".", "-"));
798 function newEventDIV(cname, calendar, starts, lasts,
799 startHour, endHour, title) {
800 var eventDiv = document.createElement("div");
801 eventDiv.cname = escape(cname);
802 eventDiv.calendar = calendar;
803 $(eventDiv).addClassName("event");
804 $(eventDiv).addClassName(eventClass(cname));
805 $(eventDiv).addClassName("starts" + starts);
806 $(eventDiv).addClassName("lasts" + lasts);
807 for (var i = 1; i < 5; i++) {
808 var shadowDiv = document.createElement("div");
809 eventDiv.appendChild(shadowDiv);
810 $(shadowDiv).addClassName("shadow");
811 $(shadowDiv).addClassName("shadow" + i);
813 var innerDiv = document.createElement("div");
814 eventDiv.appendChild(innerDiv);
815 $(innerDiv).addClassName("eventInside");
816 $(innerDiv).addClassName("calendarFolder" + calendar);
818 var gradientDiv = document.createElement("div");
819 innerDiv.appendChild(gradientDiv);
820 $(gradientDiv).addClassName("gradient");
821 var gradientImg = document.createElement("img");
822 gradientDiv.appendChild(gradientImg);
823 gradientImg.src = ResourcesURL + "/event-gradient.png";
825 var textDiv = document.createElement("div");
826 innerDiv.appendChild(textDiv);
827 $(textDiv).addClassName("text");
829 var headerSpan = document.createElement("span");
830 textDiv.appendChild(headerSpan);
831 $(headerSpan).addClassName("eventHeader");
832 headerSpan.appendChild(document.createTextNode(startHour + " - "
834 textDiv.appendChild(document.createElement("br"));
836 textDiv.appendChild(document.createTextNode(title));
838 Event.observe(eventDiv, "mousedown", listRowMouseDownHandler);
839 Event.observe(eventDiv, "click",
840 onCalendarSelectEvent.bindAsEventListener(eventDiv));
841 Event.observe(eventDiv, "dblclick",
842 editDoubleClickedEvent.bindAsEventListener(eventDiv));
847 function calendarDisplayCallback(http) {
848 var div = $("calendarView");
850 if (http.readyState == 4
851 && http.status == 200) {
852 document.dayDisplayAjaxRequest = null;
853 div.update(http.responseText);
854 if (http.callbackData["view"])
855 currentView = http.callbackData["view"];
856 if (http.callbackData["day"])
857 currentDay = http.callbackData["day"];
860 if (currentView == "monthview")
861 contentView = $("calendarContent");
863 contentView = $("daysView");
865 refreshCalendarEvents(http.callbackData.scrollEvent);
867 var days = document.getElementsByClassName("day", contentView);
868 if (currentView == "monthview")
869 for (var i = 0; i < days.length; i++) {
870 Event.observe(days[i], "click",
871 onCalendarSelectDay.bindAsEventListener(days[i]));
872 Event.observe(days[i], "dblclick",
873 onClickableCellsDblClick.bindAsEventListener(days[i]));
876 var headerDivs = $("calendarHeader").childNodesWithTag("div");
877 var headerDaysLabels = document.getElementsByClassName("day", headerDivs[0]);
878 var headerDays = document.getElementsByClassName("day", headerDivs[1]);
879 for (var i = 0; i < days.length; i++) {
880 headerDays[i].hour = "allday";
881 Event.observe(headerDaysLabels[i], "mousedown", listRowMouseDownHandler);
882 Event.observe(headerDays[i], "click",
883 onCalendarSelectDay.bindAsEventListener(days[i]));
884 Event.observe(headerDays[i], "dblclick",
885 onClickableCellsDblClick.bindAsEventListener(headerDays[i]));
886 Event.observe(days[i], "click",
887 onCalendarSelectDay.bindAsEventListener(days[i]));
888 var clickableCells = document.getElementsByClassName("clickableHourCell",
890 for (var j = 0; j < clickableCells.length; j++)
891 Event.observe(clickableCells[j], "dblclick",
892 onClickableCellsDblClick.bindAsEventListener(clickableCells[j]));
897 log ("calendarDisplayCallback Ajax error (" + http.readyState + "/" + http.status + ")");
900 function assignCalendar(name) {
901 if (typeof(skycalendar) != "undefined") {
904 node.calendar = new skycalendar(node);
905 node.calendar.setCalendarPage(ResourcesURL + "/skycalendar.html");
906 var dateFormat = node.getAttribute("dateFormat");
908 node.calendar.setDateFormat(dateFormat);
912 function popupCalendar(node) {
913 var nodeId = $(node).readAttribute("inputId");
914 var input = $(nodeId);
915 input.calendar.popup();
920 function onEventContextMenu(event) {
921 var topNode = $("eventsList");
924 var menu = $("eventsListMenu");
926 Event.observe(menu, "hideMenu", onEventContextMenuHide);
927 popupMenu(event, "eventsListMenu", this);
929 var topNode = $("eventsList");
930 var selectedNodes = topNode.getSelectedRows();
931 topNode.menuSelectedRows = selectedNodes;
932 for (var i = 0; i < selectedNodes.length; i++)
933 selectedNodes[i].deselect();
935 topNode.menuSelectedEntry = this;
939 function onEventContextMenuHide(event) {
940 var topNode = $("eventsList");
942 if (topNode.menuSelectedEntry) {
943 topNode.menuSelectedEntry.deselect();
944 topNode.menuSelectedEntry = null;
946 if (topNode.menuSelectedRows) {
947 var nodeIds = topNode.menuSelectedRows;
948 for (var i = 0; i < nodeIds.length; i++) {
949 var node = $(nodeIds[i]);
952 topNode.menuSelectedRows = null;
956 function onEventsSelectionChange() {
957 listOfSelection = this;
958 this.removeClassName("_unfocused");
959 $("tasksList").addClassName("_unfocused");
962 function onTasksSelectionChange() {
963 listOfSelection = this;
964 this.removeClassName("_unfocused");
965 $("eventsList").addClassName("_unfocused");
968 function _loadEventHref(href) {
969 if (document.eventsListAjaxRequest) {
970 document.eventsListAjaxRequest.aborted = true;
971 document.eventsListAjaxRequest.abort();
973 var url = ApplicationBaseURL + "/" + href;
974 document.eventsListAjaxRequest
975 = triggerAjaxRequest(url, eventsListCallback, href);
977 var table = $("eventsList").tBodies[0];
978 while (table.rows.length > 0)
979 table.removeChild(table.rows[0]);
984 function _loadTasksHref(href) {
985 if (document.tasksListAjaxRequest) {
986 document.tasksListAjaxRequest.aborted = true;
987 document.tasksListAjaxRequest.abort();
989 url = ApplicationBaseURL + href;
991 var tasksList = $("tasksList");
994 selectedIds = tasksList.getSelectedNodesId();
997 document.tasksListAjaxRequest
998 = triggerAjaxRequest(url, tasksListCallback, selectedIds);
1000 tasksList.previousScroll = tasksList.scrollTop;
1001 while (tasksList.childNodes.length)
1002 tasksList.removeChild(tasksList.childNodes[0]);
1007 function onHeaderClick(event) {
1008 //log("onHeaderClick: " + this.link);
1009 //_loadEventHref(this.link);
1011 preventDefault(event);
1014 function refreshCurrentFolder() {
1018 function refreshEvents() {
1020 var value = search["value"];
1021 if (value && value.length)
1022 titleSearch = "&search=" + value;
1026 return _loadEventHref("eventslist?desc=" + sortOrder
1027 + "&sort=" + sortKey
1028 + "&day=" + currentDay
1030 + "&filterpopup=" + listFilter);
1033 function refreshTasks() {
1034 return _loadTasksHref("taskslist?show-completed=" + showCompletedTasks);
1037 function refreshEventsAndDisplay() {
1039 changeCalendarDisplay();
1042 function onListFilterChange() {
1043 var node = $("filterpopup");
1045 listFilter = node.value;
1046 // log ("listFilter = " + listFilter);
1048 return refreshEvents();
1051 function onEventClick(event) {
1052 changeCalendarDisplay( { "day": this.day,
1053 "scrollEvent": this.getAttribute("id") } );
1054 changeDateSelectorDisplay(this.day);
1056 return onRowClick(event);
1059 function selectMonthInMenu(menu, month) {
1060 var entries = menu.childNodes[1].childNodesWithTag("LI");
1061 for (i = 0; i < entries.length; i++) {
1062 var entry = entries[i];
1063 var entryMonth = entry.getAttribute("month");
1064 if (entryMonth == month)
1065 entry.addClassName("currentMonth");
1067 entry.removeClassName("currentMonth");
1071 function selectYearInMenu(menu, month) {
1072 var entries = menu.childNodes[1].childNodes;
1073 for (i = 0; i < entries.length; i++) {
1074 var entry = entries[i];
1075 if (entry.tagName == "LI") {
1076 var entryMonth = entry.innerHTML;
1077 if (entryMonth == month)
1078 entry.addClassName("currentMonth");
1080 entry.removeClassName("currentMonth");
1085 function popupMonthMenu(event) {
1086 if (event.button == 0) {
1087 var id = this.getAttribute("id");
1088 if (id == "monthLabel")
1089 menuId = "monthListMenu";
1091 menuId = "yearListMenu";
1093 var popup = $(menuId);
1094 if (id == "monthLabel")
1095 selectMonthInMenu(popup, this.getAttribute("month"));
1097 selectYearInMenu(popup, this.innerHTML);
1099 popupToolbarMenu(this, menuId);
1104 function onMonthMenuItemClick(event) {
1105 var month = '' + this.getAttribute("month");
1106 var year = '' + $("yearLabel").innerHTML;
1108 changeDateSelectorDisplay(year + month + "01", true);
1111 function onYearMenuItemClick(event) {
1112 var month = '' + $("monthLabel").getAttribute("month");;
1113 var year = '' + this.innerHTML;
1115 changeDateSelectorDisplay(year + month + "01", true);
1118 function selectCalendarEvent(div) {
1119 // Select event in calendar view
1120 if (selectedCalendarCell)
1121 for (var i = 0; i < selectedCalendarCell.length; i++)
1122 selectedCalendarCell[i].deselect();
1124 for (var i = 0; i < div.siblings.length; i++)
1125 div.siblings[i].select();
1127 selectedCalendarCell = div.siblings;
1130 function onCalendarSelectEvent() {
1131 var list = $("eventsList");
1133 selectCalendarEvent(this);
1135 // Select event in events list
1136 $(list.tBodies[0]).deselectAll();
1137 var row = $(this.cname);
1139 var div = row.parentNode.parentNode.parentNode;
1140 div.scrollTop = row.offsetTop - (div.offsetHeight / 2);
1145 function onCalendarSelectDay(event) {
1147 if (currentView == "multicolumndayview")
1148 day = this.getAttribute("day");
1150 day = this.getAttribute("day");
1151 var needRefresh = (listFilter == 'view_selectedday'
1152 && day != currentDay);
1154 if (currentView == 'weekview')
1155 changeWeekCalendarDisplayOfSelectedDay(this);
1156 else if (currentView == 'monthview')
1157 changeMonthCalendarDisplayOfSelectedDay(this);
1158 changeDateSelectorDisplay(day);
1160 if (listOfSelection) {
1161 listOfSelection.addClassName("_unfocused");
1162 listOfSelection = null;
1169 function changeWeekCalendarDisplayOfSelectedDay(node) {
1170 var days = document.getElementsByClassName("day", node.parentNode);
1171 var headerDiv = $("calendarHeader").childNodesWithTag("div")[1];
1172 var headerDays = document.getElementsByClassName("day", headerDiv);
1174 // log ("days: " + days.length + "; headerDays: " + headerDays.length);
1175 for (var i = 0; i < days.length; i++)
1176 if (days[i] != node) {
1177 // log("unselect day : " + i);
1178 headerDays[i].removeClassName("selectedDay");
1179 days[i].removeClassName("selectedDay");
1182 // log("selected day : " + i);
1183 headerDays[i].addClassName("selectedDay");
1184 days[i].addClassName("selectedDay");
1188 function findMonthCalendarSelectedCell(daysContainer) {
1192 while (!found && i < daysContainer.childNodes.length) {
1193 var currentNode = daysContainer.childNodes[i];
1194 if (currentNode.tagName == 'DIV'
1195 && currentNode.hasClassName("selectedDay")) {
1196 daysContainer.selectedCell = currentNode;
1204 function changeMonthCalendarDisplayOfSelectedDay(node) {
1205 var daysContainer = node.parentNode;
1206 if (!daysContainer.selectedCell)
1207 findMonthCalendarSelectedCell(daysContainer);
1209 if (daysContainer.selectedCell)
1210 daysContainer.selectedCell.removeClassName("selectedDay");
1211 daysContainer.selectedCell = node;
1212 node.addClassName("selectedDay");
1215 function onShowCompletedTasks(event) {
1216 showCompletedTasks = (this.checked ? 1 : 0);
1218 return refreshTasks();
1221 function updateTaskStatus(event) {
1222 var taskId = this.parentNode.getAttribute("id");
1223 var newStatus = (this.checked ? 1 : 0);
1224 var http = createHTTPClient();
1226 if (isSafari() && !isSafari3()) {
1227 newStatus = (newStatus ? 0 : 1);
1230 url = (ApplicationBaseURL + this.parentNode.calendar
1231 + "/" + taskId + "/changeStatus?status=" + newStatus);
1234 // TODO: add parameter to signal that we are only interested in OK
1235 http.open("POST", url, false /* not async */);
1238 if (isHttpStatus204(http.status))
1241 log ("no http client?");
1246 function updateCalendarStatus(event) {
1247 var list = new Array();
1248 var newStatus = (this.checked ? 1 : 0);
1250 if (isSafari() && !isSafari3()) {
1251 newStatus = (newStatus ? 0 : 1);
1252 this.checked = newStatus;
1255 var nodes = $("calendarList").childNodesWithTag("li");
1256 for (var i = 0; i < nodes.length; i++) {
1257 var input = $(nodes[i]).childNodesWithTag("input")[0];
1258 if (input.checked) {
1259 var folderId = nodes[i].getAttribute("id");
1260 var elems = folderId.split(":");
1261 if (elems.length > 1)
1262 list.push(elems[0]);
1264 list.push(UserLogin);
1268 // if (!list.length) {
1269 // list.push(UserLogin);
1270 // nodes[0].childNodesWithTag("input")[0].checked = true;
1273 // ApplicationBaseURL = (UserFolderURL + "Groups/_custom_"
1274 // + list.join(",") + "/Calendar/");
1277 var folderID = this.parentNode.getAttribute("id");
1278 var urlstr = URLForFolderID(folderID);
1280 urlstr += "/activateFolder";
1282 urlstr += "/deactivateFolder";
1283 //log("updateCalendarStatus: ajax request = " + urlstr + ", folderID = " + folderID);
1284 triggerAjaxRequest(urlstr, calendarStatusCallback, folderID);
1287 updateCalendarsList();
1290 changeCalendarDisplay();
1296 function calendarStatusCallback(http) {
1297 if (http.readyState == 4) {
1298 if (isHttpStatus204(http.status)) {
1301 changeCalendarDisplay();
1304 var folder = $(http.callbackData);
1305 var input = folder.childNodesWithTag("input")[0];
1306 input.checked = (!input.checked);
1310 log("calendarStatusCallback Ajax error");
1313 function calendarEntryCallback(http) {
1314 if (http.readyState == 4) {
1315 var denied = !isHttpStatus204(http.status);
1316 var entry = $(http.callbackData);
1318 entry.addClassName("denied");
1320 entry.removeClassName("denied");
1324 function updateCalendarsList(method) {
1325 var list = $("calendarList").childNodesWithTag("li");
1326 for (var i = 0; i < list.length; i++) {
1327 var folderID = list[i].getAttribute("id");
1328 var url = URLForFolderID(folderID) + "/canAccessContent";
1329 triggerAjaxRequest(url, calendarEntryCallback, folderID);
1333 function addContact(tag, fullContactName, contactId, contactName, contactEmail) {
1334 var uids = $("uixselector-calendarsList-uidList");
1335 // log("addContact");
1338 var re = new RegExp("(^|,)" + contactId + "($|,)");
1340 if (!re.test(uids.value))
1342 if (uids.value.length > 0)
1343 uids.value += ',' + contactId;
1345 uids.value = contactId;
1346 var names = $("calendarList");
1347 var listElems = names.childNodesWithTag("li");
1348 var colorDef = indexColor(listElems.length);
1349 names.appendChild(userCalendarEntry(contactId, colorDef));
1357 function validateBrowseURL(input) {
1358 var button = $("browseURLBtn");
1360 if (input.value.length) {
1361 if (!button.enabled)
1362 enableAnchor(button);
1363 } else if (!button.disabled)
1364 disableAnchor(button);
1367 function browseURL(anchor, event) {
1368 if (event.button == 0) {
1369 var input = $("url");
1370 var url = input.value;
1372 window.open(url, '_blank');
1378 function onCalendarsMenuPrepareVisibility() {
1379 var folders = $("calendarList");
1380 var selected = folders.getSelectedNodes();
1382 if (selected.length > 0) {
1383 var folderOwner = selected[0].getAttribute("owner");
1384 var sharingOption = $(this).down("ul").childElements().last();
1385 // Disable the "Sharing" option when calendar is not owned by user
1386 if (folderOwner == UserLogin || IsSuperUser)
1387 sharingOption.removeClassName("disabled");
1389 sharingOption.addClassName("disabled");
1393 function getMenus() {
1396 var dateMenu = new Array();
1397 for (var i = 0; i < 12; i++)
1398 dateMenu.push(onMonthMenuItemClick);
1399 menus["monthListMenu"] = dateMenu;
1401 dateMenu = new Array();
1402 for (var i = 0; i < 11; i++)
1403 dateMenu.push(onYearMenuItemClick);
1404 menus["yearListMenu"] = dateMenu;
1406 menus["eventsListMenu"] = new Array(onMenuNewEventClick, "-",
1408 editEvent, deleteEvent, "-",
1411 menus["calendarsMenu"] = new Array(onMenuModify,
1413 onCalendarNew, onCalendarRemove,
1414 "-", null, null, "-",
1415 null, "-", onMenuSharing);
1416 menus["searchMenu"] = new Array(setSearchCriteria);
1418 var calendarsMenu = $("calendarsMenu");
1420 calendarsMenu.prepareVisibility = onCalendarsMenuPrepareVisibility;
1425 function onMenuSharing(event) {
1426 if ($(this).hasClassName("disabled"))
1429 var folders = $("calendarList");
1430 var selected = folders.getSelectedNodes()[0];
1431 /* FIXME: activation of the context menu should preferably select the entry
1432 above which the event has occured */
1434 var folderID = selected.getAttribute("id");
1435 var urlstr = URLForFolderID(folderID) + "/acls";
1437 openAclWindow(urlstr);
1441 function configureDragHandles() {
1442 var handle = $("verticalDragHandle");
1444 handle.addInterface(SOGoDragHandlesInterface);
1445 handle.leftBlock=$("leftPanel");
1446 handle.rightBlock=$("rightPanel");
1449 handle = $("rightDragHandle");
1451 handle.addInterface(SOGoDragHandlesInterface);
1452 handle.upperBlock=$("eventsListView");
1453 handle.lowerBlock=$("calendarView");
1457 function initCalendarSelector() {
1458 var selector = $("calendarSelector");
1459 updateCalendarStatus();
1460 selector.changeNotification = updateCalendarsList;
1462 var list = $("calendarList");
1463 list.multiselect = true;
1464 var items = list.childNodesWithTag("li");
1465 for (var i = 0; i < items.length; i++) {
1466 var input = items[i].childNodesWithTag("input")[0];
1467 Event.observe(input, "click", updateCalendarStatus.bindAsEventListener(input));
1468 Event.observe(items[i], "mousedown", listRowMouseDownHandler);
1469 Event.observe(items[i], "selectstart", listRowMouseDownHandler);
1470 Event.observe(items[i], "click", onRowClick);
1473 var links = $("calendarSelectorButtons").childNodesWithTag("a");
1474 Event.observe(links[0], "click", onCalendarNew);
1475 Event.observe(links[1], "click", onCalendarAdd);
1476 Event.observe(links[2], "click", onCalendarRemove);
1479 function onMenuModify(event) {
1480 var folders = $("calendarList");
1481 var selected = folders.getSelectedNodes()[0];
1483 if (UserLogin == selected.getAttribute("owner")) {
1484 var node = selected.childNodes[selected.childNodes.length - 1];
1485 var currentName = node.nodeValue.trim();
1486 var newName = window.prompt(labels["Name of the Calendar"],
1488 if (newName && newName.length > 0
1489 && newName != currentName) {
1490 var url = (URLForFolderID(selected.getAttribute("id"))
1491 + "/renameFolder?name=" + escape(newName.utf8encode()));
1492 triggerAjaxRequest(url, folderRenameCallback,
1493 {node: node, name: " " + newName});
1496 window.alert(clabels["Unable to rename that folder!"]);
1499 function folderRenameCallback(http) {
1500 if (http.readyState == 4) {
1501 if (isHttpStatus204(http.status)) {
1502 var dict = http.callbackData;
1503 dict["node"].nodeValue = dict["name"];
1508 function onCalendarNew(event) {
1509 createFolder(window.prompt(labels["Name of the Calendar"]),
1511 preventDefault(event);
1514 function onCalendarAdd(event) {
1515 openUserFolderSelector(onFolderSubscribeCB, "calendar");
1516 preventDefault(event);
1519 function appendCalendar(folderName, folderPath) {
1523 owner = getSubscribedFolderOwner(folderPath);
1524 folderPath = accessToSubscribedFolder(folderPath);
1527 folderPath = "/" + folderName;
1532 //log ("append name: " + folderName + "; path: " + folderPath + "; owner: " + owner);
1535 window.alert(clabels["You have already subscribed to that folder!"]);
1537 var calendarList = $("calendarList");
1538 var lis = calendarList.childNodesWithTag("li");
1539 var li = document.createElement("li");
1541 // Add the calendar to the proper place
1542 var previousOwner = null;
1543 for (var i = 0; i < lis.length; i++) {
1544 var currentFolderName = lis[i].lastChild.nodeValue.strip();
1545 var currentOwner = lis[i].readAttribute('owner');
1546 if (currentOwner == owner) {
1547 previousOwner = currentOwner;
1548 if (currentFolderName > folderName)
1551 else if (previousOwner ||
1552 (currentOwner != UserLogin && currentOwner > owner))
1555 if (i != lis.length) // User is subscribed to other calendars of the same owner
1556 calendarList.insertBefore(li, lis[i]);
1558 calendarList.appendChild(li);
1560 li.setAttribute("id", folderPath);
1561 li.setAttribute("owner", owner);
1563 // Generate new color
1564 if (calendarColorIndex == null)
1565 calendarColorIndex = lis.length;
1566 calendarColorIndex++;
1567 var colorTable = [1, 1, 1];
1569 var currentValue = calendarColorIndex;
1571 while (currentValue) {
1572 if (currentValue & 1)
1573 colorTable[index]++;
1579 colorTable[0] = parseInt(255 / colorTable[0]) - 1;
1580 colorTable[1] = parseInt(255 / colorTable[1]) - 1;
1581 colorTable[2] = parseInt(255 / colorTable[2]) - 1;
1584 + colorTable[2].toString(16)
1585 + colorTable[1].toString(16)
1586 + colorTable[0].toString(16);
1587 //log ("color = " + color);
1589 var checkBox = document.createElement("input");
1590 checkBox.setAttribute("type", "checkbox");
1591 li.appendChild(checkBox);
1592 li.appendChild(document.createTextNode(" "));
1593 $(checkBox).addClassName("checkBox");
1595 var colorBox = document.createElement("div");
1596 li.appendChild(colorBox);
1597 li.appendChild(document.createTextNode(folderName));
1598 colorBox.appendChild(document.createTextNode("OO"));
1600 $(colorBox).addClassName("colorBox");
1601 $(colorBox).addClassName('calendarFolder' + folderPath.substr(1));
1603 // Register events (doesn't work with Safari)
1604 Event.observe(li, "mousedown", listRowMouseDownHandler);
1605 Event.observe(li, "selectstart", listRowMouseDownHandler);
1606 Event.observe(li, "click", onRowClick);
1607 Event.observe(checkBox, "click",
1608 updateCalendarStatus.bindAsEventListener(checkBox));
1610 var url = URLForFolderID(folderPath) + "/canAccessContent";
1611 triggerAjaxRequest(url, calendarEntryCallback, folderPath);
1613 // Update CSS for events color
1614 if (!document.styleSheets) return;
1616 var styleElement = document.createElement("style");
1617 styleElement.type = "text/css";
1619 '.calendarFolder' + folderPath.substr(1),
1620 'div.colorBox.calendarFolder' + folderPath.substr(1)
1623 ' { background-color: ' + color + ' !important; }',
1624 ' { color: ' + color + ' !important; }'
1626 for (var i = 0; i < rules.length; i++)
1627 if (styleElement.styleSheet && styleElement.styleSheet.addRule)
1628 styleElement.styleSheet.addRule(selectors[i], rules[i]); // IE
1630 styleElement.appendChild(document.createTextNode(selectors[i] + rules[i])); // Mozilla _+ Safari
1631 document.getElementsByTagName("head")[0].appendChild(styleElement);
1635 function onFolderSubscribeCB(folderData) {
1636 var folder = $(folderData["folder"]);
1638 appendCalendar(folderData["folderName"], folderData["folder"]);
1641 function onFolderUnsubscribeCB(folderId) {
1642 var node = $(folderId);
1643 node.parentNode.removeChild(node);
1644 if (removeFolderRequestCount == 0) {
1647 changeCalendarDisplay();
1651 function onCalendarRemove(event) {
1652 if (removeFolderRequestCount == 0) {
1653 var nodes = $("calendarList").getSelectedNodes();
1654 for (var i = 0; i < nodes.length; i++) {
1655 nodes[i].deselect();
1656 var folderId = nodes[i].getAttribute("id");
1657 var folderIdElements = folderId.split("_");
1658 if (folderIdElements.length > 1) {
1659 unsubscribeFromFolder(folderId, onFolderUnsubscribeCB, folderId);
1662 deletePersonalCalendar(folderIdElements[0]);
1666 preventDefault(event);
1669 function deletePersonalCalendar(folderElement) {
1670 var folderId = folderElement.substr(1);
1672 = labels["Are you sure you want to delete the calendar \"%{0}\"?"].formatted($(folderElement).lastChild.nodeValue.strip());
1673 if (window.confirm(label)) {
1674 removeFolderRequestCount++;
1675 var url = ApplicationBaseURL + "/" + folderId + "/deleteFolder";
1676 triggerAjaxRequest(url, deletePersonalCalendarCallback, folderId);
1680 function deletePersonalCalendarCallback(http) {
1681 if (http.readyState == 4) {
1682 if (isHttpStatus204(http.status)) {
1683 var ul = $("calendarList");
1684 var children = ul.childNodesWithTag("li");
1687 while (!done && i < children.length) {
1688 var currentFolderId = children[i].getAttribute("id").substr(1);
1689 if (currentFolderId == http.callbackData) {
1690 ul.removeChild(children[i]);
1696 removeFolderRequestCount--;
1697 if (removeFolderRequestCount == 0) {
1700 changeCalendarDisplay();
1705 log ("ajax problem 5: " + http.status);
1708 function configureLists() {
1709 var list = $("tasksList");
1710 list.multiselect = true;
1711 Event.observe(list, "mousedown",
1712 onTasksSelectionChange.bindAsEventListener(list));
1714 var input = $("showHideCompletedTasks");
1715 Event.observe(input, "click",
1716 onShowCompletedTasks.bindAsEventListener(input));
1718 list = $("eventsList");
1719 list.multiselect = true;
1720 //configureSortableTableHeaders(list);
1721 TableKit.Resizable.init(list, {'trueResize' : true, 'keepWidth' : true});
1722 Event.observe(list, "mousedown",
1723 onEventsSelectionChange.bindAsEventListener(list));
1724 var div = list.parentNode;
1725 Event.observe(div, "contextmenu",
1726 onEventContextMenu.bindAsEventListener(div));
1729 function initDateSelectorEvents() {
1730 var arrow = $("rightArrow");
1731 Event.observe(arrow, "click",
1732 onDateSelectorGotoMonth.bindAsEventListener(arrow));
1733 arrow = $("leftArrow");
1734 Event.observe(arrow, "click",
1735 onDateSelectorGotoMonth.bindAsEventListener(arrow));
1737 var menuButton = $("monthLabel");
1738 Event.observe(menuButton, "click",
1739 popupMonthMenu.bindAsEventListener(menuButton));
1740 menuButton = $("yearLabel");
1741 Event.observe(menuButton, "click",
1742 popupMonthMenu.bindAsEventListener(menuButton));
1745 function initCalendars() {
1746 if (!document.body.hasClassName("popup")) {
1747 initDateSelectorEvents();
1748 initCalendarSelector();
1749 configureSearchField();
1751 var selector = $("calendarSelector");
1753 selector.attachMenu("calendarsMenu");
1757 FastInit.addOnLoad(initCalendars);