1 /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 *
15 * The Original Code is Oracle Corporation code.
16 *
17 * The Initial Developer of the Original Code is Oracle Corporation
18 * Portions created by the Initial Developer are Copyright (C) 2005
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Contributor(s):
22 * Stuart Parmenter <stuart.parmenter@oracle.com>
23 * Joey Minta <jminta@gmail.com>
24 *
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
36 *
37 * ***** END LICENSE BLOCK ***** */
38
39 var gReadOnlyMode = false;
40
41 // If the user clicks 'More', then we set this to true. Otherwise, we don't
42 // load/check the stuff in this area, because it hasn't changed.
43 var gDetailsShown = false;
44
45 var gItemDuration;
46
47 /* dialog stuff */
onLoad
48 function onLoad()
49 {
50 var args = window.arguments[0];
51
52 window.onAcceptCallback = args.onOk;
53 window.calendarItem = args.calendarEvent;
54 window.originalItem = args.calendarEvent;
55 window.mode = args.mode;
56 window.recurrenceInfo = null;
57
58 // the calling entity provides us with an object that is responsible
59 // for recording details about the initiated modification. the 'finalize'-property
60 // is our hook in order to receive a notification in case the operation needs
61 // to be terminated prematurely. this function will be called if the calling
62 // entity needs to immediately terminate the pending modification. in this
63 // case we serialize the item and close the window.
64 if (args.job) {
65
66 // keep this context...
67 var self = this;
68
69 // store the 'finalize'-functor in the provided job-object.
70 args.job.finalize = function finalize() {
71
72 // store any pending modifications...
73 self.onAccept();
74
75 var item = window.calendarItem;
76
77 // ...and close the window.
78 window.close();
79
80 return item;
81 }
82 }
83
84 if (window.calendarItem.calendar && window.calendarItem.calendar.readOnly
85 && window.mode != "new") {
86 gReadOnlyMode = true;
87 }
88
89 /* add calendars to the calendar menulist */
90 var calendarList = document.getElementById("item-calendar");
91 var calendars = getCalendarManager().getCalendars({});
92 for (i in calendars) {
93 var calendar = calendars[i];
94 var menuitem = calendarList.appendItem(calendar.name, i);
95 menuitem.calendar = calendar;
96 }
97
98 document.getElementById("send-invitations-checkbox").collapsed = isSunbird();
99
100 loadDialog(window.calendarItem);
101
102 // figure out what the title of the dialog should be and set it
103 updateTitle();
104
105 // hide rows based on if this is an event or todo
106 updateStyle();
107
108 // update the accept button
109 updateAccept();
110
111 // update datetime pickers
112 updateDueDate();
113 updateEntryDate();
114
115 // update datetime pickers
116 updateAllDay();
117
118 // update recurrence button
119 updateRecurrence();
120
121 // update our size!
122 window.sizeToContent();
123
124 document.getElementById("item-title").focus();
125
126 opener.setCursor("auto");
127 }
128
dispose
129 function dispose()
130 {
131 var args = window.arguments[0];
132 if(args.job && args.job.dispose)
133 args.job.dispose();
134 }
135
onAccept
136 function onAccept()
137 {
138 // we need to clone the item in order to apply the changes.
139 // it is important to not apply the changes to the original item
140 // (even if it happens to be mutable) in order to guarantee
141 // that providers see a proper oldItem/newItem pair in case
142 // they rely on this fact (e.g. WCAP does).
143 var originalItem = window.calendarItem;
144 var item = originalItem.clone();
145
146 saveDialog(item);
147
148 var calendar = document.getElementById("item-calendar").selectedItem.calendar;
149
150 window.onAcceptCallback(item, calendar, window.originalItem);
151
152 // We already set persist="collapsed" in the xul file, but because
153 // of a bug on 1_8_BRANCH we need this to make it really persist.
154 document.persist("description-row", "collapsed");
155
156 dispose();
157
158 window.calendarItem = item;
159
160 return true;
161 }
162
onCancel
163 function onCancel()
164 {
165 dispose();
166 }
167
loadDialog
168 function loadDialog(item)
169 {
170 var kDefaultTimezone = window.opener.calendarDefaultTimezone();
171
172 setElementValue("item-title", item.title);
173 setElementValue("item-location", item.getProperty("LOCATION"));
174
175 /* event specific properties */
176 if (isEvent(item)) {
177 var startDate = item.startDate.getInTimezone(kDefaultTimezone);
178 var endDate = item.endDate.getInTimezone(kDefaultTimezone);
179 gItemDuration = endDate.subtractDate(startDate);
180
181 // Check if an all-day event has been passed in (to adapt endDate).
182 if (startDate.isDate) {
183 setElementValue("event-all-day", true, "checked");
184 endDate.day -= 1;
185 gItemDuration.days -= 1;
186 }
187
188 setElementValue("event-starttime", startDate.jsDate);
189 setElementValue("event-endtime", endDate.jsDate);
190 document.getElementById("component-type").selectedIndex = 0;
191 }
192
193 /* todo specific properties */
194 if (isToDo(item)) {
195 var entryDate;
196 var hasEntryDate = (item.entryDate != null);
197 setElementValue("todo-has-entrydate", hasEntryDate, "checked");
198 if (hasEntryDate) {
199 entryDate = item.entryDate.getInTimezone(kDefaultTimezone);
200 setElementValue("todo-entrydate", entryDate.jsDate);
201 }
202
203 var dueDate;
204 var hasDueDate = (item.dueDate != null);
205 setElementValue("todo-has-duedate", hasDueDate, "checked");
206 if (hasDueDate) {
207 dueDate = item.dueDate.getInTimezone(kDefaultTimezone);
208 setElementValue("todo-duedate", dueDate.jsDate);
209 }
210 if (hasEntryDate && hasDueDate) {
211 gItemDuration = item.dueDate.subtractDate(item.entryDate);
212 }
213 document.getElementById("component-type").selectedIndex = 1;
214 }
215
216 /* item default calendar */
217 // If this is a new item, it might not have a calendar, but a default
218 // option could well have been passed in.
219 var calendarToUse = item.calendar || window.arguments[0].calendar
220 if (calendarToUse) {
221 var calendarList = document.getElementById("item-calendar");
222 var calendars = getCalendarManager().getCalendars({});
223 for (i in calendars) {
224 if (calendarToUse.uri.equals(calendars[i].uri))
225 calendarList.selectedIndex = i;
226 }
227 } else {
228 // no calendar attached to item
229 // select first entry in calendar list as default
230 document.getElementById("item-calendar").selectedIndex = 0;
231 }
232
233 /* Categories */
234 try {
235 var categoriesList = getPrefCategoriesArray();
236
237 // insert the category already in the menulist so it doesn't get lost
238 var itemProperty = item.getProperty("CATEGORIES");
239 if (itemProperty) {
240 var itemCategories = categoriesStringToArray(itemProperty);
241 for each (var itemCategory in itemCategories) {
242 if (!categoriesList.some(function(cat){ return cat == itemCategory; })){
243 categoriesList.push(itemCategory);
244 }
245 }
246 }
247 sortArrayByLocaleCollator(categoriesList);
248
249 var oldMenulist = document.getElementById("item-categories");
250 while (oldMenulist.hasChildNodes()) {
251 oldMenulist.removeChild(oldMenulist.lastChild);
252 }
253
254 var categoryMenuList = document.getElementById("item-categories");
255 var indexToSelect = 0;
256
257 // Add a 'none' option to allow users to cancel the category
258 var noneItem = categoryMenuList.appendItem(calGetString("calendar", "None"), "NONE");
259
260 for (var i in categoriesList) {
261 var catItem = categoryMenuList.appendItem(categoriesList[i], categoriesList[i]);
262 catItem.value = categoriesList[i];
263 if (itemCategory && categoriesList[i] == itemCategory) {
264 indexToSelect = parseInt(i)+1; // Add 1 because of 'None'
265 }
266 }
267 var newCategory = calGetString("calendar", "newCategory");
268 categoryMenuList.appendItem(newCategory, "##NEW");
269 categoryMenuList.selectedIndex = indexToSelect;
270 gOldCatIndex = indexToSelect;
271
272 } catch (ex) {
273 // The app using this dialog doesn't support categories
274 document.getElementById("categories-box").collapsed = true;
275 }
276
277
278 /* recurrence */
279 /* if the item is a proxy occurrence/instance, a few things aren't valid:
280 * - Setting recurrence on the item
281 * - changing the calendar
282 */
283 if (item.parentItem != item) {
284 setElementValue("item-recurrence", "true", "disabled");
285 setElementValue("set-recurrence", "true", "disabled");
286 setElementValue("item-calendar", "true", "disabled");
287
288 // don't allow to revoke the entrydate of recurring todo's.
289 disableElement("todo-has-entrydate");
290 } else if (item.recurrenceInfo)
291 setElementValue("item-recurrence", "true", "checked");
292
293 /* Alarms */
294 if (item.alarmOffset) {
295 var alarmRelatedStart = (item.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_START);
296 if (alarmRelatedStart) {
297 setElementValue("alarm-trigger-relation", "START");
298 } else {
299 setElementValue("alarm-trigger-relation", "END");
300 }
301
302 var offset = item.alarmOffset;
303 if (offset.minutes) {
304 var minutes = offset.minutes + offset.hours*60 + offset.days*24*60 + offset.weeks*60*24*7;
305 // Special cases for the common alarms
306 if ((minutes == 15) && alarmRelatedStart) {
307 document.getElementById("item-alarm").selectedIndex = 2;
308 } else if ((minutes == 30) && alarmRelatedStart) {
309 document.getElementById("item-alarm").selectedIndex = 3;
310 } else {
311 setElementValue("alarm-length-field", minutes);
312 setElementValue("alarm-length-units", "minutes");
313 setElementValue("item-alarm", "custom");
314 }
315 } else if (offset.hours) {
316 var hours = offset.hours + offset.days*24 + offset.weeks*24*7;
317 setElementValue("alarm-length-field", hours);
318 setElementValue("alarm-length-units", "hours");
319 setElementValue("item-alarm", "custom");
320 } else { // days
321 var days = offset.days + offset.weeks*7;
322 setElementValue("alarm-length-field", days);
323 setElementValue("alarm-length-units", "days");
324 setElementValue("item-alarm", "custom");
325 }
326 }
327
328 var detailsButton = document.getElementById("calendar-event-dialog").getButton("disclosure");
329 var detailsElements = document.getElementsByAttribute("details", "true");
330
331 if (document.getElementById("description-row").getAttribute("collapsed") != "true") {
332 detailsButton.setAttribute("label", lessLabel);
333 for each (elem in detailsElements) {
334 elem.collapsed = false;
335 }
336 loadDetails();
337 } else {
338 for each (elem in detailsElements) {
339 elem.collapsed = true;
340 }
341 detailsButton.setAttribute("label", moreLabel);
342 }
343 }
344
saveDialog
345 function saveDialog(item)
346 {
347 setItemProperty(item, "title", getElementValue("item-title"));
348 setItemProperty(item, "LOCATION", getElementValue("item-location"));
349
350 var kDefaultTimezone = window.opener.calendarDefaultTimezone();
351
352 if (isEvent(item)) {
353 var startDate = jsDateToDateTime(getElementValue("event-starttime"));
354 var endDate = jsDateToDateTime(getElementValue("event-endtime"));
355 startDate = startDate.getInTimezone(kDefaultTimezone);
356 endDate = endDate.getInTimezone(kDefaultTimezone);
357
358 var isAllDay = getElementValue("event-all-day", "checked");
359 if (isAllDay) {
360 startDate.isDate = true;
361
362 endDate.isDate = true;
363 endDate.day += 1;
364 }
365
366 setItemProperty(item, "startDate", startDate);
367 setItemProperty(item, "endDate", endDate);
368 }
369
370 if (isToDo(item)) {
371 var entryDate = getElementValue("todo-has-entrydate", "checked") ?
372 jsDateToDateTime(getElementValue("todo-entrydate")) : null;
373 if (entryDate) {
374 entryDate = entryDate.getInTimezone(kDefaultTimezone);
375 } else {
376 // no entrydate, no recurrence
377 item.recurrenceInfo = null;
378 window.recurrenceInfo = null;
379 }
380 setItemProperty(item, "entryDate", entryDate);
381
382 var dueDate = getElementValue("todo-has-duedate", "checked") ?
383 jsDateToDateTime(getElementValue("todo-duedate")) : null;
384 if (dueDate) {
385 dueDate = dueDate.getInTimezone(kDefaultTimezone);
386 }
387 setItemProperty(item, "dueDate", dueDate);
388
389 var percentCompleteInteger = 0;
390 if (getElementValue("percent-complete-textbox") != "") {
391 percentCompleteInteger = parseInt(getElementValue("percent-complete-textbox"));
392 }
393 if (percentCompleteInteger < 0) {
394 percentCompleteInteger = 0;
395 } else if (percentCompleteInteger > 100) {
396 percentCompleteInteger = 100;
397 }
398 setItemProperty(item, "PERCENT-COMPLETE", percentCompleteInteger);
399 }
400
401 /* recurrence */
402 if (getElementValue("item-recurrence", "checked")) {
403 if (window.recurrenceInfo) {
404 item.recurrenceInfo = window.recurrenceInfo;
405 }
406 } else {
407 item.recurrenceInfo = null;
408 }
409
410 /* Category */
411 var category = getElementValue("item-categories");
412
413 if (category != "NONE") {
414 setItemProperty(item, "CATEGORIES", categoriesArrayToString([category]));
415 } else {
416 item.deleteProperty("CATEGORIES");
417 }
418
419 if (!gDetailsShown) {
420 // We never showed the items in the 'More' box. That means that clone()
421 // took care of it, so just return now
422 dump(item.icalString + '\n');
423 return;
424 }
425
426 setItemProperty(item, "URL", getElementValue("item-url"));
427 setItemProperty(item, "DESCRIPTION", getElementValue("item-description"));
428
429 var status;
430 if (isEvent(item)) {
431 status = getElementValue("event-status");
432 } else {
433 status = getElementValue("todo-status");
434 if (status != "COMPLETED") {
435 item.completedDate = null;
436 }
437 }
438
439 setItemProperty(item, "STATUS", status);
440 setItemProperty(item, "PRIORITY", getElementValue("priority-levels"));
441 setItemProperty(item, "CLASS", getElementValue("privacy-menulist"));
442
443 if (item.status == "COMPLETED" && isToDo(item)) {
444 item.completedDate = jsDateToDateTime(getElementValue("completed-date-picker"));
445 }
446
447 // Attendees
448 item.removeAllAttendees();
449 var attendeeListBox = document.getElementById("attendees-list");
450 for each (att in attendeeListBox.attendees) {
451 item.addAttendee(att);
452 }
453 var sendInvitesCheckbox = document.getElementById("send-invitations-checkbox");
454 if (sendInvitesCheckbox.checked) {
455 setItemProperty(item, "X-MOZ-SEND-INVITATIONS", "TRUE");
456 } else {
457 item.deleteProperty("X-MOZ-SEND-INVITATIONS");
458 }
459
460 /* alarms */
461 var hasAlarm = (getElementValue("item-alarm") != "none");
462 if (!hasAlarm) {
463 item.alarmOffset = null;
464 item.alarmLastAck = null;
465 item.alarmRelated = null;
466 } else {
467 var alarmLength = getElementValue("alarm-length-field");
468 var alarmUnits = document.getElementById("alarm-length-units").selectedItem.value;
469 if (document.getElementById("alarm-trigger-relation").selectedItem.value == "START") {
470 item.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
471 } else {
472 item.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_END;
473 }
474 var duration = Components.classes["@mozilla.org/calendar/duration;1"]
475 .createInstance(Components.interfaces.calIDuration);
476 if (item.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_START) {
477 duration.isNegative = true;
478 }
479 duration[alarmUnits] = alarmLength;
480 duration.normalize();
481
482 item.alarmOffset = duration;
483 }
484
485 dump(item.icalString + "\n");
486 }
487
updateTitle
488 function updateTitle()
489 {
490 var isNew = window.calendarItem.isMutable;
491 if (isEvent(window.calendarItem)) {
492 if (isNew)
493 document.title = calGetString("calendar", "newEventDialog");
494 else
495 document.title = calGetString("calendar", "editEventDialog");
496 } else if (isToDo(window.calendarItem)) {
497 if (isNew)
498 document.title = calGetString("calendar", "newTaskDialog");
499 else
500 document.title = calGetString("calendar", "editTaskDialog");
501 }
502 }
503
updateComponentType
504 function updateComponentType(aValue) {
505 if ((aValue == "event" && isEvent(window.calendarItem)) ||
506 (aValue == "todo" && isToDo(window.calendarItem))) {
507 return;
508 }
509 var oldItem = window.calendarItem.clone();
510 saveDialog(oldItem);
511
512 var newItem;
513 if (aValue == "event") {
514 newItem = createEvent();
515 oldItem.wrappedJSObject.cloneItemBaseInto(newItem.wrappedJSObject);
516 newItem.startDate = oldItem.entryDate || now();
517 newItem.endDate = oldItem.dueDate || now();
518 } else {
519 newItem = createTodo();
520 oldItem.wrappedJSObject.cloneItemBaseInto(newItem.wrappedJSObject);
521 newItem.entryDate = oldItem.startDate;
522 newItem.dueDate = oldItem.endDate;
523 }
524 window.calendarItem = newItem;
525
526 loadDialog(newItem);
527
528 // remove old style rule, so the hidden stuff comes back
529 const kDialogStylesheet = "chrome://calendar/content/calendar-event-dialog.css";
530
531 for each(var stylesheet in document.styleSheets) {
532 if (stylesheet.href != kDialogStylesheet) {
533 continue;
534 }
535 for (var i=0; i < stylesheet.cssRules.length; i++) {
536 if (stylesheet.cssRules[i].selectorText == ".todo-only" ||
537 stylesheet.cssRules[i].selectorText == ".event-only") {
538 stylesheet.deleteRule(i);
539 break;
540 }
541 }
542 break;
543 }
544
545 updateStyle();
546 updateAccept();
547 updateDueDate();
548 updateEntryDate();
549 updateAllDay();
550 updateRecurrence();
551
552 window.sizeToContent();
553 }
554
updateStyle
555 function updateStyle()
556 {
557 const kDialogStylesheet = "chrome://calendar/content/calendar-event-dialog.css";
558
559 for each(var stylesheet in document.styleSheets) {
560 if (stylesheet.href == kDialogStylesheet) {
561 if (isEvent(window.calendarItem))
562 stylesheet.insertRule(".todo-only { display: none; }", 0);
563 else if (isToDo(window.calendarItem))
564 stylesheet.insertRule(".event-only { display: none; }", 0);
565 return;
566 }
567 }
568 }
569
onStartTimeChange
570 function onStartTimeChange()
571 {
572 if (!gItemDuration) {
573 return;
574 }
575 var startWidgetId;
576 var endWidgetId;
577 if (isEvent(window.calendarItem)) {
578 startWidgetId = "event-starttime";
579 endWidgetId = "event-endtime";
580 } else {
581 if (!getElementValue("todo-has-entrydate", "checked") || !getElementValue("todo-has-duedate", "checked")) {
582 gItemDuration = null;
583 return;
584 }
585 startWidgetId = "todo-entrydate";
586 endWidgetId = "todo-duedate";
587 }
588 var start = jsDateToDateTime(getElementValue(startWidgetId));
589 start.addDuration(gItemDuration);
590 setElementValue(endWidgetId, start.getInTimezone(window.opener.calendarDefaultTimezone()).jsDate);
591 updateAccept();
592 }
593
onEndTimeChange
594 function onEndTimeChange()
595 {
596 var startWidgetId;
597 var endWidgetId;
598 if (isEvent(window.calendarItem)) {
599 startWidgetId = "event-starttime";
600 endWidgetId = "event-endtime";
601 } else {
602 if (!getElementValue("todo-has-entrydate", "checked") ||
603 !getElementValue("todo-has-duedate", "checked")) {
604 gItemDuration = null;
605 return;
606 }
607 startWidgetId = "todo-entrydate";
608 endWidgetId = "todo-duedate";
609 }
610 var start = jsDateToDateTime(getElementValue(startWidgetId));
611 var end = jsDateToDateTime(getElementValue(endWidgetId));
612 gItemDuration = end.subtractDate(start);
613 updateAccept();
614 }
updateAccept
615 function updateAccept()
616 {
617 var enableAccept = true;
618
619 var kDefaultTimezone = window.opener.calendarDefaultTimezone();
620
621 var title = getElementValue("item-title");
622 if (title.length == 0)
623 enableAccept = false;
624
625 // don't allow for end dates to be before start dates
626 var startDate;
627 var endDate;
628 if (isEvent(window.calendarItem)) {
629 startDate = jsDateToDateTime(getElementValue("event-starttime"));
630 endDate = jsDateToDateTime(getElementValue("event-endtime"));
631
632 // For all-day events we are not interested in times and compare only dates.
633 if (getElementValue("event-all-day", "checked")) {
634 // jsDateToDateTime returnes the values in UTC. Depending on the local
635 // timezone and the values selected in datetimepicker the date in UTC
636 // might be shifted to the previous or next day.
637 // For example: The user (with local timezone GMT+05) selected
638 // Feb 10 2006 00:00:00. The corresponding value in UTC is
639 // Feb 09 2006 19:00:00. If we now set isDate to true we end up with
640 // a date of Feb 09 2006 instead of Feb 10 2006 resulting in errors
641 // during the following comparison.
642 // Calling getInTimezone() ensures that we use the same dates as
643 // displayed to the user in datetimepicker for comparison.
644 startDate = startDate.getInTimezone(kDefaultTimezone);
645 endDate = endDate.getInTimezone(kDefaultTimezone);
646 startDate.isDate = true;
647 endDate.isDate = true;
648 }
649 } else {
650 startDate = getElementValue("todo-has-entrydate", "checked") ?
651 jsDateToDateTime(getElementValue("todo-entrydate")) : null;
652 endDate = getElementValue("todo-has-duedate", "checked") ?
653 jsDateToDateTime(getElementValue("todo-duedate")) : null;
654
655 var taskRepeatWarning = document.getElementById("task-repeat-warning");
656 if (!startDate && getElementValue("item-recurrence", "checked")) {
657 enableAccept = false;
658 taskRepeatWarning.removeAttribute("hidden");
659 } else {
660 taskRepeatWarning.setAttribute("hidden", "true");
661 }
662 }
663
664 var timeWarning = document.getElementById("end-time-warning");
665 if (endDate && startDate && endDate.compare(startDate) == -1) {
666 enableAccept = false;
667 timeWarning.removeAttribute("hidden");
668 } else {
669 timeWarning.setAttribute("hidden", "true");
670 }
671
672 // can't add/edit items in readOnly calendars
673 document.getElementById("read-only-item").setAttribute("hidden", !gReadOnlyMode);
674 var cal = document.getElementById("item-calendar").selectedItem.calendar;
675 document.getElementById("read-only-cal").setAttribute("hidden",
676 !cal.readOnly);
677 if (gReadOnlyMode || cal.readOnly) {
678 enableAccept = false;
679 }
680
681 if (cal.sendItipInvitations) {
682 enableElement("send-invitations-checkbox");
683 } else {
684 disableElement("send-invitations-checkbox");
685 }
686
687 if (!updateTaskAlarmWarnings()) {
688 enableAccept = false;
689 }
690
691 var acceptButton = document.getElementById("calendar-event-dialog").getButton("accept");
692 if (!enableAccept) {
693 acceptButton.setAttribute("disabled", "true");
694 } else if (acceptButton.getAttribute("disabled")) {
695 acceptButton.removeAttribute("disabled");
696 }
697
698 return;
699 }
700
updateDueDate
701 function updateDueDate()
702 {
703 if (!isToDo(window.calendarItem))
704 return;
705
706 // force something to get set if there was nothing there before
707 setElementValue("todo-duedate", getElementValue("todo-duedate"));
708
709 setElementValue("todo-duedate", !getElementValue("todo-has-duedate", "checked"), "disabled");
710 if (getElementValue("todo-has-entrydate", "checked") && getElementValue("todo-has-duedate", "checked")) {
711 var start = jsDateToDateTime(getElementValue("todo-entrydate"));
712 var end = jsDateToDateTime(getElementValue("todo-duedate"));
713 gItemDuration = end.subtractDate(start);
714 } else {
715 gItemDuration = null;
716 }
717
718 updateAccept();
719 }
720
updateEntryDate
721 function updateEntryDate()
722 {
723 if (!isToDo(window.calendarItem))
724 return;
725
726 // force something to get set if there was nothing there before
727 setElementValue("todo-entrydate", getElementValue("todo-entrydate"));
728
729 setElementValue("todo-entrydate", !getElementValue("todo-has-entrydate", "checked"), "disabled");
730
731 if (getElementValue("todo-has-entrydate", "checked") && getElementValue("todo-has-duedate", "checked")) {
732 var start = jsDateToDateTime(getElementValue("todo-entrydate"));
733 var end = jsDateToDateTime(getElementValue("todo-duedate"));
734 gItemDuration = end.subtractDate(start);
735 } else {
736 gItemDuration = null;
737 }
738
739 updateAccept();
740 }
741
updateTaskAlarmWarnings
742 function updateTaskAlarmWarnings() {
743 document.getElementById("alarm-warnings").setAttribute("hidden", true);
744 document.getElementById("alarm-start-warning").setAttribute("hidden", true);
745 document.getElementById("alarm-end-warning").setAttribute("hidden", true);
746
747 var alarmType = getElementValue("item-alarm");
748 if (!isToDo(window.calendarItem) || alarmType == "none") {
749 return true;
750 }
751
752 var hasEntryDate = getElementValue("todo-has-entrydate", "checked");
753 var hasDueDate = getElementValue("todo-has-duedate", "checked");
754
755 var alarmRelated = document.getElementById("alarm-trigger-relation").selectedItem.value;
756
757 if ((alarmType != "custom" || alarmRelated == "START") && !hasEntryDate) {
758 document.getElementById("alarm-warnings").removeAttribute("hidden");
759 document.getElementById("alarm-start-warning").removeAttribute("hidden");
760 return false;
761 }
762
763 if (alarmRelated == "END" && !hasDueDate) {
764 document.getElementById("alarm-warnings").removeAttribute("hidden");
765 document.getElementById("alarm-end-warning").removeAttribute("hidden");
766 return false;
767 }
768
769 return true;
770 }
771
updateAllDay
772 function updateAllDay()
773 {
774 if (!isEvent(window.calendarItem))
775 return;
776
777 var allDay = getElementValue("event-all-day", "checked");
778 setElementValue("event-starttime", allDay, "timepickerdisabled");
779 setElementValue("event-endtime", allDay, "timepickerdisabled");
780
781 if (!allDay) {
782 // Reset default event length, if timepickers are equal
783 var startDate = jsDateToDateTime(getElementValue("event-starttime"));
784 var endDate = jsDateToDateTime(getElementValue("event-endtime"));
785 if (startDate.compare(endDate) == 0) {
786 endDate.minute += getPrefSafe("calendar.event.defaultlength", 60);
787 setElementValue("event-endtime", endDate.jsDate);
788 }
789 }
790
791 updateAccept();
792 }
793
794
updateRecurrence
795 function updateRecurrence()
796 {
797 var recur = getElementValue("item-recurrence", "checked");
798 if (recur) {
799 setElementValue("set-recurrence", false, "disabled");
800 } else {
801 setElementValue("set-recurrence", "true", "disabled");
802 }
803
804 updateAccept();
805 }
806
807 var prevAlarmItem = null;
setAlarmFields
808 function setAlarmFields(alarmItem)
809 {
810 var alarmLength = alarmItem.getAttribute("length");
811 if (alarmLength != "") {
812 var alarmUnits = alarmItem.getAttribute("unit");
813 var alarmRelation = alarmItem.getAttribute("relation");
814 setElementValue("alarm-length-field", alarmLength);
815 setElementValue("alarm-length-units", alarmUnits);
816 setElementValue("alarm-trigger-relation", alarmRelation);
817 }
818 }
updateAlarm
819 function updateAlarm()
820 {
821 var alarmMenu = document.getElementById("item-alarm");
822 var alarmItem = alarmMenu.selectedItem;
823
824 var alarmItemValue = alarmItem.getAttribute("value");
825 switch (alarmItemValue) {
826 case "custom":
827 /* restore old values if they're around */
828 setAlarmFields(alarmItem);
829
830 document.getElementById("alarm-details").removeAttribute("hidden");
831 break;
832 default:
833 var customItem = document.getElementById("alarm-custom-menuitem");
834 if (prevAlarmItem == customItem) {
835 customItem.setAttribute("length", getElementValue("alarm-length-field"));
836 customItem.setAttribute("unit", getElementValue("alarm-length-units"));
837 customItem.setAttribute("relation", getElementValue("alarm-trigger-relation"));
838 }
839 setAlarmFields(alarmItem);
840
841 document.getElementById("alarm-details").setAttribute("hidden", true);
842 break;
843 }
844
845 prevAlarmItem = alarmItem;
846 updateAccept();
847
848 this.sizeToContent();
849 }
850
editRecurrence
851 function editRecurrence()
852 {
853 var args = new Object();
854 args.calendarEvent = window.calendarItem;
855 args.recurrenceInfo = window.recurrenceInfo || args.calendarEvent.recurrenceInfo;
856
857 var kDefaultTimezone = window.opener.calendarDefaultTimezone();
858 if (isEvent(window.calendarItem)) {
859 var startDate = jsDateToDateTime(getElementValue("event-starttime")).getInTimezone(kDefaultTimezone);
860 if (getElementValue("event-all-day", "checked")) {
861 startDate.isDate = true;
862 }
863 args.startDate = startDate;
864 } else if (isToDo(window.calendarItem)) {
865 if (!getElementValue("todo-has-entrydate", "checked")) {
866 return;
867 }
868 args.startDate = jsDateToDateTime(getElementValue("todo-entrydate")).getInTimezone(kDefaultTimezone);
869 }
870
871 var savedWindow = window;
872 args.onOk = function(recurrenceInfo) {
873 savedWindow.recurrenceInfo = recurrenceInfo;
874 };
875
876 // wait cursor will revert to auto in eventDialog.js loadCalendarEventDialog
877 window.setCursor("wait");
878
879 // open the dialog modally
880 openDialog("chrome://calendar/content/calendar-recurrence-dialog.xul", "_blank", "chrome,titlebar,modal", args);
881 }
882
883
884
885 /* utility functions */
setItemProperty
886 function setItemProperty(item, propertyName, value)
887 {
888 switch(propertyName) {
889 case "startDate":
890 if (value.isDate && !item.startDate.isDate ||
891 !value.isDate && item.startDate.isDate ||
892 value.timezone != item.startDate.timezone ||
893 value.compare(item.startDate) != 0)
894 item.startDate = value;
895 break;
896 case "endDate":
897 if (value.isDate && !item.endDate.isDate ||
898 !value.isDate && item.endDate.isDate ||
899 value.timezone != item.endDate.timezone ||
900 value.compare(item.endDate) != 0)
901 item.endDate = value;
902 break;
903
904 case "entryDate":
905 if (value == item.entryDate)
906 break;
907 if ((value && !item.entryDate) ||
908 (!value && item.entryDate) ||
909 (value.timezone != item.entryDate.timezone) ||
910 (value.compare(item.entryDate) != 0) ||
911 (value.isDate != item.entryDate.isDate))
912 item.entryDate = value;
913 break;
914 case "dueDate":
915 if (value == item.dueDate)
916 break;
917 if ((value && !item.dueDate) ||
918 (!value && item.dueDate) ||
919 (value.timezone != item.dueDate.timezone) ||
920 (value.compare(item.dueDate) != 0) ||
921 (value.isDate != item.dueDate.isDate))
922 item.dueDate = value;
923 break;
924 case "isCompleted":
925 if (value != item.isCompleted)
926 item.isCompleted = value;
927 break;
928
929 case "title":
930 if (value != item.title)
931 item.title = value;
932 break;
933
934 default:
935 if (!value || value == "")
936 item.deleteProperty(propertyName);
937 else if (item.getProperty(propertyName) != value)
938 item.setProperty(propertyName, value);
939 break;
940 }
941 }
942
toggleDetails
943 function toggleDetails() {
944 var detailsElements = document.getElementsByAttribute("details", "true");
945 var detailsButton = document.getElementById("calendar-event-dialog").getButton("disclosure");
946
947 if (!detailsElements[0].collapsed) {
948 // Hide details
949 for each (elem in detailsElements) {
950 elem.collapsed = true;
951 }
952 detailsButton.setAttribute("label", moreLabel);
953 this.sizeToContent();
954 return;
955 }
956
957 // Display details
958 for each (elem in detailsElements) {
959 elem.collapsed = false;
960 }
961 detailsButton.setAttribute("label", lessLabel);
962 this.sizeToContent();
963
964 if (gDetailsShown) {
965 // Focus the description
966 document.getElementById("item-description").focus();
967
968 // We've already loaded this stuff before, so we're done
969 return;
970 }
971
972 loadDetails();
973
974 // Now focus the description
975 document.getElementById("item-description").focus();
976 }
977
loadDetails
978 function loadDetails() {
979 gDetailsShown = true;
980 var item = window.calendarItem;
981
982 // Attendees
983 var attendeeListBox = document.getElementById("attendees-list");
984 attendeeListBox.attendees = item.getAttendees({});
985 var sendInvitesCheckbox = document.getElementById("send-invitations-checkbox");
986 if (item.hasProperty("X-MOZ-SEND-INVITATIONS")) {
987 sendInvitesCheckbox.checked = (item.getProperty("X-MOZ-SEND-INVITATIONS") == "TRUE");
988 } else {
989 sendInvitesCheckbox.checked = false;
990 }
991
992 /* Status */
993 setElementValue("item-url", item.getProperty("URL"));
994 setElementValue("item-description", item.getProperty("DESCRIPTION"));
995 if (isEvent(item)) {
996 setElementValue("event-status", item.getProperty("STATUS"));
997 } else {
998 setElementValue("todo-status", item.getProperty("STATUS"));
999 }
1000
1001 /* Task completed date */
1002 if (item.completedDate) {
1003 updateToDoStatus(item.status, item.completedDate.jsDate);
1004 } else {
1005 updateToDoStatus(item.status);
1006 }
1007
1008 /* Task percent complete */
1009 if (isToDo(item)) {
1010 var percentCompleteInteger = 0;
1011 var percentCompleteProperty = item.getProperty("PERCENT-COMPLETE");
1012 if (percentCompleteProperty != null) {
1013 percentCompleteInteger = parseInt(percentCompleteProperty);
1014 }
1015 if (percentCompleteInteger < 0) {
1016 percentCompleteInteger = 0;
1017 } else if (percentCompleteInteger > 100) {
1018 percentCompleteInteger = 100;
1019 }
1020 setElementValue("percent-complete-textbox", percentCompleteInteger);
1021 }
1022
1023 /* Priority */
1024 var priorityInteger = parseInt(item.priority);
1025 if (priorityInteger >= 1 && priorityInteger <= 4) {
1026 document.getElementById("priority-levels").selectedIndex = 3; // high priority
1027 } else if (priorityInteger == 5) {
1028 document.getElementById("priority-levels").selectedIndex = 2; // medium priority
1029 } else if (priorityInteger >= 6 && priorityInteger <= 9) {
1030 document.getElementById("priority-levels").selectedIndex = 1; // low priority
1031 } else {
1032 document.getElementById("priority-levels").selectedIndex = 0; // not defined
1033 }
1034
1035 /* Privacy */
1036 switch (item.privacy) {
1037 case "PUBLIC":
1038 case "PRIVATE":
1039 case "CONFIDENTIAL":
1040 setElementValue("privacy-menulist", item.privacy);
1041 break;
1042 case "":
1043 setElementValue("private-menulist", "PUBLIC");
1044 break;
1045 default: // bogus value
1046 dump("ERROR! Event has invalid privacy string: " + item.privacy + "\n");
1047 break;
1048 }
1049
1050 // update alarm checkbox/label/settings button
1051 updateAlarm();
1052
1053 updateTaskAlarmWarnings();
1054
1055 updateURL(item.getProperty("URL"));
1056 return;
1057 }
1058
updateToDoStatus
1059 function updateToDoStatus(status, passedInCompletedDate)
1060 {
1061 // RFC2445 doesn't support completedDates without the todo's status
1062 // being "COMPLETED", however twiddling the status menulist shouldn't
1063 // destroy that information at this point (in case you change status
1064 // back to COMPLETED). When we go to store this VTODO as .ics the
1065 // date will get lost.
1066
1067 var completedDate;
1068 if (passedInCompletedDate) {
1069 completedDate = passedInCompletedDate;
1070 } else {
1071 completedDate = null;
1072 }
1073
1074 // remember the original values
1075 var oldPercentComplete = getElementValue("percent-complete-textbox");
1076 var oldCompletedDate = getElementValue("completed-date-picker");
1077
1078 switch (status) {
1079 case null:
1080 case "":
1081 case "NONE":
1082 document.getElementById("todo-status").selectedIndex = 0;
1083 disableElement("percent-complete-textbox");
1084 disableElement("percent-complete-label");
1085 break;
1086 case "CANCELLED":
1087 document.getElementById("todo-status").selectedIndex = 4;
1088 disableElement("percent-complete-textbox");
1089 disableElement("percent-complete-label");
1090 break;
1091 case "COMPLETED":
1092 document.getElementById("todo-status").selectedIndex = 3;
1093 enableElement("percent-complete-textbox");
1094 enableElement("percent-complete-label");
1095 // if there isn't a completedDate, set it to now
1096 if (!completedDate)
1097 completedDate = new Date();
1098 break;
1099 case "IN-PROCESS":
1100 document.getElementById("todo-status").selectedIndex = 2;
1101 disableElement("completed-date-picker");
1102 enableElement("percent-complete-textbox");
1103 enableElement("percent-complete-label");
1104 break;
1105 case "NEEDS-ACTION":
1106 document.getElementById("todo-status").selectedIndex = 1;
1107 enableElement("percent-complete-textbox");
1108 enableElement("percent-complete-label");
1109 break;
1110 }
1111
1112 if (status == "COMPLETED") {
1113 setElementValue("percent-complete-textbox", "100");
1114 setElementValue("completed-date-picker", completedDate);
1115 enableElement("completed-date-picker");
1116 } else {
1117 if (oldPercentComplete != 100) {
1118 setElementValue("percent-complete-textbox", oldPercentComplete);
1119 } else {
1120 setElementValue("percent-complete-textbox", "");
1121 }
1122 setElementValue("completed-date-picker", oldCompletedDate);
1123 disableElement("completed-date-picker");
1124 }
1125 }
1126
updateURL
1127 function updateURL(aValue)
1128 {
1129 var button = document.getElementById("load-url-button");
1130 button.setAttribute("disabled", true)
1131
1132 if (!aValue) {
1133 return;
1134 }
1135
1136 // The user might have just put in 'www.foo.com', correct that here
1137 if (aValue.indexOf( ":" ) == -1) {
1138 aValue = "http://" + aValue;
1139 }
1140 try {
1141 makeURL(aValue);
1142 // If we made it this far, that means it's a valid url
1143 button.removeAttribute("disabled");
1144 } catch(ex) {}
1145
1146 return;
1147 }
1148
loadURL
1149 function loadURL()
1150 {
1151 var url = getElementValue("item-url");
1152
1153 // The user might have just put in 'www.foo.com', correct that here
1154 if (url.indexOf( ":" ) == -1) {
1155 url = "http://" + url;
1156 }
1157
1158 launchBrowser(url);
1159 return;
1160 }
1161
1162 var gOldCatIndex = 0;
categorySelect
1163 function categorySelect(aValue) {
1164 if (aValue != "##NEW") {
1165 gOldCatIndex = document.getElementById("item-categories").selectedIndex;
1166 return;
1167 }
1168
1169 // Make sure we don't leave 'New..' selected if they hit cancel
1170 document.getElementById("item-categories").selectedIndex = gOldCatIndex;
1171
1172 window.openDialog("chrome://calendar/content/preferences/editCategory.xul",
1173 "addCategory", "modal,centerscreen,chrome,resizable=no",
1174 "", null, calGetString("calendar", "addCategory"));
1175 }
1176
1177 // Trick the dialog into thinking we're the categories pane
1178 var gCategoriesPane = {
eventDialog_saveCategory
1179 saveCategory: function eventDialog_saveCategory(aName, aColor) {
1180 //Check to make sure another category doesn't have the same name
1181 var promptService =
1182 Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
1183 .getService(Components.interfaces.nsIPromptService);
1184 var categoriesList = getPrefCategoriesArray();
1185 for each (cat in categoriesList) {
1186 if (aName.toLowerCase() == cat.toLowerCase()) {
1187 var repTitle = calGetString("calendar", "categoryReplaceTitle");
1188 var rep = calGetString("calendar", "categoryReplace");
1189 if (promptService.confirm(null, repTitle, rep)) {
1190 var categoryNameFix = formatStringForCSSRule(aName);
1191 setPref("calendar.category.color." + categoryNameFix,
1192 "CHAR",
1193 aColor);
1194 }
1195 return;
1196 }
1197 }
1198
1199 if (aName.length == 0) {
1200 promptService.alert(null, null, noBlankCategories);
1201 return;
1202 }
1203
1204 categoriesList.push(aName);
1205 sortArrayByLocaleCollator(categoriesList);
1206
1207 setPrefCategoriesFromArray(categoriesList);
1208
1209 if (aColor) {
1210 var categoryNameFix = formatStringForCSSRule(aName);
1211 setPref("calendar.category.color." + categoryNameFix, "CHAR", aColor);
1212 }
1213 var catList = document.getElementById("item-categories");
1214 var index = categoriesList.indexOf(aName);
1215 catList.insertItemAt(index, aName, aName);
1216 catList.selectedIndex = index;
1217 }
1218 };
1219
1220 // Make sure that an AUS update triggered restart doesn't automatically nuke
1221 // what the user is working on
calItemDialogAttemptClose
1222 window.tryToClose = function calItemDialogAttemptClose() {
1223 var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
1224 .getService(Components.interfaces.nsIPromptService);
1225 return promptService.confirm(window,
1226 calGetString("calendar", "confirmCloseTitle"),
1227 calGetString("calendar", "confirmCloseText"));
1228 }