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