7
7
*/
8
8
9
9
/**
10
- * Get common timezone and adjust dates to use this common timezone.
10
+ * Get common timezone, add UI for changing timezones, and adjust
11
+ * dates to use requested common timezone.
11
12
*
12
13
* This function is called during onload event (added to window.onload).
13
14
*
14
15
* @param {String } tzDefault: default timezone, if there is no cookie
15
- * @param {String } tzCookieName: name of cookie to store timezone
16
+ * @param {Object } tzCookieInfo: object literal with info about cookie to store timezone
17
+ * @param {String } tzCookieInfo.name: name of cookie to store timezone
16
18
* @param {String } tzClassName: denotes elements with date to be adjusted
17
19
*/
18
- function onloadTZSetup ( tzDefault , tzCookieName , tzClassName ) {
19
- var tzCookie = getCookie ( tzCookieName ) ;
20
- var tz = tzCookie ? tzCookie : tzDefault ;
20
+ function onloadTZSetup ( tzDefault , tzCookieInfo , tzClassName ) {
21
+ var tzCookieTZ = getCookie ( tzCookieInfo . name , tzCookieInfo ) ;
22
+ var tz = tzDefault ;
23
+
24
+ if ( tzCookieTZ ) {
25
+ // set timezone to value saved in a cookie
26
+ tz = tzCookieTZ ;
27
+ // refresh cookie, so its expiration counts from last use of gitweb
28
+ setCookie ( tzCookieInfo . name , tzCookieTZ , tzCookieInfo ) ;
29
+ }
30
+
31
+ // add UI for changing timezone
32
+ addChangeTZ ( tz , tzCookieInfo , tzClassName ) ;
21
33
22
34
// server-side of gitweb produces datetime in UTC,
23
35
// so if tz is 'utc' there is no need for changes
24
- if ( tz !== 'utc' ) {
25
- fixDatetimeTZ ( tz , tzClassName ) ;
26
- }
36
+ var nochange = tz === 'utc' ;
37
+
38
+ // adjust dates to use specified common timezone
39
+ fixDatetimeTZ ( tz , tzClassName , nochange ) ;
27
40
}
28
41
29
42
43
+ /* ...................................................................... */
44
+ /* Changing dates to use requested timezone */
45
+
30
46
/**
31
47
* Replace RFC-2822 dates contained in SPAN elements with tzClassName
32
48
* CSS class with equivalent dates in given timezone.
33
49
*
34
50
* @param {String } tz: numeric timezone in '(-|+)HHMM' format, or 'utc', or 'local'
35
51
* @param {String } tzClassName: specifies elements to be changed
52
+ * @param {Boolean } nochange: markup for timezone change, but don't change it
36
53
*/
37
- function fixDatetimeTZ ( tz , tzClassName ) {
54
+ function fixDatetimeTZ ( tz , tzClassName , nochange ) {
38
55
// sanity check, method should be ensured by common-lib.js
39
56
if ( ! document . getElementsByClassName ) {
40
57
return ;
@@ -48,13 +65,266 @@ function fixDatetimeTZ(tz, tzClassName) {
48
65
for ( var i = 0 , len = classesFound . length ; i < len ; i ++ ) {
49
66
var curElement = classesFound [ i ] ;
50
67
51
- // we use *.firstChild.data (W3C DOM) instead of *.innerHTML
52
- // as the latter doesn't always work everywhere in every browser
53
- var epoch = parseRFC2822Date ( curElement . firstChild . data ) ;
54
- var adjusted = formatDateRFC2882 ( epoch , tz ) ;
68
+ curElement . title = 'Click to change timezone' ;
69
+ if ( ! nochange ) {
70
+ // we use *.firstChild.data (W3C DOM) instead of *.innerHTML
71
+ // as the latter doesn't always work everywhere in every browser
72
+ var epoch = parseRFC2822Date ( curElement . firstChild . data ) ;
73
+ var adjusted = formatDateRFC2882 ( epoch , tz ) ;
74
+
75
+ curElement . firstChild . data = adjusted ;
76
+ }
77
+ }
78
+ }
79
+
80
+
81
+ /* ...................................................................... */
82
+ /* Adding triggers, generating timezone menu, displaying and hiding */
83
+
84
+ /**
85
+ * Adds triggers for UI to change common timezone used for dates in
86
+ * gitweb output: it marks up and/or creates item to click to invoke
87
+ * timezone change UI, creates timezone UI fragment to be attached,
88
+ * and installs appropriate onclick trigger (via event delegation).
89
+ *
90
+ * @param {String } tzSelected: pre-selected timezone,
91
+ * 'utc' or 'local' or '(-|+)HHMM'
92
+ * @param {Object } tzCookieInfo: object literal with info about cookie to store timezone
93
+ * @param {String } tzClassName: specifies elements to install trigger
94
+ */
95
+ function addChangeTZ ( tzSelected , tzCookieInfo , tzClassName ) {
96
+ // make link to timezone UI discoverable
97
+ addCssRule ( '.' + tzClassName + ':hover' ,
98
+ 'text-decoration: underline; cursor: help;' ) ;
99
+
100
+ // create form for selecting timezone (to be saved in a cookie)
101
+ var tzSelectFragment = document . createDocumentFragment ( ) ;
102
+ tzSelectFragment = createChangeTZForm ( tzSelectFragment ,
103
+ tzSelected , tzCookieInfo , tzClassName ) ;
104
+
105
+ // event delegation handler for timezone selection UI (clicking on entry)
106
+ // see http://www.nczonline.net/blog/2009/06/30/event-delegation-in-javascript/
107
+ // assumes that there is no existing document.onclick handler
108
+ document . onclick = function onclickHandler ( event ) {
109
+ //IE doesn't pass in the event object
110
+ event = event || window . event ;
111
+
112
+ //IE uses srcElement as the target
113
+ var target = event . target || event . srcElement ;
114
+
115
+ switch ( target . className ) {
116
+ case tzClassName :
117
+ // don't display timezone menu if it is already displayed
118
+ if ( tzSelectFragment . childNodes . length > 0 ) {
119
+ displayChangeTZForm ( target , tzSelectFragment ) ;
120
+ }
121
+ break ;
122
+ } // end switch
123
+ } ;
124
+ }
125
+
126
+ /**
127
+ * Create DocumentFragment with UI for changing common timezone in
128
+ * which dates are shown in.
129
+ *
130
+ * @param {DocumentFragment } documentFragment: where attach UI
131
+ * @param {String } tzSelected: default (pre-selected) timezone
132
+ * @param {Object } tzCookieInfo: object literal with info about cookie to store timezone
133
+ * @returns {DocumentFragment }
134
+ */
135
+ function createChangeTZForm ( documentFragment , tzSelected , tzCookieInfo , tzClassName ) {
136
+ var div = document . createElement ( "div" ) ;
137
+ div . className = 'popup' ;
138
+
139
+ /* '<div class="close-button" title="(click on this box to close)">X</div>' */
140
+ var closeButton = document . createElement ( 'div' ) ;
141
+ closeButton . className = 'close-button' ;
142
+ closeButton . title = '(click on this box to close)' ;
143
+ closeButton . appendChild ( document . createTextNode ( 'X' ) ) ;
144
+ closeButton . onclick = closeTZFormHandler ( documentFragment , tzClassName ) ;
145
+ div . appendChild ( closeButton ) ;
146
+
147
+ /* 'Select timezone: <br clear="all">' */
148
+ div . appendChild ( document . createTextNode ( 'Select timezone: ' ) ) ;
149
+ var br = document . createElement ( 'br' ) ;
150
+ br . clear = 'all' ;
151
+ div . appendChild ( br ) ;
152
+
153
+ /* '<select name="tzoffset">
154
+ * ...
155
+ * <option value="-0700">UTC-07:00</option>
156
+ * <option value="-0600">UTC-06:00</option>
157
+ * ...
158
+ * </select>' */
159
+ var select = document . createElement ( "select" ) ;
160
+ select . name = "tzoffset" ;
161
+ //select.style.clear = 'all';
162
+ select . appendChild ( generateTZOptions ( tzSelected ) ) ;
163
+ select . onchange = selectTZHandler ( documentFragment , tzCookieInfo , tzClassName ) ;
164
+ div . appendChild ( select ) ;
165
+
166
+ documentFragment . appendChild ( div ) ;
167
+
168
+ return documentFragment ;
169
+ }
170
+
171
+
172
+ /**
173
+ * Hide (remove from DOM) timezone change UI, ensuring that it is not
174
+ * garbage collected and that it can be re-enabled later.
175
+ *
176
+ * @param {DocumentFragment } documentFragment: contains detached UI
177
+ * @param {HTMLSelectElement } target: select element inside of UI
178
+ * @param {String } tzClassName: specifies element where UI was installed
179
+ * @returns {DocumentFragment } documentFragment
180
+ */
181
+ function removeChangeTZForm ( documentFragment , target , tzClassName ) {
182
+ // find containing element, where we appended timezone selection UI
183
+ // `target' is somewhere inside timezone menu
184
+ var container = target . parentNode , popup = target ;
185
+ while ( container &&
186
+ container . className !== tzClassName ) {
187
+ popup = container ;
188
+ container = container . parentNode ;
189
+ }
190
+ // safety check if we found correct container,
191
+ // and if it isn't deleted already
192
+ if ( ! container || ! popup ||
193
+ container . className !== tzClassName ||
194
+ popup . className !== 'popup' ) {
195
+ return documentFragment ;
196
+ }
55
197
56
- curElement . firstChild . data = adjusted ;
198
+ // timezone selection UI was appended as last child
199
+ // see also displayChangeTZForm function
200
+ var removed = popup . parentNode . removeChild ( popup ) ;
201
+ if ( documentFragment . firstChild !== removed ) { // the only child
202
+ // re-append it so it would be available for next time
203
+ documentFragment . appendChild ( removed ) ;
57
204
}
205
+ // all of inline style was added by this script
206
+ // it is not really needed to remove it, but it is a good practice
207
+ container . removeAttribute ( 'style' ) ;
208
+
209
+ return documentFragment ;
210
+ }
211
+
212
+
213
+ /**
214
+ * Display UI for changing common timezone for dates in gitweb output.
215
+ * To be used from 'onclick' event handler.
216
+ *
217
+ * @param {HTMLElement } target: where to install/display UI
218
+ * @param {DocumentFragment } tzSelectFragment: timezone selection UI
219
+ */
220
+ function displayChangeTZForm ( target , tzSelectFragment ) {
221
+ // for absolute positioning to be related to target element
222
+ target . style . position = 'relative' ;
223
+ target . style . display = 'inline-block' ;
224
+
225
+ // show/display UI for changing timezone
226
+ target . appendChild ( tzSelectFragment ) ;
227
+ }
228
+
229
+
230
+ /* ...................................................................... */
231
+ /* List of timezones for timezone selection menu */
232
+
233
+ /**
234
+ * Generate list of timezones for creating timezone select UI
235
+ *
236
+ * @returns {Object[] } list of e.g. { value: '+0100', descr: 'GMT+01:00' }
237
+ */
238
+ function generateTZList ( ) {
239
+ var timezones = [
240
+ { value : "utc" , descr : "UTC/GMT" } ,
241
+ { value : "local" , descr : "Local (per browser)" }
242
+ ] ;
243
+
244
+ // generate all full hour timezones (no fractional timezones)
245
+ for ( var x = - 12 , idx = timezones . length ; x <= + 14 ; x ++ , idx ++ ) {
246
+ var hours = ( x >= 0 ? '+' : '-' ) + padLeft ( x >= 0 ? x : - x , 2 ) ;
247
+ timezones [ idx ] = { value : hours + '00' , descr : 'UTC' + hours + ':00' } ;
248
+ if ( x === 0 ) {
249
+ timezones [ idx ] . descr = 'UTC\u00B100:00' ; // 'UTC±00:00'
250
+ }
251
+ }
252
+
253
+ return timezones ;
254
+ }
255
+
256
+ /**
257
+ * Generate <options> elements for timezone select UI
258
+ *
259
+ * @param {String } tzSelected: default timezone
260
+ * @returns {DocumentFragment } list of options elements to appendChild
261
+ */
262
+ function generateTZOptions ( tzSelected ) {
263
+ var elems = document . createDocumentFragment ( ) ;
264
+ var timezones = generateTZList ( ) ;
265
+
266
+ for ( var i = 0 , len = timezones . length ; i < len ; i ++ ) {
267
+ var tzone = timezones [ i ] ;
268
+ var option = document . createElement ( "option" ) ;
269
+ if ( tzone . value === tzSelected ) {
270
+ option . defaultSelected = true ;
271
+ }
272
+ option . value = tzone . value ;
273
+ option . appendChild ( document . createTextNode ( tzone . descr ) ) ;
274
+
275
+ elems . appendChild ( option ) ;
276
+ }
277
+
278
+ return elems ;
279
+ }
280
+
281
+
282
+ /* ...................................................................... */
283
+ /* Event handlers and/or their generators */
284
+
285
+ /**
286
+ * Create event handler that select timezone and closes timezone select UI.
287
+ * To be used as $('select[name="tzselect"]').onchange handler.
288
+ *
289
+ * @param {DocumentFragment } tzSelectFragment: timezone selection UI
290
+ * @param {Object } tzCookieInfo: object literal with info about cookie to store timezone
291
+ * @param {String } tzCookieInfo.name: name of cookie to save result of selection
292
+ * @param {String } tzClassName: specifies element where UI was installed
293
+ * @returns {Function } event handler
294
+ */
295
+ function selectTZHandler ( tzSelectFragment , tzCookieInfo , tzClassName ) {
296
+ //return function selectTZ(event) {
297
+ return function ( event ) {
298
+ event = event || window . event ;
299
+ var target = event . target || event . srcElement ;
300
+
301
+ var selected = target . options . item ( target . selectedIndex ) ;
302
+ removeChangeTZForm ( tzSelectFragment , target , tzClassName ) ;
303
+
304
+ if ( selected ) {
305
+ selected . defaultSelected = true ;
306
+ setCookie ( tzCookieInfo . name , selected . value , tzCookieInfo ) ;
307
+ fixDatetimeTZ ( selected . value , tzClassName ) ;
308
+ }
309
+ } ;
310
+ }
311
+
312
+ /**
313
+ * Create event handler that closes timezone select UI.
314
+ * To be used e.g. as $('.closebutton').onclick handler.
315
+ *
316
+ * @param {DocumentFragment } tzSelectFragment: timezone selection UI
317
+ * @param {String } tzClassName: specifies element where UI was installed
318
+ * @returns {Function } event handler
319
+ */
320
+ function closeTZFormHandler ( tzSelectFragment , tzClassName ) {
321
+ //return function closeTZForm(event) {
322
+ return function ( event ) {
323
+ event = event || window . event ;
324
+ var target = event . target || event . srcElement ;
325
+
326
+ removeChangeTZForm ( tzSelectFragment , target , tzClassName ) ;
327
+ } ;
58
328
}
59
329
60
330
/* end of adjust-timezone.js */
0 commit comments