1 <?xml version="1.0"?>
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
3 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
4
5 <!-- ***** BEGIN LICENSE BLOCK *****
6 - Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 -
8 - The contents of this file are subject to the Mozilla Public License Version
9 - 1.1 (the "License"); you may not use this file except in compliance with
10 - the License. You may obtain a copy of the License at
11 - http://www.mozilla.org/MPL/
12 -
13 - Software distributed under the License is distributed on an "AS IS" basis,
14 - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
15 - for the specific language governing rights and limitations under the
16 - License.
17 -
18 - The Original Code is OEone Calendar Code, released October 31st, 2001.
19 -
20 - The Initial Developer of the Original Code is
21 - OEone Corporation.
22 - Portions created by the Initial Developer are Copyright (C) 2001
23 - the Initial Developer. All Rights Reserved.
24 -
25 - Contributor(s):
26 - Garth Smedley <garths@oeone.com>
27 - Mike Potter <mikep@oeone.com>
28 -
29 - Alternatively, the contents of this file may be used under the terms of
30 - either the GNU General Public License Version 2 or later (the "GPL"), or
31 - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 - in which case the provisions of the GPL or the LGPL are applicable instead
33 - of those above. If you wish to allow use of your version of this file only
34 - under the terms of either the GPL or the LGPL, and not to allow others to
35 - use your version of this file under the terms of the MPL, indicate your
36 - decision by deleting the provisions above and replace them with the notice
37 - and other provisions required by the LGPL or the GPL. If you do not delete
38 - the provisions above, a recipient may use your version of this file under
39 - the terms of any one of the MPL, the GPL or the LGPL.
40 -
41 - ***** END LICENSE BLOCK ***** -->
42
43 <!--
44 /* This defines <datepicker/> <timepicker/> and <datetimepicker/>
45 which all descend from datetimepicker-base to get date/time parsing
46 and consistent behavior.
47 It relies on <minimonth/> for the date picker's drop down.
48 You can be notified of change event as follows:
49 <datepicker id="my-date-picker" onchange="myDatePick( this );"/>
50 May get/set value in javascript with
51 document.getElementById("my-date-picker").value = new Date();
52 May disable/enable in javascript with
53 document.getElementById("my-date-picker").disabled = true;
54 May also disable/enable a datetimepicker's component
55 datepicker or timepicker individually with
56 document.getElementById("my-datetimepicker").datepickerdisabled = true;
57 document.getElementById("my-datetimepicker").timepickerdisabled = true;
58
59 */
60 -->
61 <bindings id="xulDatePicker"
62 xmlns="http://www.mozilla.org/xbl"
63 xmlns:xbl="http://www.mozilla.org/xbl"
64 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
65
66 <binding id="datetextpicker"
67 extends="chrome://calendar/content/datetimepickers/datetimepickers.xml#datetimepicker-base">
68 <content>
69 <xul:hbox flex="1">
70 <xul:textbox anonid="date-textbox" flex="1"
71 onfocus="this.select();"
72 onkeypress="if (event.keyCode == Components.interfaces.nsIDOMKeyEvent.DOM_VK_RETURN) fireGoEvent();"/>
73 <xul:button anonid="date-go-button" oncommand="fireGoEvent()"/>
74 </xul:hbox>
75 </content>
76 <implementation>
77 <field name="mRelativeDates">[]</field>
78 <field name="mDayNames">[]</field>
79 <field name="mRelationWords">[]</field>
80 <field name="mMonthLongNames">[]</field>
81 <field name="mMonthShortNames">[]</field>
82
83 <constructor><![CDATA[
84 var goButton = document.getAnonymousElementByAttribute(this, "anonid", "date-go-button");
85 goButton.setAttribute("label", calGetString("calendar", "go"));
86 // Load the stuff we're going to use to parse written dates
87 this.mRelativeDates = [
88 {word:calGetString("calendar", "today").toLowerCase(), offset: 0},
89 {word:calGetString("calendar", "yesterday").toLowerCase(), offset: -1},
90 {word:calGetString("calendar", "tomorrow").toLowerCase(), offset: 1}];
91 var i;
92 for (i = 1; i <= 7; i++) {
93 this.mDayNames.push(calGetString("dateFormat", "day."+i+".name").toLowerCase());
94 }
95
96 for (i = 1; i <= 12; i++) {
97 this.mMonthLongNames.push(calGetString("dateFormat", "month."+i+".name").toLowerCase());
98 this.mMonthShortNames.push(calGetString("dateFormat", "month."+i+".Mmm").toLowerCase());
99 }
100
101 // note that some languages have different conjugations of
102 // next/last depending on the day
103 this.mRelationWords = [
104 {word:calGetString("calendar", "last1"), offset: -1},
105 {word:calGetString("calendar", "last2"), offset: -1},
106 {word:calGetString("calendar", "next1"), offset: 0},
107 {word:calGetString("calendar", "next2"), offset: 0}];
108
109 // Set the value to today
110 var text = document.getAnonymousElementByAttribute(this, "anonid", "date-textbox");
111 text.value = calGetString("calendar", "today");
112 ]]></constructor>
113
114 <property name="value">
115 <getter><![CDATA[
116 return this.mValue;
117 ]]></getter>
118 <setter><![CDATA[
119 var text = document.getAnonymousElementByAttribute(this, "anonid", "date-textbox");
120 try {
121 text.value = this.formatDate(val);
122 } catch(ex) {}
123 return val;
124 ]]></setter>
125 </property>
126
127 <method name="fireGoEvent">
128 <body><![CDATA[
129 var text = document.getAnonymousElementByAttribute(this, "anonid", "date-textbox");
130 var date = this.parseLanguageDate(text.value);
131 if (!date) {
132 date = this.parseDateTime(text.value);
133 }
134 if (date) {
135 // format fails if year <= 1600 on win2k, so try format first.
136 var prettyDate;
137 try {
138 prettyDate = this.formatDate(date);
139 } catch (ex) {} // fall thru
140 }
141 if (date && prettyDate) {
142 this.mValue = date;
143 text.value = prettyDate;
144 this.fireEvent("command", date);
145 return;
146 }
147 ]]></body>
148 </method>
149
150 <!-- This function will take written (with words) dates and, if
151 - possible, return a Date() object described by the words. Note
152 - that this function will not parse explicit dates, like 1/1/06,
153 - you should use parseDateTime for that.
154 -->
155 <method name="parseLanguageDate">
156 <parameter name="aValue"/>
157 <body><![CDATA[
158 if (!aValue) {
159 return null;
160 }
161 var val = aValue.toLowerCase();
162 // Look for the easy ones like today, tomorrow, etc
163 for each (var rel in this.mRelativeDates) {
164 if (val == rel.word) {
165 var now = new Date();
166 now.setDate(now.getDate()+rel.offset);
167 return now;
168 }
169 }
170
171 var parser = this;
172
173 // Takes a written day of the week and returns a js-date
174 // corresponding to the nearest day in the future that is
175 // that day of the week
176 function getDateForDay(aWord) {
177 for (var i in parser.mDayNames) {
178 if (aWord != parser.mDayNames[i]) {
179 continue;
180 }
181 // Figure out what day of the week today is.
182 var today = now();
183
184 // i-weekday gets the offset. Add 7 to ensure that the %
185 // operation stays positive.
186 var offset = (i - today.weekday + 7) % 7;
187 today.day = today.day + offset;
188 return today.jsDate;
189 }
190 return null;
191 }
192
193 // Remove commas
194 val = val.replace(',', '');
195
196 if (val.indexOf(' ') == -1) {
197 // Just a single word, or a single date.
198 return getDateForDay(val);
199 }
200
201 // Replace month names with numbers
202 for (var i in this.mMonthLongNames) {
203 if (val.indexOf(this.mMonthLongNames[i]) != -1) {
204 var newVal = val.replace(this.mMonthLongNames[i], Number(i)+1);
205 newVal = newVal.replace(' ', '/');
206 return this.parseDateTime(newVal);
207 }
208 }
209
210 // Same for short month names
211 for (var i in this.mMonthShortNames) {
212 if (val.indexOf(this.mMonthShortNames[i]) != -1) {
213 var newVal = val.replace(this.mMonthShortNames[i], Number(i)+1);
214 newVal = newVal.replace(' ', '/');
215 return this.parseDateTime(newVal);
216 }
217 }
218
219 // Now for the cool 'next' and 'last'
220 var words = val.split(' ');
221 var offset, day;
222 for each (word in words) {
223 for each (rel in this.mRelationWords) {
224 if (word == rel.word) {
225 offset = rel.offset;
226 break;
227 }
228 }
229 for (var i in this.mDayNames) {
230 if (word == this.mDayNames[i]) {
231 day = getDateForDay(word);
232 break;
233 }
234 }
235 }
236
237 if (day && offset != undefined) {
238 day.setDate(day.getDate()+(7*offset));
239 return day;
240 }
241 return null;
242 ]]></body>
243 </method>
244 </implementation>
245 </binding>
246 <binding id="datepicker" extends="chrome://calendar/content/datetimepickers/datetimepickers.xml#datetimepicker-base">
247 <!-- ::::::::::::::::: CONTENT ::::::::::::::::::::::::: -->
248 <!-- Desired behavior: when user is done editing the date field
249 and either leaves the field (onblur) or closes the dialog
250 (13 is enter/return key), parse the date and redisplay it
251 in the date field using the current format to verify
252 whether the date was parsed correctly.
253 This cannot be done with textbox oninput, so use a workaround.
254 This was done with textbox onkeypress="parseTextBoxDate(true)"
255 and onblur="parseTextBoxDate(true)" which worked in Moz1.6, but
256 no longer works in Moz1.7.
257 Therefore constructor stores attribute kDatePicker on the textbox,
258 and the onblur and onkeypress commands navigate to this kDatePicker.
259 xul:Textbox contains an xul:hbox which contains html:input.
260 Onkeypress and onblur are not documented attributes of xul:textbox,
261 but become attributes of the html:input.
262 Not clear how to navigate from the textbox to the input, otherwise
263 could put kDatePicker property on the input element.
264 [document.getAnonymousNodes(textBox) fails as of Moz1.7b]).
265 So navigate parents to textbox in order to call parseTextBoxDate.
266 [Note: minimonth onmonthchange reshows parent popup to fix title
267 month/year (bug 973914). minimonth onpopuplisthidden reshows parent
268 popup to avoid freeze (bug 278877).]
269 [this comment is outside the <content> so it won't become a
270 node that interferes with navigation to interior nodes.] -->
271 <content>
272 <xul:hbox flex="1" id="hbox" class="datepicker-box-class">
273 <xul:menulist editable="true" sizetopopup="false"
274 class="datepicker-text-class"
275 onkeypress="if (event.keyCode == 13) this.kDatePicker.parseTextBoxDate(true);"
276 xbl:inherits="disabled">
277 <xul:menupopup popupanchor="bottomright" popupalign="topright"
278 anonid="datepopup"
279 onpopupshowing="this.parentNode.kDatePicker.onPopup();"
280 onpopuphiding="this.firstChild.hidePopupList();">
281 <xul:minimonth/>
282 </xul:menupopup>
283 </xul:menulist>
284 </xul:hbox>
285 </content>
286
287 <!-- ::::::::::::::::: INTERFACE ::::::::::::::::::::::::: -->
288 <implementation>
289 <constructor>
290 <![CDATA[
291 var hbox = document.getAnonymousNodes(this)[0];
292 this.kTextBox = hbox.firstChild;
293 this.kTextBox.kDatePicker = this; // enable call back to method in Moz1.7
294 this.kTextBox.menupopup.kDatePicker = this;
295 this.kMinimonth = this.kTextBox.menupopup.firstChild;
296 this.mInPopup = false;
297 this.mValue = null;
298 var val = this.getAttribute("value");
299 if (val) {
300 this.value = new Date(val); // setting value property calls update
301 } else {
302 this.value = new Date();
303 }
304 this.kMinimonth.addEventListener("select", this.clickDate, false);
305
306 this.mUseReshowHack = true;
307 #ifdef XP_MACOSX
308 // doesn't work on Mac: Sunbird works around in widget code, Lightning could not:
309 const kSUNBIRD_UID = "{718e30fb-e89b-41dd-9da7-e25a45638b28}";
310 var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
311 .getService(Components.interfaces.nsIXULAppInfo);
312 this.mUseReshowHack = (appInfo.ID == kSUNBIRD_UID);
313 #endif
314 if (this.mUseReshowHack) {
315 this.kMinimonth.addEventListener("monthchange", this.reshowPopup, false);
316 this.kMinimonth.addEventListener("popuplisthidden", this.reshowPopup, false);
317 }
318 this.mIsReshowing = false;
319 ]]>
320 </constructor>
321
322 <destructor>
323 <![CDATA[
324 this.kMinimonth.removeEventListener("select", this.clickDate, false);
325 if (this.mUseReshowHack) {
326 this.kMinimonth.removeEventListener("monthchange", this.reshowPopup, false);
327 this.kMinimonth.removeEventListener("popuplisthidden", this.reshowPopup, false);
328 }
329 ]]>
330 </destructor>
331
332 <method name="update">
333 <parameter name="aValue"/>
334 <parameter name="aRefresh"/>
335 <body>
336 <![CDATA[
337 if (aValue != null) {
338 // format fails if year <= 1600 on win2k, so try format first.
339 var formattedValue = null;
340 try {
341 formattedValue = this.formatDate(aValue);
342 } catch (ex) {} // fall thru
343
344 if (formattedValue) {
345 // format succeeded, safe to set value
346 var dateChanged = true;
347 if (this.mValue != null) {
348 dateChanged = (this.mValue.getDate() != aValue.getDate()) ||
349 (this.mValue.getMonth() != aValue.getMonth()) ||
350 (this.mValue.getFullYear() != aValue.getFullYear());
351 }
352 this.mValue = aValue;
353 this.kTextBox.value = formattedValue;
354 if (aRefresh && dateChanged) {
355 this.fireEvent("change");
356 return;
357 }
358 }
359 }
360 // invalid date, revert to previous date
361 // set textBox.value property, not attribute
362 if (this.mValue) {
363 this.kTextBox.value = this.formatDate(this.mValue);
364 }
365 ]]>
366 </body>
367 </method>
368
369 <method name="onPopup">
370 <body>
371 <![CDATA[
372 // avoid reinitializing during reshow, for bugs 273914 & 278877 workaround
373 if (! this.mIsReshowing) {
374 this.mInPopup = true;
375 this.kMinimonth.update( this.mValue );
376 this.mInPopup = false;
377 // select all to remove cursor since can't type while popped-up
378 this.select();
379 }
380 ]]>
381 </body>
382 </method>
383
384 <!-- Reshow hides and shows parent popup without reinitializing in onPopup
385 to workaround bugs 273914 (update title) & 278877 (avoid freeze) -->
386 <method name="reshowPopup">
387 <parameter name="aEvent"/>
388 <body>
389 <![CDATA[
390 var datepicker = aEvent.target.parentNode.parentNode.kDatePicker;
391 datepicker.mIsReshowing = true;
392 try {
393 aEvent.target.parentNode.hidePopup();
394 aEvent.target.parentNode.showPopup();
395 } finally {
396 datepicker.mIsReshowing = false;
397 }
398 ]]>
399 </body>
400 </method>
401
402 <method name="parseTextBoxDate">
403 <parameter name="aRefresh"/>
404 <body>
405 <![CDATA[
406 this.update(this.parseDateTime(this.kTextBox.value), aRefresh);
407 this.lastDateParseIncludedTime = false;
408 ]]>
409 </body>
410 </method>
411
412 <method name="select">
413 <body>
414 <![CDATA[
415 // select all in text box
416 this.kTextBox.select();
417 ]]>
418 </body>
419 </method>
420
421 <method name="clickDate">
422 <parameter name="aEvent" />
423 <body>
424 <![CDATA[
425 var datepicker = aEvent.target.parentNode.parentNode.kDatePicker;
426 if (! datepicker.mInPopup)
427 {
428 // aEvent.target is the minimonth
429 datepicker.update(new Date(aEvent.target.value), true);
430 // select changed value so no cursor appears (can't type to it).
431 datepicker.select();
432 aEvent.target.parentNode.hidePopup();
433 }
434 ]]>
435 </body>
436 </method>
437
438 </implementation>
439
440 <!-- ::::::::::::::::: HANDLERS ::::::::::::::::::::::::: -->
441 <handlers>
442 <handler event="bindingattached" action="this.initialize();"/>
443
444 <handler event="blur" phase="capturing">
445 <![CDATA[
446 this.parseTextBoxDate(true);
447 ]]>
448 </handler>
449 </handlers>
450
451 </binding>
452
453 <binding id="timepicker" extends="chrome://calendar/content/datetimepickers/datetimepickers.xml#datetimepicker-base">
454 <!-- ::::::::::::::::: CONTENT ::::::::::::::::::::::::: -->
455 <!-- Desired behavior: when user is done editing the time field
456 and either leaves the field (onblur) or closes the dialog
457 (13 is enter/return key), parse the time and redisplay it
458 in the time field using the current format to verify
459 whether the time was parsed correctly.
460 This cannot be done with textbox oninput, so use a workaround.
461 This was done with textbox onkeypress="parseTextBoxTime(true)"
462 and onblur="parseTextBoxTime(true)" which worked in Moz1.6, but
463 no longer works in Moz1.7.
464 Therefore constructor stores attribute kTimePicker on the textbox,
465 and the onblur and onkeypress commands navigate to this kTimePicker.
466 xul:Textbox contains an xul:hbox which contains html:input.
467 Onkeypress and onblur are not documented attributes of xul:textbox,
468 but become attributes of the html:input.
469 Not clear how to navigate from the textbox to the input, otherwise
470 could put kTimePicker property on the input element.
471 [document.getAnonymousNodes(textBox) fails as of Moz1.7b]).
472 So navigate parents to textbox in order to call parseTextBoxTime.
473 [this comment is outside the <content> so it won't become a
474 node that interferes with navigation to interior nodes.] -->
475 <content>
476 <xul:hbox flex="1" id="hbox" class="timepicker-box-class">
477 <xul:menulist editable="true" sizetopopup="false"
478 id="timepicker-text"
479 class="timepicker-text-class"
480 onkeypress="if (event.keyCode == 13) this.kTimePicker.parseTextBoxTime(true);"
481 xbl:inherits="disabled">
482 <xul:menupopup popupalign="topright" popupanchor="bottomright"
483 onpopupshowing="onPopup(this)"
484 onpopuphiding="onHide(this)">
485 <xul:timepicker-grids />
486 </xul:menupopup>
487 </xul:menulist>
488 </xul:hbox>
489 </content>
490
491 <!-- ::::::::::::::::: INTERFACE ::::::::::::::::::::::::: -->
492 <implementation>
493 <constructor>
494 <![CDATA[
495 var hbox = document.getAnonymousNodes(this)[0];
496 this.kTextBox = hbox.childNodes[0];
497 this.kTextBox.kTimePicker = this; // enable call back to method in Moz1.7
498
499 var val = this.getAttribute("value");
500 if (val)
501 this.value = (new Date(val));
502 else
503 this.value = (new Date());
504 ]]>
505 </constructor>
506
507 <method name="update">
508 <parameter name="aValue"/>
509 <parameter name="aRefresh"/>
510 <body>
511 <![CDATA[
512 if (aValue != null) {
513 this.mValue = aValue;
514 }
515 // set textBox.value property, not attribute
516 this.kTextBox.value = this.formatTime(this.mValue);
517
518 if (aValue != null && aRefresh) {
519 var event = document.createEvent('Events');
520 event.initEvent("change", true, true);
521 this.dispatchEvent(event);;
522 }
523 ]]>
524 </body>
525 </method>
526
527 <method name="parseTextBoxTime">
528 <parameter name="aRefresh"/>
529 <body>
530 <![CDATA[
531 var time = this.parseTime(this.kTextBox.value);
532 this.update(time, aRefresh);
533 return time;
534 ]]>
535 </body>
536 </method>
537
538 <method name="onPopup">
539 <parameter name="aPopup" />
540 <body>
541 <![CDATA[
542 // select all to remove cursor since can't type while popped-up
543 this.select();
544 var timePickerGrid = aPopup.childNodes[0];
545 timePickerGrid.onPopupShowing(this, aPopup);
546 ]]>
547 </body>
548 </method>
549
550 <method name="onHide">
551 <parameter name="aPopup"/>
552 <body>
553 <![CDATA[
554 // This is the timepicker grid. Why aren't we using anonid?
555 aPopup.childNodes[0].onPopupHiding();
556 ]]>
557 </body>
558 </method>
559
560 <method name="select">
561 <body>
562 <![CDATA[
563 // select all in text box
564 this.kTextBox.select();
565 ]]>
566 </body>
567 </method>
568
569 </implementation>
570
571 <!-- ::::::::::::::::: HANDLERS ::::::::::::::::::::::::: -->
572
573 <handlers>
574 <handler event="bindingattached" action="this.initialize();"/>
575 <handler event="blur" phase="capturing">
576 <![CDATA[
577 this.parseTextBoxTime(true);
578 ]]>
579 </handler>
580 </handlers>
581
582 </binding>
583
584 <binding id="timepicker-hour">
585 <content>
586 <xul:spacer flex="1"/>
587 <xul:vbox anonid="hourbox"
588 onclick="clickHour(this.parentNode, this.parentNode.getAttribute('value'))"
589 ondblclick="doubleClickHour(this.parentNode, this.parentNode.getAttribute('value'))">
590 <xul:box>
591 <xul:label class="time-picker-hour-label" anonid="hourlabel" xbl:inherits="value=label,selected"/>
592 </xul:box>
593 <xul:spacer flex="1"/>
594 </xul:vbox>
595 <xul:spacer flex="1"/>
596 </content>
597 <handlers>
598 <handler event="DOMMouseScroll">
599 <![CDATA[
600 var rows = event.detail;
601 if (rows == NSUIEvent.SCROLL_PAGE_UP) {
602 rows = -1;
603 } else if (rows == NSUIEvent.SCROLL_PAGE_DOWN) {
604 rows = 1;
605 } else {
606 // In this case event.detail contains the default number of lines
607 // to scroll. We always want to only scroll 1 hour though
608 rows = (rows > 0) ? 1 : -1;
609 }
610 moveHours(rows);
611 ]]>
612 </handler>
613 </handlers>
614 </binding>
615
616 <binding id="timepicker-minute">
617 <content>
618 <xul:spacer flex="1"/>
619 <xul:vbox anonid="minutebox"
620 onclick="clickMinute(this.parentNode, this.parentNode.getAttribute('value'))">
621 <xul:box>
622 <xul:label class="time-picker-minute-label" anonid="minutelabel" xbl:inherits="value=label,selected"/>
623 </xul:box>
624 </xul:vbox>
625 <xul:spacer flex="1"/>
626 </content>
627 <handlers>
628 <handler event="DOMMouseScroll">
629 <![CDATA[
630 var rows = event.detail;
631 if (rows == NSUIEvent.SCROLL_PAGE_UP) {
632 rows = -1;
633 } else if (rows == NSUIEvent.SCROLL_PAGE_DOWN) {
634 rows = 1;
635 } else {
636 // In this case event.detail contains the default number of lines
637 // to scroll. We always want to only scroll 1 minute though
638 rows = (rows > 0) ? 1 : -1;
639 }
640 moveMinutes(rows);
641 ]]>
642 </handler>
643 </handlers>
644 </binding>
645
646 <binding id="timepicker-grids" extends="xul:box">
647 <resources>
648 <stylesheet src="chrome://calendar/skin/datetimepickers/datetimepickers.css"/>
649 </resources>
650
651 <!-- ::::::::::::::::: CONTENT ::::::::::::::::::::::::: -->
652 <content>
653 <!-- Box to hold time picker internals -->
654 <vbox anonid="time-picker-grids"
655 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
656 <!-- Hour Grid -->
657 <grid anonid="time-picker-hour-grid">
658 <columns>
659 <column class="time-picker-hour-column-class" flex="1"/>
660 <column class="time-picker-hour-column-class" flex="1"/>
661 <column class="time-picker-hour-column-class" flex="1"/>
662 <column class="time-picker-hour-column-class" flex="1"/>
663 <column class="time-picker-hour-column-class" flex="1"/>
664 <column class="time-picker-hour-column-class" flex="1"/>
665 <column class="time-picker-hour-column-class" flex="1"/>
666 <column class="time-picker-hour-column-class" flex="1"/>
667 <column class="time-picker-hour-column-class" flex="1"/>
668 <column class="time-picker-hour-column-class" flex="1"/>
669 <column class="time-picker-hour-column-class" flex="1"/>
670 <column class="time-picker-hour-column-class" flex="1"/>
671 </columns>
672 <rows>
673 <row flex="1">
674 <timepicker-hour class="time-picker-hour-box-class" value="0"
675 anonid="time-picker-hour-box-0" label="0"/>
676 <timepicker-hour class="time-picker-hour-box-class" value="1"
677 anonid="time-picker-hour-box-1" label="1"/>
678 <timepicker-hour class="time-picker-hour-box-class" value="2"
679 anonid="time-picker-hour-box-2" label="2"/>
680 <timepicker-hour class="time-picker-hour-box-class" value="3"
681 anonid="time-picker-hour-box-3" label="3"/>
682 <timepicker-hour class="time-picker-hour-box-class" value="4"
683 anonid="time-picker-hour-box-4" label="4"/>
684 <timepicker-hour class="time-picker-hour-box-class" value="5"
685 anonid="time-picker-hour-box-5" label="5"/>
686 <timepicker-hour class="time-picker-hour-box-class" value="6"
687 anonid="time-picker-hour-box-6" label="6"/>
688 <timepicker-hour class="time-picker-hour-box-class" value="7"
689 anonid="time-picker-hour-box-7" label="7"/>
690 <timepicker-hour class="time-picker-hour-box-class" value="8"
691 anonid="time-picker-hour-box-8" label="8"/>
692 <timepicker-hour class="time-picker-hour-box-class" value="9"
693 anonid="time-picker-hour-box-9" label="9"/>
694 <timepicker-hour class="time-picker-hour-box-class" value="10"
695 anonid="time-picker-hour-box-10" label="10"/>
696 <timepicker-hour class="time-picker-hour-box-class" value="11"
697 anonid="time-picker-hour-box-11" label="11"/>
698 </row>
699 <row flex="1">
700 <timepicker-hour class="time-picker-hour-box-class" value="12"
701 anonid="time-picker-hour-box-12" label="12"/>
702 <timepicker-hour class="time-picker-hour-box-class" value="13"
703 anonid="time-picker-hour-box-13" label="13"/>
704 <timepicker-hour class="time-picker-hour-box-class" value="14"
705 anonid="time-picker-hour-box-14" label="14"/>
706 <timepicker-hour class="time-picker-hour-box-class" value="15"
707 anonid="time-picker-hour-box-15" label="15"/>
708 <timepicker-hour class="time-picker-hour-box-class" value="16"
709 anonid="time-picker-hour-box-16" label="16"/>
710 <timepicker-hour class="time-picker-hour-box-class" value="17"
711 anonid="time-picker-hour-box-17" label="17"/>
712 <timepicker-hour class="time-picker-hour-box-class" value="18"
713 anonid="time-picker-hour-box-18" label="18"/>
714 <timepicker-hour class="time-picker-hour-box-class" value="19"
715 anonid="time-picker-hour-box-19" label="19"/>
716 <timepicker-hour class="time-picker-hour-box-class" value="20"
717 anonid="time-picker-hour-box-20" label="20"/>
718 <timepicker-hour class="time-picker-hour-box-class" value="21"
719 anonid="time-picker-hour-box-21" label="21"/>
720 <timepicker-hour class="time-picker-hour-box-class" value="22"
721 anonid="time-picker-hour-box-22" label="22"/>
722 <timepicker-hour class="time-picker-hour-box-class" value="23"
723 anonid="time-picker-hour-box-23" label="23"/>
724 </row>
725 </rows>
726 </grid> <!-- Hour Grid -->
727 <!-- Five Minute Grid -->
728 <vbox anonid="time-picker-five-minute-grid-box" flex="1">
729 <grid anonid="time-picker-five-minute-grid" flex="1">
730 <columns>
731 <column class="time-picker-five-minute-column-class" flex="1"/>
732 <column class="time-picker-five-minute-column-class" flex="1"/>
733 <column class="time-picker-five-minute-column-class" flex="1"/>
734 <column class="time-picker-five-minute-column-class" flex="1"/>
735 <column class="time-picker-five-minute-column-class" flex="1"/>
736 <column class="time-picker-five-minute-column-class" flex="1"/>
737 </columns>
738 <rows>
739 <row flex="1">
740 <timepicker-minute class="time-picker-five-minute-class" value="0"
741 anonid="time-picker-five-minute-box-0" label=":00"/>
742 <timepicker-minute class="time-picker-five-minute-class" value="5"
743 anonid="time-picker-five-minute-box-5" label=":05"/>
744 <timepicker-minute class="time-picker-five-minute-class" value="10"
745 anonid="time-picker-five-minute-box-10" label=":10"/>
746 <timepicker-minute class="time-picker-five-minute-class" value="15"
747 anonid="time-picker-five-minute-box-15" label=":15"/>
748 <timepicker-minute class="time-picker-five-minute-class" value="20"
749 anonid="time-picker-five-minute-box-20" label=":20"/>
750 <timepicker-minute class="time-picker-five-minute-class" value="25"
751 anonid="time-picker-five-minute-box-25" label=":25"/>
752 </row>
753 <row flex="1">
754 <timepicker-minute class="time-picker-five-minute-class" value="30"
755 anonid="time-picker-five-minute-box-30" label=":30"/>
756 <timepicker-minute class="time-picker-five-minute-class" value="35"
757 anonid="time-picker-five-minute-box-35" label=":35"/>
758 <timepicker-minute class="time-picker-five-minute-class" value="40"
759 anonid="time-picker-five-minute-box-40" label=":40"/>
760 <timepicker-minute class="time-picker-five-minute-class" value="45"
761 anonid="time-picker-five-minute-box-45" label=":45"/>
762 <timepicker-minute class="time-picker-five-minute-class" value="50"
763 anonid="time-picker-five-minute-box-50" label=":50"/>
764 <timepicker-minute class="time-picker-five-minute-class" value="55"
765 anonid="time-picker-five-minute-box-55" label=":55"/>
766 </row>
767 </rows>
768 </grid> <!-- Five Minute Grid -->
769 <hbox class="time-picker-minutes-bottom">
770 <spacer flex="1"/>
771 <label class="time-picker-more-control-label" value="»" onclick="clickMore()"/>
772 </hbox>
773 </vbox> <!-- Five Minute Grid Box -->
774 <!-- One Minute Grid -->
775 <vbox anonid="time-picker-one-minute-grid-box" flex="1" hidden="true">
776 <grid anonid="time-picker-one-minute-grid" flex="1">
777 <columns>
778 <column class="time-picker-one-minute-column-class" flex="1"/>
779 <column class="time-picker-one-minute-column-class" flex="1"/>
780 <column class="time-picker-one-minute-column-class" flex="1"/>
781 <column class="time-picker-one-minute-column-class" flex="1"/>
782 <column class="time-picker-one-minute-column-class" flex="1"/>
783 </columns>
784 <rows >
785 <row flex="1">
786 <timepicker-minute class="time-picker-one-minute-class" value="0"
787 anonid="time-picker-one-minute-box-0" label=":00"/>
788 <timepicker-minute class="time-picker-one-minute-class" value="1"
789 anonid="time-picker-one-minute-box-1" label=":01"/>
790 <timepicker-minute class="time-picker-one-minute-class" value="2"
791 anonid="time-picker-one-minute-box-2" label=":02"/>
792 <timepicker-minute class="time-picker-one-minute-class" value="3"
793 anonid="time-picker-one-minute-box-3" label=":03"/>
794 <timepicker-minute class="time-picker-one-minute-class" value="4"
795 anonid="time-picker-one-minute-box-4" label=":04"/>
796 </row>
797 <row flex="1">
798 <timepicker-minute class="time-picker-one-minute-class" value="5"
799 anonid="time-picker-one-minute-box-5" label=":05"/>
800 <timepicker-minute class="time-picker-one-minute-class" value="6"
801 anonid="time-picker-one-minute-box-6" label=":06"/>
802 <timepicker-minute class="time-picker-one-minute-class" value="7"
803 anonid="time-picker-one-minute-box-7" label=":07"/>
804 <timepicker-minute class="time-picker-one-minute-class" value="8"
805 anonid="time-picker-one-minute-box-8" label=":08"/>
806 <timepicker-minute class="time-picker-one-minute-class" value="9"
807 anonid="time-picker-one-minute-box-9" label=":09"/>
808 </row>
809 <row flex="1">
810 <timepicker-minute class="time-picker-one-minute-class" value="10"
811 anonid="time-picker-one-minute-box-10" label=":10"/>
812 <timepicker-minute class="time-picker-one-minute-class" value="11"
813 anonid="time-picker-one-minute-box-11" label=":11"/>
814 <timepicker-minute class="time-picker-one-minute-class" value="12"
815 anonid="time-picker-one-minute-box-12" label=":12"/>
816 <timepicker-minute class="time-picker-one-minute-class" value="13"
817 anonid="time-picker-one-minute-box-13" label=":13"/>
818 <timepicker-minute class="time-picker-one-minute-class" value="14"
819 anonid="time-picker-one-minute-box-14" label=":14"/>
820 </row>
821 <row flex="1">
822 <timepicker-minute class="time-picker-one-minute-class" value="15"
823 anonid="time-picker-one-minute-box-15" label=":15"/>
824 <timepicker-minute class="time-picker-one-minute-class" value="16"
825 anonid="time-picker-one-minute-box-16" label=":16"/>
826 <timepicker-minute class="time-picker-one-minute-class" value="17"
827 anonid="time-picker-one-minute-box-17" label=":17"/>
828 <timepicker-minute class="time-picker-one-minute-class" value="18"
829 anonid="time-picker-one-minute-box-18" label=":18"/>
830 <timepicker-minute class="time-picker-one-minute-class" value="19"
831 anonid="time-picker-one-minute-box-19" label=":19"/>
832 </row>
833 <row flex="1">
834 <timepicker-minute class="time-picker-one-minute-class" value="20"
835 anonid="time-picker-one-minute-box-20" label=":20"/>
836 <timepicker-minute class="time-picker-one-minute-class" value="21"
837 anonid="time-picker-one-minute-box-21" label=":21"/>
838 <timepicker-minute class="time-picker-one-minute-class" value="22"
839 anonid="time-picker-one-minute-box-22" label=":22"/>
840 <timepicker-minute class="time-picker-one-minute-class" value="23"
841 anonid="time-picker-one-minute-box-23" label=":23"/>
842 <timepicker-minute class="time-picker-one-minute-class" value="24"
843 anonid="time-picker-one-minute-box-24" label=":24"/>
844 </row>
845 <row flex="1">
846 <timepicker-minute class="time-picker-one-minute-class" value="25"
847 anonid="time-picker-one-minute-box-25" label=":25"/>
848 <timepicker-minute class="time-picker-one-minute-class" value="26"
849 anonid="time-picker-one-minute-box-26" label=":26"/>
850 <timepicker-minute class="time-picker-one-minute-class" value="27"
851 anonid="time-picker-one-minute-box-27" label=":27"/>
852 <timepicker-minute class="time-picker-one-minute-class" value="28"
853 anonid="time-picker-one-minute-box-28" label=":28"/>
854 <timepicker-minute class="time-picker-one-minute-class" value="29"
855 anonid="time-picker-one-minute-box-29" label=":29"/>
856 </row>
857 <row flex="1">
858 <timepicker-minute class="time-picker-one-minute-class" value="30"
859 anonid="time-picker-one-minute-box-30" label=":30"/>
860 <timepicker-minute class="time-picker-one-minute-class" value="31"
861 anonid="time-picker-one-minute-box-31" label=":31"/>
862 <timepicker-minute class="time-picker-one-minute-class" value="32"
863 anonid="time-picker-one-minute-box-32" label=":32"/>
864 <timepicker-minute class="time-picker-one-minute-class" value="33"
865 anonid="time-picker-one-minute-box-33" label=":33"/>
866 <timepicker-minute class="time-picker-one-minute-class" value="34"
867 anonid="time-picker-one-minute-box-34" label=":34"/>
868 </row>
869 <row flex="1">
870 <timepicker-minute class="time-picker-one-minute-class" value="35"
871 anonid="time-picker-one-minute-box-35" label=":35"/>
872 <timepicker-minute class="time-picker-one-minute-class" value="36"
873 anonid="time-picker-one-minute-box-36" label=":36"/>
874 <timepicker-minute class="time-picker-one-minute-class" value="37"
875 anonid="time-picker-one-minute-box-37" label=":37"/>
876 <timepicker-minute class="time-picker-one-minute-class" value="38"
877 anonid="time-picker-one-minute-box-38" label=":38"/>
878 <timepicker-minute class="time-picker-one-minute-class" value="39"
879 anonid="time-picker-one-minute-box-39" label=":39"/>
880 </row>
881 <row flex="1">
882 <timepicker-minute class="time-picker-one-minute-class" value="40"
883 anonid="time-picker-one-minute-box-40" label=":40"/>
884 <timepicker-minute class="time-picker-one-minute-class" value="41"
885 anonid="time-picker-one-minute-box-41" label=":41"/>
886 <timepicker-minute class="time-picker-one-minute-class" value="42"
887 anonid="time-picker-one-minute-box-42" label=":42"/>
888 <timepicker-minute class="time-picker-one-minute-class" value="43"
889 anonid="time-picker-one-minute-box-43" label=":43"/>
890 <timepicker-minute class="time-picker-one-minute-class" value="44"
891 anonid="time-picker-one-minute-box-44" label=":44"/>
892 </row>
893 <row flex="1">
894 <timepicker-minute class="time-picker-one-minute-class" value="45"
895 anonid="time-picker-one-minute-box-45" label=":45"/>
896 <timepicker-minute class="time-picker-one-minute-class" value="46"
897 anonid="time-picker-one-minute-box-46" label=":46"/>
898 <timepicker-minute class="time-picker-one-minute-class" value="47"
899 anonid="time-picker-one-minute-box-47" label=":47"/>
900 <timepicker-minute class="time-picker-one-minute-class" value="48"
901 anonid="time-picker-one-minute-box-48" label=":48"/>
902 <timepicker-minute class="time-picker-one-minute-class" value="49"
903 anonid="time-picker-one-minute-box-49" label=":49"/>
904 </row>
905 <row flex="1">
906 <timepicker-minute class="time-picker-one-minute-class" value="50"
907 anonid="time-picker-one-minute-box-50" label=":50"/>
908 <timepicker-minute class="time-picker-one-minute-class" value="51"
909 anonid="time-picker-one-minute-box-51" label=":51"/>
910 <timepicker-minute class="time-picker-one-minute-class" value="52"
911 anonid="time-picker-one-minute-box-52" label=":52"/>
912 <timepicker-minute class="time-picker-one-minute-class" value="53"
913 anonid="time-picker-one-minute-box-53" label=":53"/>
914 <timepicker-minute class="time-picker-one-minute-class" value="54"
915 anonid="time-picker-one-minute-box-54" label=":54"/>
916 </row>
917 <row flex="1">
918 <timepicker-minute class="time-picker-one-minute-class" value="55"
919 anonid="time-picker-one-minute-box-55" label=":55"/>
920 <timepicker-minute class="time-picker-one-minute-class" value="56"
921 anonid="time-picker-one-minute-box-56" label=":56"/>
922 <timepicker-minute class="time-picker-one-minute-class" value="57"
923 anonid="time-picker-one-minute-box-57" label=":57"/>
924 <timepicker-minute class="time-picker-one-minute-class" value="58"
925 anonid="time-picker-one-minute-box-58" label=":58"/>
926 <timepicker-minute class="time-picker-one-minute-class" value="59"
927 anonid="time-picker-one-minute-box-59" label=":59"/>
928 </row>
929 </rows>
930 </grid> <!-- One Minute Grid -->
931 <hbox class="time-picker-minutes-bottom">
932 <spacer flex="1"/>
933 <label class="time-picker-more-control-label" value="«" onclick="clickLess()"/>
934 </hbox>
935 </vbox> <!-- One Minute Grid Box -->
936 </vbox> <!-- Box to hold time picker internals -->
937 </content>
938
939 <!-- ::::::::::::::::: INTERFACE ::::::::::::::::::::::::: -->
940 <implementation>
941 <constructor>
942 <![CDATA[
943 // set by onPopupShowing
944 this.mPicker = null;
945 this.mPopup = null;
946
947 // The currently selected time
948 this.mSelectedTime = new Date();
949 // The selected hour and selected minute items
950 this.mSelectedHourItem = null;
951 this.mSelectedMinuteItem = null;
952 // constants use to specify one and five minute view
953 this.kMINUTE_VIEW_FIVE = 5;
954 this.kMINUTE_VIEW_ONE = 1;
955 ]]>
956 </constructor>
957
958 <!-- Set up the picker, called when the popup pops -->
959 <method name="onPopupShowing">
960 <parameter name="aPicker"/>
961 <parameter name="aPopup"/>
962 <body>
963 <![CDATA[
964 this.mPicker = aPicker;
965 this.mPopup = aPopup;
966
967 // if there is a Date object in the popup item's
968 // value attribute, use it, otherwise use Now.
969 var inputTime = this.mPicker.value;
970 if (inputTime) {
971 this.mSelectedTime = new Date(inputTime);
972 } else {
973 this.mSelectedTime = new Date();
974 }
975
976 // select the hour item
977 var hours24 = this.mSelectedTime.getHours();
978 var hourItemId = "time-picker-hour-box-" + hours24;
979 var hourItem = document.getAnonymousElementByAttribute(this, "anonid", hourItemId);
980 this.selectHourItem( hourItem );
981
982 // Show the five minute view if we are an even five minutes,
983 // otherwise one minute view
984 var minutesByFive = this.calcNearestFiveMinutes( this.mSelectedTime );
985
986 if (minutesByFive == this.mSelectedTime.getMinutes()) {
987 this.clickLess();
988 } else {
989 this.clickMore();
990 }
991 ]]>
992 </body>
993 </method>
994
995 <method name="onPopupHiding">
996 <body>
997 <![CDATA[
998 if (this.hasChanged)
999 this.timeSelected();
1000 ]]>
1001 </body>
1002 </method>
1003
1004 <!-- Called when the more tab is clicked, and possibly at startup -->
1005 <method name="clickMore">
1006 <body>
1007 <![CDATA[
1008 // switch to one minute view
1009 this.switchMinuteView( this.kMINUTE_VIEW_ONE );
1010
1011 // select minute box corresponding to the time
1012 var minutes = this.mSelectedTime.getMinutes();
1013 var oneMinuteItemId = "time-picker-one-minute-box-" + minutes;
1014 var oneMinuteItem = document.getAnonymousElementByAttribute(this, "anonid", oneMinuteItemId);
1015 this.selectMinuteItem(oneMinuteItem);
1016 ]]>
1017 </body>
1018 </method>
1019
1020 <!-- Called when the less tab is clicked, and possibly at startup -->
1021 <method name="clickLess">
1022 <body>
1023 <![CDATA[
1024 // switch to five minute view
1025 this.switchMinuteView( this.kMINUTE_VIEW_FIVE );
1026
1027 // select closest five minute box,
1028 // BUT leave the selected time at what may NOT be an even five minutes
1029 // So that If they click more again the proper non-even-five minute
1030 // box will be selected
1031 var minutesByFive = this.calcNearestFiveMinutes(this.mSelectedTime);
1032 var fiveMinuteItemId = "time-picker-five-minute-box-"+minutesByFive;
1033 var fiveMinuteItem = document.getAnonymousElementByAttribute(this, "anonid", fiveMinuteItemId);
1034 this.selectMinuteItem(fiveMinuteItem);
1035 ]]>
1036 </body>
1037 </method>
1038
1039 <!-- Called when one of the hour boxes is clicked -->
1040 <method name="clickHour">
1041 <parameter name="hourItem"/>
1042 <parameter name="hourNumber"/>
1043 <body>
1044 <![CDATA[
1045 // select the item
1046 this.selectHourItem( hourItem );
1047
1048 // Change the hour in the selected time.
1049 this.mSelectedTime.setHours( hourNumber );
1050
1051 this.hasChanged = true;
1052 ]]>
1053 </body>
1054 </method>
1055
1056 <!-- Called when one of the hour boxes is double clicked
1057 Sets the time to the selected hour, on the hour, and closes the popup -->
1058 <method name="doubleClickHour">
1059 <parameter name="hourItem"/>
1060 <parameter name="hourNumber"/>
1061 <body>
1062 <![CDATA[
1063 // set the minutes to :00
1064 this.mSelectedTime.setMinutes(0);
1065
1066 // remove the popup grid
1067 this.mPopup.hidePopup();
1068 ]]>
1069 </body>
1070 </method>
1071
1072 <!-- Called when one of the minute boxes is clicked,
1073 Calls the client's onchange and Closes the popup -->
1074 <method name="clickMinute">
1075 <parameter name="minuteItem"/>
1076 <parameter name="minuteNumber"/>
1077 <body>
1078 <![CDATA[
1079 // set the minutes in the selected time
1080 this.mSelectedTime.setMinutes(minuteNumber);
1081 this.selectMinuteItem(minuteItem);
1082 this.hasChanged = true;
1083 // remove the popup grid
1084 this.mPopup.hidePopup();
1085 ]]>
1086 </body>
1087 </method>
1088
1089 <!-- Called by clickMinute when one of the minute boxes is clicked,
1090 Sets the value property and calls the client's onchange. -->
1091 <method name="timeSelected">
1092 <body>
1093 <![CDATA[
1094 // Copy picked time to avoid problems changing Date object in place
1095 var pickedTime = new Date( this.mSelectedTime );
1096
1097 // put the picked time in the value property, and update
1098 this.mPicker.update(pickedTime, true);
1099 // select changed value so no cursor appears (can't type to it).
1100 this.mPicker.select();
1101 ]]>
1102 </body>
1103 </method>
1104
1105 <!-- Helper function to switch between "one" and "five" minute views -->
1106 <method name="switchMinuteView">
1107 <parameter name="view"/>
1108 <body>
1109 <![CDATA[
1110 var fiveMinuteBox = document.getAnonymousElementByAttribute(this, "anonid", "time-picker-five-minute-grid-box");
1111 var oneMinuteBox = document.getAnonymousElementByAttribute(this, "anonid", "time-picker-one-minute-grid-box");
1112
1113 if (view == this.kMINUTE_VIEW_ONE) {
1114 fiveMinuteBox.setAttribute( "hidden", true );
1115 oneMinuteBox.setAttribute( "hidden", false );
1116 } else {
1117 fiveMinuteBox.setAttribute( "hidden", false );
1118 oneMinuteBox.setAttribute( "hidden", true );
1119 }
1120 ]]>
1121 </body>
1122 </method>
1123
1124 <!-- Helper function to select an hour item -->
1125 <method name="selectHourItem">
1126 <parameter name="hourItem"/>
1127 <body>
1128 <![CDATA[
1129 // clear old selection, if there is one
1130 if (this.mSelectedHourItem != null)
1131 this.mSelectedHourItem.removeAttribute( "selected" );
1132 // set selected attribute, to cause the selected style to apply
1133 hourItem.setAttribute( "selected" , "true" );
1134 // remember the selected item so we can deselect it
1135 this.mSelectedHourItem = hourItem;
1136 ]]>
1137 </body>
1138 </method>
1139
1140 <!-- Helper function to select an minute item -->
1141 <method name="selectMinuteItem">
1142 <parameter name="minuteItem"/>
1143 <body>
1144 <![CDATA[
1145 // clear old selection, if there is one
1146 if (this.mSelectedMinuteItem != null)
1147 this.mSelectedMinuteItem.removeAttribute( "selected" );
1148 // set selected attribute, to cause the selected style to apply
1149 minuteItem.setAttribute( "selected" , "true" );
1150 // remember the selected item so we can deselect it
1151 this.mSelectedMinuteItem = minuteItem;
1152 ]]>
1153 </body>
1154 </method>
1155 <method name="moveMinutes">
1156 <parameter name="aNumber"/>
1157 <body>
1158 <![CDATA[
1159 if (!this.mSelectedTime)
1160 return;
1161
1162 var idPrefix = "time-picker-one-minute-box-";
1163
1164 // Everything above assumes that we are showing the one-minute-grid,
1165 // If not, we need to do these corrections;
1166 var fiveMinuteBox = document.getAnonymousElementByAttribute(
1167 this, "anonid", "time-picker-five-minute-grid-box");
1168
1169 if (fiveMinuteBox.getAttribute("hidden") == 'false') {
1170 aNumber *= 5;
1171 idPrefix = "time-picker-five-minute-box-";
1172
1173 // If the detailed view was shown before, then mSelectedTime.getMinutes
1174 // might not be a multiple of 5.
1175 this.mSelectedTime.setMinutes(this.calcNearestFiveMinutes(this.mSelectedTime));
1176 }
1177
1178 var newMinutes = this.mSelectedTime.getMinutes() + aNumber;
1179
1180 // Handle rollover cases
1181 if (newMinutes < 0)
1182 newMinutes += 60;
1183 if (newMinutes > 59)
1184 newMinutes -= 60;
1185
1186 this.mSelectedTime.setMinutes(newMinutes);
1187
1188 var minuteItemId = idPrefix + this.mSelectedTime.getMinutes();
1189 var minuteItem = document.getAnonymousElementByAttribute(this, "anonid", minuteItemId);
1190
1191 this.selectMinuteItem(minuteItem);
1192 this.mPicker.kTextBox.value = this.mPicker.formatTime(this.mSelectedTime);
1193 this.hasChanged = true;
1194 ]]>
1195 </body>
1196 </method>
1197 <method name="moveHours">
1198 <parameter name="aNumber"/>
1199 <body>
1200 <![CDATA[
1201 if (!this.mSelectedTime)
1202 return;
1203
1204 var newHours = this.mSelectedTime.getHours() + aNumber;
1205
1206 // Handle rollover cases
1207 if (newHours < 0)
1208 newHours += 24;
1209 if (newHours > 23)
1210 newHours -= 24;
1211
1212 this.mSelectedTime.setHours(newHours);
1213
1214 var hourItemId = "time-picker-hour-box-" + this.mSelectedTime.getHours();
1215 var hourItem = document.getAnonymousElementByAttribute(this, "anonid", hourItemId);
1216
1217 this.selectHourItem(hourItem);
1218 this.mPicker.kTextBox.value = this.mPicker.formatTime(this.mSelectedTime);
1219 this.hasChanged = true;
1220 ]]>
1221 </body>
1222 </method>
1223
1224 <!-- Helper function to calulate the nearest even five minutes -->
1225 <method name="calcNearestFiveMinutes">
1226 <parameter name="time"/>
1227 <body>
1228 <![CDATA[
1229 var minutes = time.getMinutes();
1230 var minutesByFive = Math.round( minutes / 5 ) * 5;
1231
1232 if (minutesByFive > 59)
1233 minutesByFive = 55;
1234 return minutesByFive;
1235 ]]>
1236 </body>
1237 </method>
1238 </implementation>
1239
1240 <!-- ::::::::::::::::: HANDLERS ::::::::::::::::::::::::: -->
1241
1242 <handlers>
1243 <handler event="bindingattached" action="this.initialize();"/>
1244 </handlers>
1245
1246 </binding>
1247
1248 <binding id="datetimepicker" extends="chrome://calendar/content/datetimepickers/datetimepickers.xml#datetimepicker-base"
1249 inherits="value,onchange,disabled,datepickerdisabled,timepickerdisabled">
1250 <!-- ::::::::::::::::: CONTENT ::::::::::::::::::::::::: -->
1251 <!-- onchange was simply "onDatePick()" in Moz1.6, but stopped working in Moz1.7
1252 so had to add navigation by parents. -->
1253 <content>
1254 <xul:hbox flex="1" anonid="hbox">
1255 <xul:datepicker anonid="date-picker"
1256 onchange="this.parentNode.parentNode.onDatePick();"
1257 xbl:inherits="value,disabled,disabled=datepickerdisabled"/>
1258 <xul:timepicker anonid="time-picker"
1259 onchange="this.parentNode.parentNode.onTimePick();"
1260 xbl:inherits="value,disabled,disabled=timepickerdisabled"/>
1261 </xul:hbox>
1262 </content>
1263
1264 <!-- ::::::::::::::::: INTERFACE ::::::::::::::::::::::::: -->
1265 <implementation>
1266 <property name="datepickerdisabled"
1267 onset="if (val) this.kDatePicker.setAttribute('disabled', 'true');
1268 else this.kDatePicker.removeAttribute('disabled');"
1269 onget="return this.kDatePicker.getAttribute('disabled')=='true';"/>
1270
1271 <!-- timepicker may be disabled alone for all day events -->
1272 <property name="timepickerdisabled"
1273 onset="if (val) this.kTimePicker.setAttribute('disabled', 'true');
1274 else this.kTimePicker.removeAttribute('disabled');"
1275 onget="return this.kTimePicker.getAttribute('disabled')=='true';"/>
1276
1277 <constructor>
1278 <![CDATA[
1279 this.kDatePicker =
1280 document.getAnonymousElementByAttribute(this, "anonid", "date-picker");
1281 this.kTimePicker =
1282 document.getAnonymousElementByAttribute(this, "anonid", "time-picker");
1283
1284 var val = this.getAttribute("value");
1285 this.mValue = (val ? new Date(val)
1286 : new Date());
1287 ]]>
1288 </constructor>
1289
1290 <method name="update">
1291 <parameter name="aValue"/>
1292 <parameter name="aRefresh"/>
1293 <body>
1294 <![CDATA[
1295 if (aValue != null) {
1296 this.mValue = aValue;
1297 }
1298 // set textBox.value property, not attribute
1299 this.kDatePicker.update(this.mValue, aRefresh);
1300 this.kTimePicker.update(this.mValue, aRefresh);
1301 if (aRefresh) {
1302 this.fireEvent("change");
1303 }
1304 ]]>
1305 </body>
1306 </method>
1307
1308 <!-- Date was changed by gui: update value. -->
1309 <method name="onDatePick">
1310 <body>
1311 <![CDATA[
1312 var tempTime;
1313 if (this.kDatePicker.lastDateParseIncludedTime)
1314 tempTime = new Date(this.kDatePicker.value);
1315 else
1316 tempTime = new Date(this.mValue);
1317 var newDate = new Date(this.kDatePicker.value);
1318 // Note: create new date because setting month and date of month in
1319 // either order can lead to unexpected results (month may be advanced
1320 // automatically if day of month is temporarily out of range).
1321 var dateTime = new Date(newDate.getFullYear(),
1322 newDate.getMonth(),
1323 newDate.getDate(),
1324 tempTime.getHours(),
1325 tempTime.getMinutes(),
1326 tempTime.getSeconds());
1327 this.mValue = dateTime;
1328 this.kTimePicker.update(dateTime, false);
1329 this.fireEvent("change");
1330 ]]>
1331 </body>
1332 </method>
1333
1334 <!-- Time was changed by gui: update value -->
1335 <method name="onTimePick">
1336 <body>
1337 <![CDATA[
1338 var dateTime = new Date(this.mValue);
1339 var newTime = this.kTimePicker.value;
1340 dateTime.setHours(newTime.getHours());
1341 dateTime.setMinutes(newTime.getMinutes());
1342 dateTime.setSeconds(newTime.getSeconds());
1343 this.mValue = dateTime;
1344 this.kDatePicker.update(dateTime, false);
1345 this.fireEvent("change");
1346 ]]>
1347 </body>
1348 </method>
1349
1350 </implementation>
1351
1352 <!-- ::::::::::::::::: HANDLERS ::::::::::::::::::::::::: -->
1353
1354 <handlers>
1355 <handler event="bindingattached" action="this.initialize();"/>
1356 </handlers>
1357
1358 </binding>
1359
1360 <binding id="datetimepicker-base" extends="chrome://global/content/bindings/general.xml#basecontrol"
1361 inherits="value,onchange">
1362 <resources>
1363 <stylesheet src="chrome://calendar/skin/datetimepickers/datetimepickers.css"/>
1364 </resources>
1365 <implementation>
1366 <constructor>
1367 <![CDATA[
1368 this.initDateFormat();
1369 this.initTimeFormat();
1370 ]]>
1371 </constructor>
1372
1373 <property name="value"
1374 onget="return this.mValue"
1375 onset="this.update(val, false)"/>
1376
1377 <property name="lastDateParseIncludedTime"
1378 onget="return this.mLastDateParseIncludedTime;"
1379 onset="this.mLastDateParseIncludedTime = val;" />
1380
1381 <method name="update">
1382 <parameter name="aValue"/>
1383 <parameter name="aRefresh"/>
1384 <body>
1385 <![CDATA[
1386 if (aValue != null) {
1387 this.mValue = aValue;
1388 }
1389 ]]>
1390 </body>
1391 </method>
1392
1393 <method name="fireEvent">
1394 <parameter name="aEventName"/>
1395 <parameter name="aDetail"/>
1396 <body>
1397 <![CDATA[
1398 var event = document.createEvent('Events');
1399 event.initEvent(aEventName, true, true);
1400 event.detail = aDetail;
1401 this.dispatchEvent(event);
1402 ]]>
1403 </body>
1404 </method>
1405
1406 <!-- Parameter aValue may be a date or a date time. Dates are
1407 read according to locale/OS setting (d-m-y or m-d-y or ...).
1408 (see initDateFormat). Uses parseTime() for times.
1409 -->
1410 <method name="parseDateTime">
1411 <parameter name="aValue"/>
1412 <body>
1413 <![CDATA[
1414 this.mLastDateParseIncludedTime = false;
1415 var tempDate = null;
1416 if (!this.probeSucceeded)
1417 return null; // avoid errors accessing uninitialized data.
1418
1419 var year = Number.MIN_VALUE; var month = -1; var day = -1; var timeString = null;
1420 if (this.alphaMonths == null) {
1421 // SHORT NUMERIC DATE, such as 2002-03-04, 4/3/2002, or CE2002Y03M04D.
1422 // Made of digits & nonDigits. (Nondigits may be unicode letters
1423 // which do not match \w, esp. in CJK locales.)
1424 // (.*)? binds to null if no suffix.
1425 var parseNumShortDateRegex = /^\D*(\d+)\D+(\d+)\D+(\d+)(.*)?$/;
1426 var dateNumbersArray = parseNumShortDateRegex.exec(aValue);
1427 if (dateNumbersArray != null) {
1428 year = Number(dateNumbersArray[this.yearIndex]);
1429 month = Number(dateNumbersArray[this.monthIndex]) - 1; // 0-based
1430 day = Number(dateNumbersArray[this.dayIndex]);
1431 timeString = dateNumbersArray[4];
1432 }
1433 } else {
1434 // SHORT DATE WITH ALPHABETIC MONTH, such as "dd MMM yy" or "MMMM dd, yyyy"
1435 // (\d+|[^\d\W]) is digits or letters, not both together.
1436 // Allows 31dec1999 (no delimiters between parts) if OS does (w2k does not).
1437 // Allows Dec 31, 1999 (comma and space between parts)
1438 // (Only accepts ASCII month names; JavaScript RegExp does not have an
1439 // easy way to describe unicode letters short of a HUGE character range
1440 // regexp derived from the Alphabetic ranges in
1441 // http://www.unicode.org/Public/UNIDATA/DerivedCoreProperties.txt)
1442 // (.*)? binds to null if no suffix.
1443 var parseAlphShortDateRegex = /^\s*(\d+|[^\d\W]+)\W{0,2}(\d+|[^\d\W]+)\W{0,2}(\d+|[^\d\W]+)(.*)?$/;
1444 var datePartsArray = parseAlphShortDateRegex.exec(aValue);
1445 if (datePartsArray != null) {
1446 year = Number(datePartsArray[this.yearIndex]);
1447 var monthString = datePartsArray[this.monthIndex].toUpperCase();
1448 for (var m = 0; m < this.alphaMonths.length; m++) {
1449 if (monthString == this.alphaMonths[m]) {
1450 month = m;
1451 break;
1452 }
1453 }
1454 day = Number(datePartsArray[this.dayIndex]);
1455 timeString = datePartsArray[4];
1456 }
1457 }
1458 if (year != Number.MIN_VALUE && month != -1 && day != -1) {
1459 // year, month, day successfully parsed
1460 if (0 <= year && year < 100) {
1461 // If 0 <= year < 100, treat as 2-digit year (like formatDate):
1462 // parse year as up to 30 years in future or 69 years in past.
1463 // (Covers 30-year mortgage and most working people's birthdate.)
1464 // otherwise will be treated as four digit year.
1465 var currentYear = new Date().getFullYear();
1466 var currentCentury = currentYear - currentYear % 100;
1467 year = currentCentury + year;
1468 if (year < currentYear - 69)
1469 year += 100;
1470 if (year > currentYear + 30)
1471 year -= 100;
1472 }
1473 // if time is also present, parse it
1474 var hours = 0; var minutes = 0; var seconds = 0;
1475 if (timeString != null) {
1476 var time = this.parseTime(dateNumbersArray[4]);
1477 if (time != null) {
1478 hours = time.getHours();
1479 minutes = time.getMinutes();
1480 seconds = time.getSeconds();
1481 this.mLastDateParseIncludedTime = true;
1482 }
1483 }
1484 tempDate = new Date(year, month, day, hours, minutes, seconds, 0);
1485 } //else did not match regex, not a valid date
1486 return tempDate;
1487 ]]>
1488 </body>
1489 </method>
1490
1491 <!-- Parse a variety of time formats so that cut and paste is likely to work.
1492 separator: ':' '.' ' ' symbol none
1493 "12:34:56" "12.34.56" "12 34 56" "12h34m56s" "123456"
1494 seconds optional: "02:34" "02.34" "02 34" "02h34m" "0234"
1495 minutes optional: "12" "12" "12" "12h" "12"
1496 1st hr digit optional:"9:34" " 9.34" "9 34" "9H34M" "934am"
1497 skip nondigit prefix " 12:34" "t12.34" " 12 34" "T12H34M" "T0234"
1498 am/pm optional "02:34 a.m.""02.34pm" "02 34 A M" "02H34M P.M." "0234pm"
1499 am/pm prefix "a.m. 02:34""pm02.34" "A M 02 34" "P.M. 02H34M" "pm0234"
1500 am/pm cyrillic "02:34\u0430.\u043c." "02 34 \u0420 \u041c"
1501 am/pm arabic "\u063502:34" (RTL 02:34a) "\u0645 02.34" (RTL 02.34 p)
1502 above/below noon "\u4e0a\u534802:34" "\u4e0b\u5348 02 34"
1503 noon before/after "\u5348\u524d02:34" "\u5348\u5f8c 02 34"
1504 -->
1505 <method name="parseTime">
1506 <parameter name="aValue"/>
1507 <body>
1508 <![CDATA[
1509 // Try/catch this, since some people are apparently
1510 // interested in using the datepicker in remote apps, where
1511 // calGetString wouldn't exist
1512 try {
1513 var noon = calGetString("dateFormat", "noon");
1514 if (aValue.toLowerCase() == noon.toLowerCase()) {
1515 return new Date(0, 0, 0, 12, 0, 0, 0);
1516 }
1517
1518 var midnight = calGetString("dateFormat", "midnight");
1519 if (aValue.toLowerCase() == midnight.toLowerCase()) {
1520 return new Date(0, 0, 0, 0, 0, 0, 0);
1521 }
1522 } catch(ex) {
1523 }
1524
1525 var time = null;
1526 var timePartsArray = this.parseTimeRegExp.exec(aValue);
1527 const PRE_INDEX=1, HR_INDEX=2, MIN_INDEX=4, SEC_INDEX=6, POST_INDEX=8;
1528
1529 if (timePartsArray != null) {
1530 var hoursString = timePartsArray[HR_INDEX]
1531 var hours = Number(hoursString);
1532 var hoursSuffix = timePartsArray[HR_INDEX + 1];
1533 if (!(0 <= hours && hours < 24)) return null;
1534
1535 var minutesString = timePartsArray[MIN_INDEX];
1536 var minutes = (minutesString == null? 0 : Number(minutesString));
1537 var minutesSuffix = timePartsArray[MIN_INDEX + 1];
1538 if (!(0 <= minutes && minutes < 60)) return null;
1539
1540 var secondsString = timePartsArray[SEC_INDEX];
1541 var seconds = (secondsString == null? 0 : Number(secondsString));
1542 var secondsSuffix = timePartsArray[SEC_INDEX + 1];
1543 if (!(0 <= seconds && seconds < 60)) return null;
1544
1545 var ampmCode = null;
1546 if (timePartsArray[PRE_INDEX] || timePartsArray[POST_INDEX]) {
1547 if (this.ampmIndex && timePartsArray[this.ampmIndex]) {
1548 // try current format order first
1549 var ampmString = timePartsArray[this.ampmIndex];
1550 if (this.amRegExp.test(ampmString)) {
1551 ampmCode = "AM";
1552 } else if (this.pmRegExp.test(ampmString)) {
1553 ampmCode = "PM";
1554 }
1555 }
1556 if (ampmCode == null) { // not yet found
1557 // try any format order
1558 var preString = timePartsArray[PRE_INDEX];
1559 var postString = timePartsArray[POST_INDEX];
1560 if ((preString && this.amRegExp.test(preString)) ||
1561 (postString && this.amRegExp.test(postString))) {
1562 ampmCode = "AM";
1563 } else if ((preString && this.pmRegExp.test(preString)) ||
1564 (postString && this.pmRegExp.test(postString))) {
1565 ampmCode = "PM";
1566 } // else no match, ignore and treat as 24hour time.
1567 }
1568 }
1569 if (ampmCode == "AM") {
1570 if (hours == 12)
1571 hours = 0;
1572 } else if (ampmCode == "PM") {
1573 if (hours < 12)
1574 hours += 12;
1575 }
1576 time = new Date(0, 0, 0, hours, minutes, seconds, 0);
1577 } // else did not match regex, not valid time
1578 return time;
1579 ]]>
1580 </body>
1581 </method>
1582
1583 <method name="initDateFormat">
1584 <body>
1585 <![CDATA[
1586 // probe the dateformat
1587 this.yearIndex = -1;
1588 this.monthIndex = -1;
1589 this.dayIndex = -1;
1590 this.twoDigitYear = false;
1591 this.alphaMonths = null;
1592 this.probeSucceeded = false;
1593 this.mLastDateParseIncludedTime = false;
1594
1595 // SHORT NUMERIC DATE, such as 2002-03-04, 4/3/2002, or CE2002Y03M04D.
1596 // Made of digits & nonDigits. (Nondigits may be unicode letters
1597 // which do not match \w, esp. in CJK locales.)
1598 this.parseShortDateRegex = /^\D*(\d+)\D+(\d+)\D+(\d+)\D?$/;
1599 var probeDate = new Date(2002,3-1,4); // month is 0-based
1600 var probeString = this.formatDate(probeDate);
1601 var probeArray = this.parseShortDateRegex.exec(probeString);
1602 if (probeArray != null) {
1603 // Numeric month format
1604 for (var i = 1; i <= 3; i++) {
1605 switch (Number(probeArray[i])) {
1606 case 02: this.twoDigitYear = true; // fall thru
1607 case 2002: this.yearIndex = i; break;
1608 case 3: this.monthIndex = i; break;
1609 case 4: this.dayIndex = i; break;
1610 }
1611 }
1612 // All three indexes are set (not -1) at this point.
1613 this.probeSucceeded = true;
1614 } else {
1615 // SHORT DATE WITH ALPHABETIC MONTH, such as "dd MMM yy" or "MMMM dd, yyyy"
1616 // (\d+|[^\d\W]) is digits or letters, not both together.
1617 // Allows 31dec1999 (no delimiters between parts) if OS does (w2k does not).
1618 // Allows Dec 31, 1999 (comma and space between parts)
1619 // (Only accepts ASCII month names; JavaScript RegExp does not have an
1620 // easy way to describe unicode letters short of a HUGE character range
1621 // regexp derived from the Alphabetic ranges in
1622 // http://www.unicode.org/Public/UNIDATA/DerivedCoreProperties.txt)
1623 this.parseShortDateRegex = /^\s*(\d+|[^\d\W]+)\W{0,2}(\d+|[^\d\W]+)\W{0,2}(\d+|[^\d\W]+)\s*$/;
1624 probeArray = this.parseShortDateRegex.exec(probeString);
1625 if (probeArray != null) {
1626 for (var j = 1; j <= 3; j++) {
1627 switch (Number(probeArray[j])) {
1628 case 02: this.twoDigitYear = true; // fall thru
1629 case 2002: this.yearIndex = j; break;
1630 case 4: this.dayIndex = j; break;
1631 default: this.monthIndex = j; break;
1632 }
1633 }
1634 if (this.yearIndex != -1 && this.dayIndex != -1 && this.monthIndex != -1) {
1635 this.probeSucceeded = true;
1636 // Fill this.alphaMonths with month names.
1637 this.alphaMonths = new Array(12);
1638 for (var m = 0; m < 12; m++) {
1639 probeDate.setMonth(m);
1640 probeString = this.formatDate(probeDate);
1641 probeArray = this.parseShortDateRegex.exec(probeString);
1642 if (probeArray != null)
1643 this.alphaMonths[m] = probeArray[this.monthIndex].toUpperCase();
1644 else
1645 this.probeSucceeded = false;
1646 }
1647 }
1648 }
1649 }
1650 if (! this.probeSucceeded)
1651 dump("\nOperating system short date format is not recognized: "+probeString+"\n");
1652 ]]>
1653 </body>
1654 </method>
1655
1656 <!-- Time format in 24-hour format or 12-hour format with am/pm string.
1657 Should match formats
1658 HH:mm, H:mm, HH:mm:ss, H:mm:ss
1659 hh:mm tt, h:mm tt, hh:mm:ss tt, h:mm:ss tt
1660 tt hh:mm, tt h:mm, tt hh:mm:ss, tt h:mm:ss
1661 where
1662 HH is 24 hour digits, with leading 0. H is 24 hour digits, no leading 0.
1663 hh is 12 hour digits, with leading 0. h is 12 hour digits, no leading 0.
1664 mm and ss are is minutes and seconds digits, with leading 0.
1665 tt is localized AM or PM string.
1666 ':' may be ':' or a units marker such as 'h', 'm', or 's' in 15h12m00s
1667 or may be omitted as in 151200.
1668 -->
1669 <method name="initTimeFormat">
1670 <body>
1671 <![CDATA[
1672 // probe the Time format
1673 this.ampmIndex = null;
1674 // Digits HR sep MIN sep SEC sep
1675 // Index: 2 3 4 5 6 7
1676 var digitsExpr = "(\\d?\\d)(\\D)?(?:(\\d\\d)(\\D)?(?:(\\d\\d)(\\D)?)?)?";
1677 // any letters or '.': non-digit alphanumeric, period (a.m.), or space (P M)
1678 var anyAmPmExpr = "(?:[^\\d\\W]|[. ])+";
1679 // digitsExpr has 6 captures, so index of first ampmExpr is 1, of last is 8.
1680 var probeTimeRegExp =
1681 new RegExp("^("+anyAmPmExpr+")?\\s?"+digitsExpr+"("+anyAmPmExpr+")?\\s*$");
1682 const PRE_INDEX=1, HR_INDEX=2, MIN_INDEX=4, SEC_INDEX=6, POST_INDEX=8;
1683 var amProbeTime = new Date(2000,0,1,6,12,34);
1684 var amProbeString = amProbeTime.toLocaleFormat("%X");
1685 var pmProbeTime = new Date(2000,0,1,18,12,34);
1686 var pmProbeString = pmProbeTime.toLocaleFormat("%X");
1687 var amFormatExpr = null, pmFormatExpr = null;
1688 if (amProbeString != pmProbeString) {
1689 var amProbeArray = probeTimeRegExp.exec(amProbeString);
1690 var pmProbeArray = probeTimeRegExp.exec(pmProbeString);
1691 if (amProbeArray != null && pmProbeArray != null) {
1692 if (amProbeArray[PRE_INDEX] && pmProbeArray[PRE_INDEX] &&
1693 amProbeArray[PRE_INDEX] != pmProbeArray[PRE_INDEX]) {
1694 this.ampmIndex = PRE_INDEX;
1695 } else if (amProbeArray[POST_INDEX] && pmProbeArray[POST_INDEX]) {
1696 if (amProbeArray[POST_INDEX] != pmProbeArray[POST_INDEX]) {
1697 this.ampmIndex = POST_INDEX;
1698 } else {
1699 // check if need to append previous character,
1700 // captured by the optional separator pattern after seconds digits,
1701 // or after minutes if no seconds, or after hours if no minutes.
1702 for (var k = SEC_INDEX; k >= HR_INDEX; k -= 2) {
1703 var nextSepI = k + 1;
1704 var nextDigitsI = k + 2;
1705 if ((k == SEC_INDEX ||
1706 (!amProbeArray[nextDigitsI] && !pmProbeArray[nextDigitsI]))
1707 && amProbeArray[nextSepI] && pmProbeArray[nextSepI]
1708 && amProbeArray[nextSepI] != pmProbeArray[nextSepI])
1709 {
1710 amProbeArray[POST_INDEX] =
1711 amProbeArray[nextSepI] + amProbeArray[POST_INDEX];
1712 pmProbeArray[POST_INDEX] =
1713 pmProbeArray[nextSepI] + pmProbeArray[POST_INDEX];
1714 this.ampmIndex = POST_INDEX;
1715 break;
1716 }
1717 }
1718 }
1719 }
1720 if (this.ampmIndex) {
1721 var makeFormatRegExp = function(string) {
1722 // make expr to accept either as provided, lowercased, or uppercased
1723 var regExp = string.replace(/(\W)/g, "[$1]"); // escape punctuation
1724 var lowercased = string.toLowerCase();
1725 if (string != lowercased)
1726 regExp += "|"+lowercased;
1727 var uppercased = string.toUpperCase();
1728 if (string != uppercased)
1729 regExp += "|"+uppercased;
1730 return regExp;
1731 };
1732 amFormatExpr = makeFormatRegExp(amProbeArray[this.ampmIndex]);
1733 pmFormatExpr = makeFormatRegExp(pmProbeArray[this.ampmIndex]);
1734 }
1735 }
1736 }
1737 // International formats ([roman, cyrillic]|arabic|chinese/kanji characters)
1738 // covering languages of U.N. (en,fr,sp,ru,ar,zh) and G8 (en,fr,de,it,ru,ja).
1739 // See examples at parseTimeOfDay.
1740 var amExpr =
1741 "[Aa\u0410\u0430][. ]?[Mm\u041c\u043c][. ]?|\u0635|\u4e0a\u5348|\u5348\u524d";
1742 var pmExpr =
1743 "[Pp\u0420\u0440][. ]?[Mm\u041c\u043c][. ]?|\u0645|\u4e0b\u5348|\u5348\u5f8c";
1744 if (this.ampmIndex){
1745 amExpr = amFormatExpr+"|"+amExpr;
1746 pmExpr = pmFormatExpr+"|"+pmExpr;
1747 }
1748 var ampmExpr = amExpr+"|"+pmExpr;
1749 // Must build am/pm formats into parse time regexp so that it can
1750 // match them without mistaking the initial char for an optional divider.
1751 // (For example, want to be able to parse both "12:34pm" and
1752 // "12H34M56Spm" for any characters H,M,S and any language's "pm".
1753 // The character between the last digit and the "pm" is optional.
1754 // Must recogize "pm" directly, otherwise in "12:34pm" the "S" pattern
1755 // matches the "p" character so only "m" is matched as ampm suffix.)
1756 //
1757 // digitsExpr has 6 captures, so index of first ampmExpr is 1, of last is 8.
1758 this.parseTimeRegExp =
1759 new RegExp("("+ampmExpr+")?\\s?"+digitsExpr+"("+ampmExpr+")?\\s*$");
1760 this.amRegExp = new RegExp("^(?:"+amExpr+")$");
1761 this.pmRegExp = new RegExp("^(?:"+pmExpr+")$");
1762 // build time display format that mimics "%x" format without seconds
1763 var ampmSep = (pmProbeString.indexOf(' ') != -1? " " : "");
1764 if (this.ampmIndex == PRE_INDEX)
1765 this.kTimeFormatString = "%p" + ampmSep + "%I:%M";
1766 else if (this.ampmIndex == POST_INDEX)
1767 this.kTimeFormatString = "%I:%M" + ampmSep + "%p";
1768 else
1769 this.kTimeFormatString = "%H:%M";
1770 ]]>
1771 </body>
1772 </method>
1773
1774 <method name="formatDate">
1775 <parameter name="aDate"/>
1776 <body>
1777 <![CDATA[
1778 // workaround for bugs 354073/327869:
1779 // aDate.toLocaleFormat("%x") exits application
1780 // if year before 1900 (e.g., typo) on MSWindows (due to MS bug).
1781 var nsIScriptableDateFormat =
1782 Components.interfaces.nsIScriptableDateFormat;
1783 var dateService =
1784 Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
1785 .getService(nsIScriptableDateFormat);
1786 return dateService.FormatDate("", dateService.dateFormatShort,
1787 aDate.getFullYear(),
1788 aDate.getMonth()+1,
1789 aDate.getDate());
1790 ]]>
1791 </body>
1792 </method>
1793
1794 <method name="formatTime">
1795 <parameter name="aValue"/>
1796 <body>
1797 <![CDATA[
1798 // remove leading zero in hours before colon in xx01:23pmxx
1799 return aValue.toLocaleFormat(this.kTimeFormatString).replace(/0(\d):/,"$1:");
1800 ]]>
1801 </body>
1802 </method>
1803 </implementation>
1804 </binding>
1805
1806 </bindings>