]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**S\r |
2 | * @class Ext.calendar.view.DayBody\r | |
3 | * @extends Ext.calendar.view.AbstractCalendar\r | |
4 | * <p>This is the scrolling container within the day and week views where non-all-day events are displayed.\r | |
5 | * Normally you should not need to use this class directly -- instead you should use {@link Ext.calendar.DayView DayView}\r | |
6 | * which aggregates this class and the {@link Ext.calendar.DayHeaderView DayHeaderView} into the single unified view\r | |
7 | * presented by {@link Ext.calendar.CalendarPanel CalendarPanel}.</p>\r | |
8 | * @constructor\r | |
9 | * @param {Object} config The config object\r | |
10 | */\r | |
11 | Ext.define('Ext.calendar.view.DayBody', {\r | |
12 | extend: 'Ext.calendar.view.AbstractCalendar',\r | |
13 | alias: 'widget.daybodyview',\r | |
14 | \r | |
15 | requires: [\r | |
16 | 'Ext.XTemplate',\r | |
17 | 'Ext.calendar.template.DayBody',\r | |
18 | 'Ext.calendar.data.EventMappings',\r | |
19 | 'Ext.calendar.dd.DayDragZone',\r | |
20 | 'Ext.calendar.dd.DayDropZone'\r | |
21 | ],\r | |
22 | \r | |
23 | //private\r | |
24 | dayColumnElIdDelimiter: '-day-col-',\r | |
25 | \r | |
26 | /**\r | |
27 | * @event eventresize\r | |
28 | * Fires after the user drags the resize handle of an event to resize it\r | |
29 | * @param {Ext.calendar.view.DayBody} this\r | |
30 | * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was resized\r | |
31 | * containing the updated start and end dates\r | |
32 | */\r | |
33 | \r | |
34 | /**\r | |
35 | * @event dayclick\r | |
36 | * Fires after the user clicks within the day view container and not on an event element\r | |
37 | * @param {Ext.calendar.view.DayBody} this\r | |
38 | * @param {Date} dt The date/time that was clicked on\r | |
39 | * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks within the \r | |
40 | * DayBodyView always return false for this param.\r | |
41 | * @param {Ext.core.Element} el The Element that was clicked on\r | |
42 | */\r | |
43 | \r | |
44 | //private\r | |
45 | initDD: function() {\r | |
46 | var cfg = {\r | |
47 | createText: this.ddCreateEventText,\r | |
48 | moveText: this.ddMoveEventText,\r | |
49 | resizeText: this.ddResizeEventText\r | |
50 | };\r | |
51 | \r | |
52 | this.el.ddScrollConfig = {\r | |
53 | // scrolling is buggy in IE/Opera for some reason. A larger vthresh\r | |
54 | // makes it at least functional if not perfect\r | |
55 | vthresh: Ext.isIE || Ext.isOpera ? 100: 40,\r | |
56 | hthresh: -1,\r | |
57 | frequency: 50,\r | |
58 | increment: 100,\r | |
59 | ddGroup: 'DayViewDD'\r | |
60 | };\r | |
61 | this.dragZone = new Ext.calendar.dd.DayDragZone(this.el, Ext.apply({\r | |
62 | view: this,\r | |
63 | containerScroll: true\r | |
64 | },\r | |
65 | cfg));\r | |
66 | \r | |
67 | this.dropZone = new Ext.calendar.dd.DayDropZone(this.el, Ext.apply({\r | |
68 | view: this\r | |
69 | },\r | |
70 | cfg));\r | |
71 | },\r | |
72 | \r | |
73 | //private\r | |
74 | refresh: function() {\r | |
75 | var top = this.el.getScroll().top;\r | |
76 | this.prepareData();\r | |
77 | this.renderTemplate();\r | |
78 | this.renderItems();\r | |
79 | \r | |
80 | // skip this if the initial render scroll position has not yet been set.\r | |
81 | // necessary since IE/Opera must be deferred, so the first refresh will\r | |
82 | // override the initial position by default and always set it to 0.\r | |
83 | if (this.scrollReady) {\r | |
84 | this.scrollTo(top);\r | |
85 | }\r | |
86 | },\r | |
87 | \r | |
88 | /**\r | |
89 | * Scrolls the container to the specified vertical position. If the view is large enough that\r | |
90 | * there is no scroll overflow then this method will have no effect.\r | |
91 | * @param {Number} y The new vertical scroll position in pixels \r | |
92 | * @param {Boolean} defer (optional) <p>True to slightly defer the call, false to execute immediately.</p> \r | |
93 | * <p>This method will automatically defer itself for IE and Opera (even if you pass false) otherwise\r | |
94 | * the scroll position will not update in those browsers. You can optionally pass true, however, to\r | |
95 | * force the defer in all browsers, or use your own custom conditions to determine whether this is needed.</p>\r | |
96 | * <p>Note that this method should not generally need to be called directly as scroll position is managed internally.</p>\r | |
97 | */\r | |
98 | scrollTo: function(y, defer) {\r | |
99 | defer = defer || (Ext.isIE || Ext.isOpera);\r | |
100 | if (defer) {\r | |
101 | Ext.defer(function() {\r | |
102 | this.el.scrollTo('top', y, true);\r | |
103 | this.scrollReady = true;\r | |
104 | }, 10, this);\r | |
105 | }\r | |
106 | else {\r | |
107 | this.el.scrollTo('top', y, true);\r | |
108 | this.scrollReady = true;\r | |
109 | }\r | |
110 | },\r | |
111 | \r | |
112 | // private\r | |
113 | afterRender: function() {\r | |
114 | if (!this.tpl) {\r | |
115 | this.tpl = new Ext.calendar.template.DayBody({\r | |
116 | id: this.id,\r | |
117 | dayCount: this.dayCount,\r | |
118 | showTodayText: this.showTodayText,\r | |
119 | todayText: this.todayText,\r | |
120 | showTime: this.showTime\r | |
121 | });\r | |
122 | }\r | |
123 | this.tpl.compile();\r | |
124 | \r | |
125 | this.addCls('ext-cal-body-ct');\r | |
126 | \r | |
127 | this.callParent(arguments);\r | |
128 | \r | |
129 | // default scroll position to 7am:\r | |
130 | this.scrollTo(7 * 42);\r | |
131 | },\r | |
132 | \r | |
133 | // private\r | |
134 | forceSize: Ext.emptyFn,\r | |
135 | \r | |
136 | // private\r | |
137 | onEventResize: function(rec, data) {\r | |
138 | var D = Ext.calendar.util.Date,\r | |
139 | start = Ext.calendar.data.EventMappings.StartDate.name,\r | |
140 | end = Ext.calendar.data.EventMappings.EndDate.name;\r | |
141 | \r | |
142 | if (D.compare(rec.data[start], data.StartDate) === 0 &&\r | |
143 | D.compare(rec.data[end], data.EndDate) === 0) {\r | |
144 | // no changes\r | |
145 | return;\r | |
146 | }\r | |
147 | rec.set(start, data.StartDate);\r | |
148 | rec.set(end, data.EndDate);\r | |
149 | \r | |
150 | this.fireEvent('eventresize', this, rec);\r | |
151 | },\r | |
152 | \r | |
153 | // inherited docs\r | |
154 | getEventBodyMarkup: function() {\r | |
155 | if (!this.eventBodyMarkup) {\r | |
156 | this.eventBodyMarkup = ['{Title}',\r | |
157 | '<tpl if="_isReminder">',\r | |
158 | '<i class="ext-cal-ic ext-cal-ic-rem"> </i>',\r | |
159 | '</tpl>',\r | |
160 | '<tpl if="_isRecurring">',\r | |
161 | '<i class="ext-cal-ic ext-cal-ic-rcr"> </i>',\r | |
162 | '</tpl>'\r | |
163 | // '<tpl if="spanLeft">',\r | |
164 | // '<i class="ext-cal-spl"> </i>',\r | |
165 | // '</tpl>',\r | |
166 | // '<tpl if="spanRight">',\r | |
167 | // '<i class="ext-cal-spr"> </i>',\r | |
168 | // '</tpl>'\r | |
169 | ].join('');\r | |
170 | }\r | |
171 | return this.eventBodyMarkup;\r | |
172 | },\r | |
173 | \r | |
174 | // inherited docs\r | |
175 | getEventTemplate: function() {\r | |
176 | if (!this.eventTpl) {\r | |
177 | this.eventTpl = !(Ext.isIE || Ext.isOpera) ?\r | |
178 | new Ext.XTemplate(\r | |
179 | '<div id="{_elId}" class="{_selectorCls} {_colorCls} ext-cal-evt ext-cal-evr" style="left: {_left}%; width: {_width}%; top: {_top}px; height: {_height}px;">',\r | |
180 | '<div class="ext-evt-bd">', this.getEventBodyMarkup(), '</div>',\r | |
181 | '<div class="ext-evt-rsz"><div class="ext-evt-rsz-h"> </div></div>',\r | |
182 | '</div>'\r | |
183 | )\r | |
184 | : new Ext.XTemplate(\r | |
185 | '<div id="{_elId}" class="ext-cal-evt {_selectorCls} {_colorCls}-x" style="left: {_left}%; width: {_width}%; top: {_top}px;">',\r | |
186 | '<div class="ext-cal-evb"> </div>',\r | |
187 | '<dl style="height: {_height}px;" class="ext-cal-evdm">',\r | |
188 | '<dd class="ext-evt-bd">',\r | |
189 | this.getEventBodyMarkup(),\r | |
190 | '</dd>',\r | |
191 | '<div class="ext-evt-rsz"><div class="ext-evt-rsz-h"> </div></div>',\r | |
192 | '</dl>',\r | |
193 | '<div class="ext-cal-evb"> </div>',\r | |
194 | '</div>'\r | |
195 | );\r | |
196 | this.eventTpl.compile();\r | |
197 | }\r | |
198 | return this.eventTpl;\r | |
199 | },\r | |
200 | \r | |
201 | /**\r | |
202 | * <p>Returns the XTemplate that is bound to the calendar's event store (it expects records of type\r | |
203 | * {@link Ext.calendar.EventRecord}) to populate the calendar views with <strong>all-day</strong> events. \r | |
204 | * Internally this method by default generates different markup for browsers that support CSS border radius \r | |
205 | * and those that don't. This method can be overridden as needed to customize the markup generated.</p>\r | |
206 | * <p>Note that this method calls {@link #getEventBodyMarkup} to retrieve the body markup for events separately\r | |
207 | * from the surrounding container markup. This provdes the flexibility to customize what's in the body without\r | |
208 | * having to override the entire XTemplate. If you do override this method, you should make sure that your \r | |
209 | * overridden version also does the same.</p>\r | |
210 | * @return {Ext.XTemplate} The event XTemplate\r | |
211 | */\r | |
212 | getEventAllDayTemplate: function() {\r | |
213 | if (!this.eventAllDayTpl) {\r | |
214 | var tpl,\r | |
215 | body = this.getEventBodyMarkup();\r | |
216 | \r | |
217 | tpl = !(Ext.isIE || Ext.isOpera) ?\r | |
218 | new Ext.XTemplate(\r | |
219 | '<div id="{_elId}" class="{_selectorCls} {_colorCls} {spanCls} ext-cal-evt ext-cal-evr" style="left: {_left}%; width: {_width}%; top: {_top}px; height: {_height}px;">',\r | |
220 | body,\r | |
221 | '</div>'\r | |
222 | )\r | |
223 | : new Ext.XTemplate(\r | |
224 | '<div id="{_elId}" class="ext-cal-evt" style="left: {_left}%; width: {_width}%; top: {_top}px; height: {_height}px;">',\r | |
225 | '<div class="{_selectorCls} {spanCls} {_colorCls} ext-cal-evo">',\r | |
226 | '<div class="ext-cal-evm">',\r | |
227 | '<div class="ext-cal-evi">',\r | |
228 | body,\r | |
229 | '</div>',\r | |
230 | '</div>',\r | |
231 | '</div></div>'\r | |
232 | );\r | |
233 | tpl.compile();\r | |
234 | this.eventAllDayTpl = tpl;\r | |
235 | }\r | |
236 | return this.eventAllDayTpl;\r | |
237 | },\r | |
238 | \r | |
239 | // private\r | |
240 | getTemplateEventData: function(evt) {\r | |
241 | var selector = this.getEventSelectorCls(evt[Ext.calendar.data.EventMappings.EventId.name]),\r | |
242 | data = {},\r | |
243 | M = Ext.calendar.data.EventMappings;\r | |
244 | \r | |
245 | this.getTemplateEventBox(evt);\r | |
246 | \r | |
247 | data._selectorCls = selector;\r | |
248 | data._colorCls = 'ext-color-' + (evt[M.CalendarId.name] || '0') + (evt._renderAsAllDay ? '-ad': '');\r | |
249 | data._elId = selector + (evt._weekIndex ? '-' + evt._weekIndex: '');\r | |
250 | data._isRecurring = evt.Recurrence && evt.Recurrence != '';\r | |
251 | data._isReminder = evt[M.Reminder.name] && evt[M.Reminder.name] != '';\r | |
252 | var title = evt[M.Title.name];\r | |
253 | data.Title = (evt[M.IsAllDay.name] ? '': Ext.Date.format(evt[M.StartDate.name], 'g:ia ')) + (!title || title.length == 0 ? '(No title)': title);\r | |
254 | \r | |
255 | return Ext.applyIf(data, evt);\r | |
256 | },\r | |
257 | \r | |
258 | // private\r | |
259 | getTemplateEventBox: function(evt) {\r | |
260 | var heightFactor = 0.7,\r | |
261 | start = evt[Ext.calendar.data.EventMappings.StartDate.name],\r | |
262 | end = evt[Ext.calendar.data.EventMappings.EndDate.name],\r | |
263 | startMins = start.getHours() * 60 + start.getMinutes(),\r | |
264 | endMins = end.getHours() * 60 + end.getMinutes(),\r | |
265 | diffMins = endMins - startMins;\r | |
266 | \r | |
267 | evt._left = 0;\r | |
268 | evt._width = 100;\r | |
269 | evt._top = Math.round(startMins * heightFactor);\r | |
270 | evt._height = Math.max((diffMins * heightFactor), 15);\r | |
271 | },\r | |
272 | \r | |
273 | // private\r | |
274 | renderItems: function() {\r | |
275 | var day = 0,\r | |
276 | evts = [],\r | |
277 | ev,\r | |
278 | d,\r | |
279 | ct,\r | |
280 | item,\r | |
281 | i,\r | |
282 | j,\r | |
283 | l,\r | |
284 | emptyCells, skipped,\r | |
285 | evt,\r | |
286 | evt2,\r | |
287 | overlapCols,\r | |
288 | prevCol,\r | |
289 | colWidth,\r | |
290 | evtWidth,\r | |
291 | markup,\r | |
292 | target;\r | |
293 | for (; day < this.dayCount; day++) {\r | |
294 | ev = emptyCells = skipped = 0;\r | |
295 | d = this.eventGrid[0][day];\r | |
296 | ct = d ? d.length: 0;\r | |
297 | \r | |
298 | for (; ev < ct; ev++) {\r | |
299 | evt = d[ev];\r | |
300 | if (!evt) {\r | |
301 | continue;\r | |
302 | }\r | |
303 | item = evt.data || evt.event.data;\r | |
304 | if (item._renderAsAllDay) {\r | |
305 | continue;\r | |
306 | }\r | |
307 | Ext.apply(item, {\r | |
308 | cls: 'ext-cal-ev',\r | |
309 | _positioned: true\r | |
310 | });\r | |
311 | evts.push({\r | |
312 | data: this.getTemplateEventData(item),\r | |
313 | date: Ext.calendar.util.Date.add(this.viewStart, {days: day})\r | |
314 | });\r | |
315 | }\r | |
316 | }\r | |
317 | \r | |
318 | // overlapping event pre-processing loop\r | |
319 | i = j = overlapCols = prevCol = 0;\r | |
320 | l = evts.length;\r | |
321 | for (; i < l; i++) {\r | |
322 | evt = evts[i].data;\r | |
323 | evt2 = null;\r | |
324 | prevCol = overlapCols;\r | |
325 | for (j = 0; j < l; j++) {\r | |
326 | if (i == j) {\r | |
327 | continue;\r | |
328 | }\r | |
329 | evt2 = evts[j].data;\r | |
330 | if (this.isOverlapping(evt, evt2)) {\r | |
331 | evt._overlap = evt._overlap == undefined ? 1: evt._overlap + 1;\r | |
332 | if (i < j) {\r | |
333 | if (evt._overcol === undefined) {\r | |
334 | evt._overcol = 0;\r | |
335 | }\r | |
336 | evt2._overcol = evt._overcol + 1;\r | |
337 | overlapCols = Math.max(overlapCols, evt2._overcol);\r | |
338 | }\r | |
339 | }\r | |
340 | }\r | |
341 | }\r | |
342 | \r | |
343 | // rendering loop\r | |
344 | for (i = 0; i < l; i++) {\r | |
345 | evt = evts[i].data;\r | |
346 | if (evt._overlap !== undefined) {\r | |
347 | colWidth = 100 / (overlapCols + 1);\r | |
348 | evtWidth = 100 - (colWidth * evt._overlap);\r | |
349 | \r | |
350 | evt._width = colWidth;\r | |
351 | evt._left = colWidth * evt._overcol;\r | |
352 | }\r | |
353 | markup = this.getEventTemplate().apply(evt);\r | |
354 | target = this.id + '-day-col-' + Ext.Date.format(evts[i].date, 'Ymd');\r | |
355 | Ext.get(target).select('*').destroy();\r | |
356 | \r | |
357 | Ext.core.DomHelper.append(target, markup);\r | |
358 | }\r | |
359 | \r | |
360 | this.fireEvent('eventsrendered', this);\r | |
361 | },\r | |
362 | \r | |
363 | // private\r | |
364 | getDayEl: function(dt) {\r | |
365 | return Ext.get(this.getDayId(dt));\r | |
366 | },\r | |
367 | \r | |
368 | // private\r | |
369 | getDayId: function(dt) {\r | |
370 | if (Ext.isDate(dt)) {\r | |
371 | dt = Ext.Date.format(dt, 'Ymd');\r | |
372 | }\r | |
373 | return this.id + this.dayColumnElIdDelimiter + dt;\r | |
374 | },\r | |
375 | \r | |
376 | // private\r | |
377 | getDaySize: function() {\r | |
378 | var box = this.el.down('.ext-cal-day-col-inner').getBox();\r | |
379 | return {\r | |
380 | height: box.height,\r | |
381 | width: box.width\r | |
382 | };\r | |
383 | },\r | |
384 | \r | |
385 | // private\r | |
386 | getDayAt: function(x, y) {\r | |
387 | var xoffset = this.el.down('.ext-cal-day-times').getWidth(),\r | |
388 | viewBox = this.el.getBox(),\r | |
389 | daySize = this.getDaySize(false),\r | |
390 | relX = x - viewBox.x - xoffset,\r | |
391 | dayIndex = Math.floor(relX / daySize.width),\r | |
392 | // clicked col index\r | |
393 | scroll = this.el.getScroll(),\r | |
394 | row = this.el.down('.ext-cal-bg-row'),\r | |
395 | // first avail row, just to calc size\r | |
396 | rowH = row.getHeight() / 2,\r | |
397 | // 30 minute increment since a row is 60 minutes\r | |
398 | relY = y - viewBox.y - rowH + scroll.top,\r | |
399 | rowIndex = Math.max(0, Math.ceil(relY / rowH)),\r | |
400 | mins = rowIndex * 30,\r | |
401 | dt = Ext.calendar.util.Date.add(this.viewStart, {days: dayIndex, minutes: mins}),\r | |
402 | el = this.getDayEl(dt),\r | |
403 | timeX = x;\r | |
404 | \r | |
405 | if (el) {\r | |
406 | timeX = el.getX();\r | |
407 | }\r | |
408 | \r | |
409 | return {\r | |
410 | date: dt,\r | |
411 | el: el,\r | |
412 | // this is the box for the specific time block in the day that was clicked on:\r | |
413 | timeBox: {\r | |
414 | x: timeX,\r | |
415 | y: (rowIndex * 21) + viewBox.y - scroll.top,\r | |
416 | width: daySize.width,\r | |
417 | height: rowH\r | |
418 | }\r | |
419 | };\r | |
420 | },\r | |
421 | \r | |
422 | // private\r | |
423 | onClick: function(e, t) {\r | |
424 | if (this.dragPending || Ext.calendar.view.DayBody.superclass.onClick.apply(this, arguments)) {\r | |
425 | // The superclass handled the click already so exit\r | |
426 | return;\r | |
427 | }\r | |
428 | if (e.getTarget('.ext-cal-day-times', 3) !== null) {\r | |
429 | // ignore clicks on the times-of-day gutter\r | |
430 | return;\r | |
431 | }\r | |
432 | var el = e.getTarget('td', 3);\r | |
433 | if (el) {\r | |
434 | if (el.id && el.id.indexOf(this.dayElIdDelimiter) > -1) {\r | |
435 | var dt = this.getDateFromId(el.id, this.dayElIdDelimiter);\r | |
436 | this.fireEvent('dayclick', this, Ext.Date.parseDate(dt, 'Ymd'), true, Ext.get(this.getDayId(dt, true)));\r | |
437 | return;\r | |
438 | }\r | |
439 | }\r | |
440 | var day = this.getDayAt(e.getX(), e.getY());\r | |
441 | if (day && day.date) {\r | |
442 | this.fireEvent('dayclick', this, day.date, false, null);\r | |
443 | }\r | |
444 | }\r | |
445 | });\r |