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 *
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
35 *
36 * ***** END LICENSE BLOCK ***** */
37
38 /* dialog stuff */
39 function onLoad()
40 {
41 var args = window.arguments[0];
42
43 window.onAcceptCallback = args.onOk;
44 window.calendarEvent = args.calendarEvent;
45 window.originalRecurrenceInfo = args.recurrenceInfo;
46 window.startDate = args.startDate;
47
48 loadDialog();
49
50 updateDeck();
51
52 updateDuration();
53
54 updateAccept();
55
56 opener.setCursor("auto");
57
58 self.focus();
59 }
60
61 function onAccept()
62 {
63 var event = window.calendarEvent;
64
65 var recurrenceInfo = saveDialog();
66
67 window.onAcceptCallback(recurrenceInfo);
68
69 return true;
70 }
71
72 function onCancel()
73 {
74
75 }
76
77 function loadDialog()
78 {
79 // Start with setting some labels, that depend on the (start)date of the item
80 // Those labels are for the monthly recurrence deck.
81 var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
82 .getService(Components.interfaces.nsIStringBundleService);
83 var props = sbs.createBundle("chrome://calendar/locale/dateFormat.properties");
84
85 // Set label to '15th day of the month'
86 var nthstr = props.GetStringFromName("ordinal.suffix."+window.startDate.day);
87 var str = props.formatStringFromName("recurNthDay", [window.startDate.day, nthstr], 2);
88 document.getElementById("monthly-nth-day").label = str;
89
90 // Set label to 'second week of the month'
91 // Note that the date here needs to be 0 based to work properly
92 var monthWeekNum = Math.floor((window.startDate.day - 1) / 7) + 1;
93
94 // In order to remain somewhat sane for l10n, turns a number into a word
95 var numWordMap = ["", "first", "second", "third", "fourth", "fifth"];
96 var numDayMap = ["sunday", "monday", "tuesday", "wednesday",
97 "thursday", "friday", "saturday"];
98
99 str = calGetString("dateFormat",
100 "recur." + numWordMap[monthWeekNum] + "." +
101 numDayMap[window.startDate.weekday]);
102 document.getElementById("monthly-nth-week").label = str;
103
104 // Set two values needed to create the real rrule later
105 document.getElementById("monthly-nth-week").day = window.startDate.weekday;
106 document.getElementById("monthly-nth-week").week = monthWeekNum;
107
108 // If this is the last friday of the month, set label to 'last friday of the month'
109 // (Or any other day, ofcourse.) Otherwise, hide last option
110 var monthLength = window.startDate.endOfMonth.day;
111 var isLastWeek = (monthLength - window.startDate.day) < 7;
112 document.getElementById("monthly-last-week").hidden = !isLastWeek;
113 var isLastDay = (monthLength == window.startDate.day);
114 document.getElementById("monthly-last-day").hidden = !isLastDay;
115 if (isLastWeek) {
116 str = calGetString("dateFormat", "recur.last." + numDayMap[window.startDate.weekday]);
117 document.getElementById("monthly-last-week").label = str;
118 }
119
120 document.getElementById("monthly-last-week").day = window.startDate.weekday;
121
122 if (!window.originalRecurrenceInfo)
123 return;
124
125 /* split out rules and exceptions */
126 var rrules = splitRecurrenceRules(window.originalRecurrenceInfo);
127 var rules = rrules[0];
128 var exceptions = rrules[1];
129
130 /* deal with the rules */
131 if (rules.length > 0) {
132 // we only handle 1 rule currently
133 var rule = rules[0];
134 if (rule instanceof Components.interfaces.calIRecurrenceRule) {
135
136 switch(rule.type) {
137 case "DAILY":
138 document.getElementById("period-list").selectedIndex = 0;
139
140 setElementValue("daily-days", rule.interval);
141 break;
142 case "WEEKLY":
143 document.getElementById("period-list").selectedIndex = 1;
144 setElementValue("weekly-weeks", rule.interval);
145
146 const byDayTable = { 1 : "sun", 2 : "mon", 3 : "tue", 4 : "wed",
147 5 : "thu", 6 : "fri", 7: "sat" };
148
149 for each (var i in rule.getComponent("BYDAY", {})) {
150 setElementValue("weekly-" + byDayTable[i], "true", "checked");
151 }
152 break;
153 case "MONTHLY":
154 document.getElementById("period-list").selectedIndex = 2;
155 // XXX This code ignores a lot of monthly recurrence rules that
156 // can come in from external sources. There just is no UI to
157 // show them
158 setElementValue("monthly-months", rule.interval);
159 var days = rule.getComponent("BYMONTHDAY", {});
160 if (days.length > 0 && days[0]) {
161 if (days[0] == -1) {
162 calRadioGroupSelectItem("monthly-type", "monthly-last-day");
163 } else {
164 calRadioGroupSelectItem("monthly-type", "monthly-nth-day");
165 }
166 }
167 days = rule.getComponent("BYDAY", {}) ;
168 if (days.length > 0 && days[0] > 0) {
169 calRadioGroupSelectItem("monthly-type", "monthly-nth-week");
170 }
171 if (days.length > 0 && days[0] < 0) {
172 calRadioGroupSelectItem("monthly-type", "monthly-last-week");
173 }
174 break;
175 case "YEARLY":
176 document.getElementById("period-list").selectedIndex = 3;
177 break;
178 default:
179 dump("unable to handle your rule type!\n");
180 break;
181 }
182
183 /* load up the duration of the event radiogroup */
184 if (rule.isByCount) {
185 if (rule.count == -1) {
186 setElementValue("recurrence-duration", "forever");
187 } else {
188 setElementValue("recurrence-duration", "ntimes");
189 setElementValue("repeat-ntimes-count", rule.count );
190 }
191 } else {
192 var endDate = rule.endDate;
193 if (!endDate) {
194 setElementValue("recurrence-duration", "forever");
195 } else {
196
197 // rule.endDate is a floating datetime, however per RFC2445
198 // it must be in UTC. Since we want the datepicker to show
199 // the date based on our _local_ timezone, we must first
200 // "pin" the floating datetime to UTC, and then convert
201 // from UTC to our local timezone. We _can't_ simply
202 // convert directly from floating to our local timezone.
203 endDate = endDate.getInTimezone("UTC");
204 endDate = endDate.getInTimezone(calendarDefaultTimezone());
205 setElementValue("recurrence-duration", "until");
206 setElementValue("repeat-until-date", endDate.jsDate);
207 }
208 }
209 }
210 }
211 }
212
213 function saveDialog()
214 {
215 // This works, but if we ever support more complex recurrence,
216 // e.g. recurrence for Martians, then we're going to want to
217 // not clone and just recreate the recurrenceInfo each time.
218 // The reason is that the order of items (rules/dates/datesets)
219 // matters, so we can't always just append at the end. This
220 // code here always inserts a rule first, because all our
221 // exceptions should come afterward.
222 var deckNumber = Number(getElementValue("period-list"));
223
224 var recurrenceInfo = null;
225 if (window.originalRecurrenceInfo) {
226 recurrenceInfo = window.originalRecurrenceInfo.clone();
227 var rrules = splitRecurrenceRules(recurrenceInfo);
228 if (rrules[0].length > 0)
229 recurrenceInfo.deleteRecurrenceItem(rrules[0][0]);
230 } else {
231 recurrenceInfo = createRecurrenceInfo(window.calendarEvent);
232 }
233
234 var recRule = createRecurrenceRule();
235 switch (deckNumber) {
236 case 0:
237 recRule.type = "DAILY";
238 var ndays = Number(getElementValue("daily-days"));
239 recRule.interval = ndays;
240 break;
241 case 1:
242 recRule.type = "WEEKLY";
243 recRule.interval = getElementValue("weekly-weeks");
244 var onDays = [];
245 ["sun", "mon", "tue", "wed", "thu", "fri", "sat"].
246 forEach(function(d)
247 {
248 var elem = document.getElementById("weekly-" + d);
249 if (elem.checked) {
250 onDays.push(elem.getAttribute("value"));
251 }
252 });
253 if (onDays.length > 0)
254 recRule.setComponent("BYDAY", onDays.length, onDays);
255 break;
256 case 2:
257 recRule.type = "MONTHLY";
258 recRule.interval = getElementValue("monthly-months");
259 var recurtype = getElementValue("monthly-type");
260 switch (recurtype) {
261 case "nth-day":
262 recRule.setComponent("BYMONTHDAY", 1, [window.startDate.day]);
263 break;
264 case "nth-week":
265 var el = document.getElementById('monthly-nth-week');
266 // For more info on where this magic formula comes from, see icalrecur.c,
267 // icalrecurrencetype_day_day_of_week()
268 recRule.setComponent("BYDAY", 1, [el.week*8 + el.day+1]);
269 break;
270 case "last-week":
271 el = document.getElementById('monthly-last-week');
272 recRule.setComponent("BYDAY", 1, [(-1)*(8+Number(el.day)+1)]);
273 break;
274 case "last-day":
275 recRule.setComponent("BYMONTHDAY", 1, [-1]);
276 break;
277 }
278 break;
279 case 3:
280 recRule.type = "YEARLY";
281 var nyears = Number(getElementValue("yearly-years"));
282 if (nyears == "")
283 nyears = 1;
284 recRule.interval = nyears;
285 break;
286 }
287
288 /* figure out how long this event is supposed to last */
289 switch(document.getElementById("recurrence-duration").selectedItem.value) {
290 case "forever":
291 recRule.count = -1;
292 break;
293 case "ntimes":
294 recRule.count = Math.max(1, getElementValue("repeat-ntimes-count"));
295 break;
296 case "until":
297 // get the datetime from the control (which is in localtime),
298 // set the time to 23:59:99 and convert that to UTC time.
299 var endDate = getElementValue("repeat-until-date")
300 endDate.setHours(23);
301 endDate.setMinutes(59);
302 endDate.setSeconds(59);
303 endDate.setMilliseconds(999);
304 endDate = jsDateToDateTime(endDate);
305 recRule.endDate = endDate;
306 break;
307 }
308
309 recurrenceInfo.insertRecurrenceItemAt(recRule, 0);
310
311 return recurrenceInfo;
312 }
313
314
315 function updateDeck()
316 {
317 document.getElementById("period-deck").selectedIndex = Number(getElementValue("period-list"));
318
319 updateAccept();
320 }
321
322 function updateDuration()
323 {
324 var durationSelection = document.getElementById("recurrence-duration").selectedItem.value;
325 if (durationSelection == "forever") {
326 }
327
328 if (durationSelection == "ntimes") {
329 setElementValue("repeat-ntimes-count", false, "disabled");
330 } else {
331 setElementValue("repeat-ntimes-count", "true", "disabled");
332 }
333
334 if (durationSelection == "until") {
335 setElementValue("repeat-until-date", false, "disabled");
336 } else {
337 setElementValue("repeat-until-date", "true", "disabled");
338 }
339 }
340
341 function updateAccept()
342 {
343 var acceptButton = document.getElementById("calendar-recurrence-dialog").getButton("accept");
344 acceptButton.removeAttribute("disabled", "true");
345 document.getElementById("repeat-interval-warning").setAttribute("hidden", true);
346 document.getElementById("repeat-numberoftimes-warning").setAttribute("hidden", true);
347
348 var interval;
349 switch (Number(getElementValue("period-list"))) {
350 case 0: // daily
351 interval = Number(getElementValue("daily-days"));
352 break;
353 case 1: // weekly
354 interval = Number(getElementValue("weekly-weeks"));
355 break;
356 case 2: // monthly
357 interval = Number(getElementValue("monthly-months"));
358 break;
359 case 3: // yearly
360 interval = Number(getElementValue("yearly-years"));
361 break;
362 }
363 if (interval == "" || interval < 1) {
364 document.getElementById("repeat-interval-warning").removeAttribute("hidden");
365 acceptButton.setAttribute("disabled", "true");
366 }
367
368 if (document.getElementById("recurrence-duration").selectedItem.value == "ntimes") {
369 var ntimes = getElementValue("repeat-ntimes-count");
370 if (ntimes == "" || ntimes < 1) {
371 document.getElementById("repeat-numberoftimes-warning").removeAttribute("hidden");
372 acceptButton.setAttribute("disabled", "true");
373 }
374 }
375
376 this.sizeToContent();
377 }
378
379 function splitRecurrenceRules(recurrenceInfo)
380 {
381 var ritems = recurrenceInfo.getRecurrenceItems({});
382
383 var rules = [];
384 var exceptions = [];
385
386 for each (var r in ritems) {
387 if (r.isNegative)
388 exceptions.push(r);
389 else
390 rules.push(r);
391 }
392
393 return [rules, exceptions];
394 }