1 /** |
|
2 * This file contains Calendar utilities |
|
3 * :organization: Logilab |
|
4 * :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
5 * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
6 */ |
|
7 |
|
8 // IMPORTANT NOTE: the variables DAYNAMES AND MONTHNAMES will be added |
|
9 // by cubicweb automatically |
|
10 // dynamically computed (and cached) |
|
11 var _CAL_HEADER = null; |
|
12 |
|
13 TODAY = new Date(); |
|
14 |
|
15 /** |
|
16 * .. class:: Calendar |
|
17 * |
|
18 * Calendar (graphical) widget |
|
19 * |
|
20 * public methods are : |
|
21 * |
|
22 * __init__ : |
|
23 * :attr:`containerId`: the DOM node's ID where the calendar will be displayed |
|
24 * :attr:`inputId`: which input needs to be updated when a date is selected |
|
25 * :attr:`year`, :attr:`month`: year and month to be displayed |
|
26 * :attr:`cssclass`: CSS class of the calendar widget (default is 'commandCal') |
|
27 * |
|
28 * show() / hide(): |
|
29 * show or hide the calendar widget |
|
30 * |
|
31 * toggle(): |
|
32 * show (resp. hide) the calendar if it's hidden (resp. displayed) |
|
33 * |
|
34 * displayNextMonth(): (resp. displayPreviousMonth()) |
|
35 * update the calendar to display next (resp. previous) month |
|
36 */ |
|
37 Calendar = function(containerId, inputId, year, month, cssclass) { |
|
38 this.containerId = containerId; |
|
39 this.inputId = inputId; |
|
40 this.year = year; |
|
41 this.month = month - 1; // Javascript's counter starts at 0 for january |
|
42 this.cssclass = cssclass || "popupCalendar"; |
|
43 this.visible = false; |
|
44 this.domtable = null; |
|
45 |
|
46 this.cellprops = { |
|
47 'onclick': function() { |
|
48 dateSelected(this, containerId); |
|
49 }, |
|
50 'onmouseover': function() { |
|
51 this.style.fontWeight = 'bold'; |
|
52 }, |
|
53 'onmouseout': function() { |
|
54 this.style.fontWeight = 'normal'; |
|
55 } |
|
56 }; |
|
57 |
|
58 this.todayprops = jQuery.extend({}, |
|
59 this.cellprops, { |
|
60 'class': 'today' |
|
61 }); |
|
62 |
|
63 this._rowdisplay = function(row) { |
|
64 var _td = function(elt) { |
|
65 return TD(this.cellprops, elt); |
|
66 }; |
|
67 return TR(null, $.map(row, _td)); |
|
68 }; |
|
69 |
|
70 this._makecell = function(cellinfo) { |
|
71 return TD(cellinfo[0], cellinfo[1]); |
|
72 }; |
|
73 |
|
74 /** |
|
75 * .. function:: Calendar._uppercaseFirst(s) |
|
76 * |
|
77 * utility function (the only use for now is inside the calendar) |
|
78 */ |
|
79 this._uppercaseFirst = function(s) { |
|
80 return s.charAt(0).toUpperCase(); |
|
81 }; |
|
82 |
|
83 /** |
|
84 * .. function:: Calendar._domForRows(rows) |
|
85 * |
|
86 * accepts the cells data and builds the corresponding TR nodes |
|
87 * |
|
88 * * `rows`, a list of list of couples (daynum, cssprops) |
|
89 */ |
|
90 this._domForRows = function(rows) { |
|
91 var lines = []; |
|
92 for (i = 0; i < rows.length; i++) { |
|
93 lines.push(TR(null, $.map(rows[i], this._makecell))); |
|
94 } |
|
95 return lines; |
|
96 }; |
|
97 |
|
98 /** |
|
99 * .. function:: Calendar._headdisplay(row) |
|
100 * |
|
101 * builds the calendar headers |
|
102 */ |
|
103 this._headdisplay = function(row) { |
|
104 if (_CAL_HEADER) { |
|
105 return _CAL_HEADER; |
|
106 } |
|
107 var self = this; |
|
108 var _th = function(day) { |
|
109 return TH(null, self._uppercaseFirst(day)); |
|
110 }; |
|
111 return TR(null, $.map(DAYNAMES, _th)); |
|
112 }; |
|
113 |
|
114 this._getrows = function() { |
|
115 var rows = []; |
|
116 var firstday = new Date(this.year, this.month, 1); |
|
117 var stopdate = firstday.nextMonth(); |
|
118 var curdate = firstday.sub(firstday.getRealDay()); |
|
119 while (curdate.getTime() < stopdate) { |
|
120 var row = []; |
|
121 for (var i = 0; i < 7; i++) { |
|
122 if (curdate.getMonth() == this.month) { |
|
123 props = curdate.equals(TODAY) ? this.todayprops: this.cellprops; |
|
124 row.push([props, curdate.getDate()]); |
|
125 } else { |
|
126 row.push([this.cellprops, ""]); |
|
127 } |
|
128 curdate.iadd(1); |
|
129 } |
|
130 rows.push(row); |
|
131 } |
|
132 return rows; |
|
133 }; |
|
134 |
|
135 this._makecal = function() { |
|
136 var rows = this._getrows(); |
|
137 var monthname = MONTHNAMES[this.month] + " " + this.year; |
|
138 var prevlink = "javascript: togglePreviousMonth('" + this.containerId + "');"; |
|
139 var nextlink = "javascript: toggleNextMonth('" + this.containerId + "');"; |
|
140 this.domtable = TABLE({ |
|
141 'class': this.cssclass |
|
142 }, |
|
143 THEAD(null, TR(null, TH(null, A({ |
|
144 'href': prevlink |
|
145 }, |
|
146 "<<")), |
|
147 // IE 6/7 requires colSpan instead of colspan |
|
148 TH({ |
|
149 'colSpan': 5, |
|
150 'colspan': 5, |
|
151 'style': "text-align: center;" |
|
152 }, |
|
153 monthname), TH(null, A({ |
|
154 'href': nextlink |
|
155 }, |
|
156 ">>")))), TBODY(null, this._headdisplay(), this._domForRows(rows))); |
|
157 return this.domtable; |
|
158 }; |
|
159 |
|
160 this._updateDiv = function() { |
|
161 if (!this.domtable) { |
|
162 this._makecal(); |
|
163 } |
|
164 cw.jqNode(this.containerId).empty().append(this.domtable); |
|
165 // replaceChildNodes($(this.containerId), this.domtable); |
|
166 }; |
|
167 |
|
168 this.displayNextMonth = function() { |
|
169 this.domtable = null; |
|
170 if (this.month == 11) { |
|
171 this.year++; |
|
172 } |
|
173 this.month = (this.month + 1) % 12; |
|
174 this._updateDiv(); |
|
175 }; |
|
176 |
|
177 this.displayPreviousMonth = function() { |
|
178 this.domtable = null; |
|
179 if (this.month == 0) { |
|
180 this.year--; |
|
181 } |
|
182 this.month = (this.month + 11) % 12; |
|
183 this._updateDiv(); |
|
184 }; |
|
185 |
|
186 this.show = function() { |
|
187 if (!this.visible) { |
|
188 var container = cw.jqNode(this.containerId); |
|
189 if (!this.domtable) { |
|
190 this._makecal(); |
|
191 } |
|
192 container.empty().append(this.domtable); |
|
193 toggleVisibility(container); |
|
194 this.visible = true; |
|
195 } |
|
196 }; |
|
197 |
|
198 this.hide = function(event) { |
|
199 var self; |
|
200 if (event) { |
|
201 self = event.data.self; |
|
202 } else { |
|
203 self = this; |
|
204 } |
|
205 if (self.visible) { |
|
206 toggleVisibility(self.containerId); |
|
207 self.visible = false; |
|
208 } |
|
209 }; |
|
210 |
|
211 this.toggle = function() { |
|
212 if (this.visible) { |
|
213 this.hide(); |
|
214 } |
|
215 else { |
|
216 this.show(); |
|
217 } |
|
218 }; |
|
219 |
|
220 // call hide() when the user explicitly sets the focus on the matching input |
|
221 cw.jqNode(inputId).bind('focus', { |
|
222 'self': this |
|
223 }, |
|
224 this.hide); // connect(inputId, 'onfocus', this, 'hide'); |
|
225 }; |
|
226 |
|
227 /** |
|
228 * .. data:: Calendar.REGISTRY |
|
229 * |
|
230 * keep track of each calendar created |
|
231 */ |
|
232 Calendar.REGISTRY = {}; |
|
233 |
|
234 /** |
|
235 * .. function:: toggleCalendar(containerId, inputId, year, month) |
|
236 * |
|
237 * popup / hide calendar associated to `containerId` |
|
238 */ |
|
239 function toggleCalendar(containerId, inputId, year, month) { |
|
240 var cal = Calendar.REGISTRY[containerId]; |
|
241 if (!cal) { |
|
242 cal = new Calendar(containerId, inputId, year, month); |
|
243 Calendar.REGISTRY[containerId] = cal; |
|
244 } |
|
245 /* hide other calendars */ |
|
246 for (containerId in Calendar.REGISTRY) { |
|
247 var othercal = Calendar.REGISTRY[containerId]; |
|
248 if (othercal !== cal) { |
|
249 othercal.hide(); |
|
250 } |
|
251 } |
|
252 cal.toggle(); |
|
253 } |
|
254 |
|
255 /** |
|
256 * .. function:: toggleNextMonth(containerId) |
|
257 * |
|
258 * ask for next month to calendar displayed in `containerId` |
|
259 */ |
|
260 function toggleNextMonth(containerId) { |
|
261 var cal = Calendar.REGISTRY[containerId]; |
|
262 cal.displayNextMonth(); |
|
263 } |
|
264 |
|
265 /** |
|
266 * .. function:: togglePreviousMonth(containerId) |
|
267 * |
|
268 * ask for previous month to calendar displayed in `containerId` |
|
269 */ |
|
270 function togglePreviousMonth(containerId) { |
|
271 var cal = Calendar.REGISTRY[containerId]; |
|
272 cal.displayPreviousMonth(); |
|
273 } |
|
274 |
|
275 /** |
|
276 * .. function:: dateSelected(cell, containerId) |
|
277 * |
|
278 * callback called when the user clicked on a cell in the popup calendar |
|
279 */ |
|
280 function dateSelected(cell, containerId) { |
|
281 var cal = Calendar.REGISTRY[containerId]; |
|
282 var input = cw.getNode(cal.inputId); |
|
283 // XXX: the use of innerHTML might cause problems, but it seems to be |
|
284 // the only way understood by both IE and Mozilla. Otherwise, |
|
285 // IE accepts innerText and mozilla accepts textContent |
|
286 var selectedDate = new Date(cal.year, cal.month, cell.innerHTML, 12); |
|
287 input.value = remoteExec("format_date", cw.utils.toISOTimestamp(selectedDate)); |
|
288 cal.hide(); |
|
289 } |
|
290 |
|
291 function whichElement(e) { |
|
292 var targ; |
|
293 if (!e) { |
|
294 e = window.event; |
|
295 } |
|
296 if (e.target) { |
|
297 targ = e.target; |
|
298 } |
|
299 else if (e.srcElement) { |
|
300 targ = e.srcElement; |
|
301 } |
|
302 if (targ.nodeType == 3) // defeat Safari bug |
|
303 { |
|
304 targ = targ.parentNode; |
|
305 } |
|
306 return targ; |
|
307 } |
|
308 |
|
309 function getPosition(element) { |
|
310 var left; |
|
311 var top; |
|
312 var offset; |
|
313 // TODO: deal scrollbar positions also! |
|
314 left = element.offsetLeft; |
|
315 top = element.offsetTop; |
|
316 |
|
317 if (element.offsetParent != null) { |
|
318 offset = getPosition(element.offsetParent); |
|
319 left = left + offset[0]; |
|
320 top = top + offset[1]; |
|
321 |
|
322 } |
|
323 return [left, top]; |
|
324 } |
|
325 |
|
326 function getMouseInBlock(event) { |
|
327 var elt = event.target; |
|
328 var x = event.clientX; |
|
329 var y = event.clientY; |
|
330 var w = elt.clientWidth; |
|
331 var h = elt.clientHeight; |
|
332 var offset = getPosition(elt); |
|
333 |
|
334 x = 1.0 * (x - offset[0]) / w; |
|
335 y = 1.0 * (y - offset[1]) / h; |
|
336 return [x, y]; |
|
337 } |
|
338 function getHourFromMouse(event, hmin, hmax) { |
|
339 var pos = getMouseInBlock(event); |
|
340 var y = pos[1]; |
|
341 return Math.floor((hmax - hmin) * y + hmin); |
|
342 } |
|
343 |
|
344 function addCalendarItem(event, hmin, hmax, year, month, day, duration, baseurl) { |
|
345 var hour = getHourFromMouse(event, hmin, hmax); |
|
346 |
|
347 if (0 <= hour && hour < 24) { |
|
348 baseurl += "&start=" + year + "%2F" + month + "%2F" + day + "%20" + hour + ":00"; |
|
349 baseurl += "&stop=" + year + "%2F" + month + "%2F" + day + "%20" + (hour + duration) + ":00"; |
|
350 |
|
351 stopPropagation(event); |
|
352 window.location.assign(baseurl); |
|
353 return false; |
|
354 } |
|
355 return true; |
|
356 } |
|
357 |
|
358 function stopPropagation(event) { |
|
359 event.cancelBubble = true; |
|
360 if (event.stopPropagation) event.stopPropagation(); |
|
361 } |
|