Fix 24:00 again
[tine20] / tine20 / Calendar / js / DaysView.js
1 /* 
2  * Tine 2.0
3  * 
4  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
5  * @author      Cornelius Weiss <c.weiss@metaways.de>
6  * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
7  */
8
9 Ext.ns('Tine.Calendar');
10
11 /**
12  * @namespace   Tine.Calendar
13  * @class       Tine.Calendar.DaysView
14  * @extends     Ext.util.Observable
15  * Calendar view representing each day in a column
16  * @author      Cornelius Weiss <c.weiss@metaways.de>
17  * @constructor
18  * @param {Object} config
19  */
20 Tine.Calendar.DaysView = function(config){
21     Ext.apply(this, config);
22     Tine.Calendar.DaysView.superclass.constructor.call(this);
23     
24     this.printRenderer = Tine.Calendar.Printer.DaysViewRenderer;
25     
26     this.addEvents(
27         /**
28          * @event click
29          * fired if an event got clicked
30          * @param {Tine.Calendar.Model.Event} event
31          * @param {Ext.EventObject} e
32          */
33         'click',
34         /**
35          * @event contextmenu
36          * fired if an event got contextmenu 
37          * @param {Ext.EventObject} e
38          */
39         'contextmenu',
40         /**
41          * @event dblclick
42          * fired if an event got dblclicked
43          * @param {Tine.Calendar.Model.Event} event
44          * @param {Ext.EventObject} e
45          */
46         'dblclick',
47         /**
48          * @event changeView
49          * fired if user wants to change view
50          * @param {String} requested view name
51          * @param {mixed} start param of requested view
52          */
53         'changeView',
54         /**
55          * @event changePeriod
56          * fired when period changed
57          * @param {Object} period
58          */
59         'changePeriod',
60         /**
61          * @event addEvent
62          * fired when a new event got inserted
63          * 
64          * @param {Tine.Calendar.Model.Event} event
65          */
66         'addEvent',
67         /**
68          * @event updateEvent
69          * fired when an event go resised/moved
70          * 
71          * @param {Tine.Calendar.Model.Event} event
72          */
73         'updateEvent'
74     );
75 };
76
77 Ext.extend(Tine.Calendar.DaysView, Ext.Container, {
78     /**
79      * @cfg {Date} startDate
80      * start date
81      */
82     startDate: new Date(),
83     /**
84      * @cfg {Number} numOfDays
85      * number of days to display
86      */
87     numOfDays: 4,
88     /**
89      * @cfg {String} (H:i) dayStart
90      * generic start of the (work) day
91      */
92     dayStart: '08:00',
93     /**
94      * @cfg {String} (H:i) dayEnd
95      * generic end of the (work) day
96      */
97     dayEnd: '18:00',
98     /**
99      * @cfg {Bool} cropDayTime
100      * crop times before and after dayStart and dayEnd
101      */
102     cropDayTime: false,
103     /**
104      * @cfg {String} newEventSummary
105      * _('New Event')
106      */
107     newEventSummary: 'New Event',
108     /**
109      * @cfg {String} dayFormatString
110      * _('{0}, the {1}. of {2}')
111      */
112     dayFormatString: '{0}, the {1}. of {2}',
113     /**
114      * @cfg {Number} timeGranularity
115      * granularity of timegrid in minutes
116      */
117     timeGranularity: 30,
118     /**
119      * @cfg {Number} granularityUnitHeights
120      * heights in px of a granularity unit
121      */
122     granularityUnitHeights: 18,
123     /**
124      * @cfg {Boolean} denyDragOnMissingEditGrant
125      * deny drag action if edit grant for event is missing
126      */
127     denyDragOnMissingEditGrant: true,
128     /**
129      * store holding timescale
130      * @type {Ext.data.Store}
131      */
132     timeScale: null,
133     /**
134      * The amount of space to reserve for the scrollbar (defaults to 19 pixels)
135      * @type {Number}
136      */
137     scrollOffset: 19,
138
139     /**
140      * The time in milliseconds, a scroll should be delayed after using the mousewheel
141      * 
142      * @type Number
143      */
144     scrollBuffer: 200,
145     
146     /**
147      * @property {bool} editing
148      * @private
149      */
150     editing: false,
151     /**
152      * currently active event
153      * $type {Tine.Calendar.Model.Event}
154      */
155     activeEvent: null,
156     /**
157      * @property {Ext.data.Store}
158      * @private
159      */
160     ds: null,
161     
162     /**
163      * updates period to display
164      * @param {Array} period
165      */
166     updatePeriod: function(period) {
167         this.startDate = period.from;
168         
169         var tbar = this.findParentBy(function(c) {return c.getTopToolbar()}).getTopToolbar();
170         if (tbar) {
171             tbar.periodPicker.update(this.startDate);
172             this.startDate = tbar.periodPicker.getPeriod().from;
173         }
174         
175         this.endDate = this.startDate.add(Date.DAY, this.numOfDays+1);
176         
177         //this.parallelScrollerEventsRegistry = new Tine.Calendar.ParallelEventsRegistry({dtStart: this.startDate, dtEnd: this.endDate});
178         //this.parallelWholeDayEventsRegistry = new Tine.Calendar.ParallelEventsRegistry({dtStart: this.startDate, dtEnd: this.endDate});
179         //this.store.each(this.removeEvent, this);
180         
181         this.updateDayHeaders();
182         this.onBeforeScroll();
183         
184         this.fireEvent('changePeriod', period);
185     },
186     
187     /**
188      * init this view
189      */
190     initComponent: function() {
191         this.app = Tine.Tinebase.appMgr.get('Calendar');
192         
193         this.newEventSummary      =  this.app.i18n._hidden(this.newEventSummary);
194         this.dayFormatString      =  this.app.i18n._hidden(this.dayFormatString);
195         
196         this.startDate.setHours(0);
197         this.startDate.setMinutes(0);
198         this.startDate.setSeconds(0);
199         
200         this.endDate = this.startDate.add(Date.DAY, this.numOfDays+1);
201         
202         this.boxMinWidth = 60*this.numOfDays;
203         
204         this.parallelScrollerEventsRegistry = new Tine.Calendar.ParallelEventsRegistry({dtStart: this.startDate, dtEnd: this.endDate});
205         this.parallelWholeDayEventsRegistry = new Tine.Calendar.ParallelEventsRegistry({dtStart: this.startDate, dtEnd: this.endDate});
206         
207         this.initData(this.store);
208         
209         this.initTimeScale();
210         this.initTemplates();
211         
212         this.on('beforehide', this.onBeforeHide, this);
213         this.on('show', this.onShow, this);
214         
215         this.mon(Tine.Tinebase.appMgr, 'activate', this.onAppActivate, this);
216         
217         if (! this.selModel) {
218             this.selModel = this.selModel || new Tine.Calendar.EventSelectionModel();
219         }
220         
221         this.onLayout = Function.createBuffered(this.onLayout, 100, this);
222
223         // apply preferences
224         var prefs = this.app.getRegistry().get('preferences'),
225             startTime = Date.parseDate(prefs.get('daysviewstarttime'), 'H:i'),
226             endTime = Date.parseDate(prefs.get('daysviewendtime'), 'H:i');
227         
228         this.dayStart = Ext.isDate(startTime) ? startTime : Date.parseDate(this.dayStart, 'H:i');
229         this.dayEnd = Ext.isDate(endTime) ? endTime : Date.parseDate(this.dayEnd, 'H:i');
230         // 00:00 in users timezone is a spechial case where the user expects
231         // something like 24:00 and not 00:00
232         if (this.dayEnd.format('H:i') == '00:00') {
233             this.dayEnd = this.dayEnd.add(Date.MINUTE, -1);
234         }
235         this.dayEndPx = this.getTimeOffset(this.dayEnd);
236         
237         this.cropDayTime = !! Tine.Tinebase.configManager.get('daysviewcroptime', 'Calendar') && !(!this.getTimeOffset(this.dayStart) && !this.getTimeOffset(this.dayEnd));
238         
239         Tine.Calendar.DaysView.superclass.initComponent.apply(this, arguments);
240     },
241     
242     /**
243      * @private
244      * @param {Ext.data.Store} ds
245      */
246     initData : function(ds){
247         if(this.store){
248             this.store.un("load", this.onLoad, this);
249             this.store.un("beforeload", this.onBeforeLoad, this);
250             this.store.un("add", this.onAdd, this);
251             this.store.un("remove", this.onRemove, this);
252             this.store.un("update", this.onUpdate, this);
253         }
254         if(ds){
255             ds.on("load", this.onLoad, this);
256             ds.on("beforeload", this.onBeforeLoad, this);
257             ds.on("add", this.onAdd, this);
258             ds.on("remove", this.onRemove, this);
259             ds.on("update", this.onUpdate, this);
260         }
261         this.store = ds;
262     },
263     /**
264      * inits time scale
265      * @private
266      */
267     initTimeScale: function() {
268         var data = [];
269         var scaleSize = Date.msDAY/(this.timeGranularity * Date.msMINUTE);
270         var baseDate = this.startDate.clone();
271         
272         var minutes;
273         for (var i=0; i<scaleSize; i++) {
274             minutes = i * this.timeGranularity;
275             data.push([i, minutes, minutes * Date.msMINUTE, baseDate.add(Date.MINUTE, minutes).format('H:i')]);
276         }
277         
278         this.timeScale = new Ext.data.SimpleStore({
279             fields: ['index', 'minutes', 'milliseconds', 'time'],
280             data: data,
281             id: 'index'
282         });
283     },
284     
285     initDropZone: function() {
286         this.dd = new Ext.dd.DropZone(this.mainWrap.dom, {
287             ddGroup: 'cal-event',
288             
289             notifyOver : function(dd, e, data) {
290                 var sourceEl = Ext.fly(data.sourceEl);
291                 sourceEl.setStyle({'border-style': 'dashed'});
292                 sourceEl.setOpacity(0.5);
293                 
294                 if (data.event) {
295                     var event = data.event;
296                     
297                     var targetDateTime = Tine.Calendar.DaysView.prototype.getTargetDateTime.call(data.scope, e);
298                     if (targetDateTime) {
299                         var dtString = targetDateTime.format(targetDateTime.is_all_day_event ? Ext.form.DateField.prototype.format : 'H:i');
300                         if (! event.data.is_all_day_event) {
301                             Ext.fly(dd.proxy.el.query('div[class=cal-daysviewpanel-event-header-inner]')[0]).update(dtString);
302                         }
303                         
304                         if (event.get('editGrant')) {
305                             return Math.abs(targetDateTime.getTime() - event.get('dtstart').getTime()) < Date.msMINUTE ? 'cal-daysviewpanel-event-drop-nodrop' : 'cal-daysviewpanel-event-drop-ok';
306                         }
307                     }
308                 }
309                 
310                 return 'cal-daysviewpanel-event-drop-nodrop';
311             },
312             
313             notifyOut : function() {
314                 //console.log('notifyOut');
315                 //delete this.grid;
316             },
317             
318             notifyDrop : function(dd, e, data) {
319                 var v = data.scope;
320                 
321                 var targetDate = v.getTargetDateTime(e);
322                 
323                 if (targetDate) {
324                     var event = data.event;
325                     
326                     var originalDuration = (event.get('dtend').getTime() - event.get('dtstart').getTime()) / Date.msMINUTE;
327                     
328                     // Get the new endDate to ensure it's not out of croptimes
329                     var copyTargetDate = targetDate;
330                     var newEnd = copyTargetDate.add(Date.MINUTE, originalDuration);
331                     
332                     v.dayEnd.setDate(newEnd.getDate());
333                        
334                     // deny drop for missing edit grant or no time change
335                     if (! event.get('editGrant') || Math.abs(targetDate.getTime() - event.get('dtstart').getTime()) < Date.msMINUTE
336                             || ((v.cropDayTime == true) && (newEnd > v.dayEnd))) {
337                         return false;
338                     }
339                     
340                     event.beginEdit();
341                     
342                     event.set('dtstart', targetDate);
343                     
344                     if (! event.get('is_all_day_event') && targetDate.is_all_day_event && event.duration < Date.msDAY) {
345                         // draged from scroller -> dropped to allDay and duration less than a day
346                         event.set('dtend', targetDate.add(Date.DAY, 1).add(Date.SECOND, -1));
347                     } else if (event.get('is_all_day_event') && !targetDate.is_all_day_event) {
348                         // draged from allDay -> droped to scroller will be resetted to hone hour
349                         event.set('dtend', targetDate.add(Date.HOUR, 1));
350                     } else {
351                         event.set('dtend', targetDate.add(Date.MINUTE, originalDuration));
352                     }
353                     
354                     event.set('is_all_day_event', targetDate.is_all_day_event);
355                     event.endEdit();
356                     
357                     v.fireEvent('updateEvent', event);
358                 }
359                 
360                 return !!targetDate;
361             }
362         });
363     },
364     
365     /**
366      * @private
367      */
368     initDragZone: function() {
369         this.scroller.ddScrollConfig = {
370             vthresh: this.granularityUnitHeights * 2,
371             increment: this.granularityUnitHeights * 4,
372             hthresh: -1,
373             frequency: 500
374         };
375         Ext.dd.ScrollManager.register(this.scroller);
376         
377         // init dragables
378         this.dragZone = new Ext.dd.DragZone(this.el, {
379             ddGroup: 'cal-event',
380             view: this,
381             scroll: false,
382             containerScroll: true,
383             
384             getDragData: function(e) {
385                 var selected = this.view.getSelectionModel().getSelectedEvents();
386                 
387                 var eventEl = e.getTarget('div.cal-daysviewpanel-event', 10);
388                 if (eventEl) {
389                     var parts = eventEl.id.split(':');
390                     var event = this.view.store.getById(parts[1]);
391                     
392                     // don't allow dragging of dirty events
393                     // don't allow dragging with missing edit grant
394                     if (! event || event.dirty || (this.view.denyDragOnMissingEditGrant && ! event.get('editGrant'))) {
395                         return;
396                     }
397                     
398                     // we need to clone an event with summary in
399                     var d = Ext.get(event.ui.domIds[0]).dom.cloneNode(true);
400                     d.id = Ext.id();
401                     
402                     if (event.get('is_all_day_event')) {
403                         Ext.fly(d).setLeft(0);
404                     } else {
405                         var width = (Ext.fly(this.view.dayCols[0]).getWidth() * 0.9);
406                         Ext.fly(d).setTop(0);
407                         Ext.fly(d).setWidth(width);
408                         Ext.fly(d).setHeight(this.view.getTimeHeight.call(this.view, event.get('dtstart'), event.get('dtend')));
409                     }
410                     
411                     return {
412                         scope: this.view,
413                         sourceEl: eventEl,
414                         event: event,
415                         ddel: d,
416                         selections: this.view.getSelectionModel().getSelectedEvents()
417                     }
418                 }
419             },
420             
421             getRepairXY: function(e, dd) {
422                 Ext.fly(this.dragData.sourceEl).setStyle({'border-style': 'solid'});
423                 Ext.fly(this.dragData.sourceEl).setOpacity(1, 1);
424                 
425                 return Ext.fly(this.dragData.sourceEl).getXY();
426             }
427         });
428     },
429     
430     /**
431      * renders the view
432      */
433     onRender: function(container, position) {
434         Tine.Calendar.DaysView.superclass.onRender.apply(this, arguments);
435         
436         this.templates.master.append(this.el.dom, {
437             header: this.templates.header.applyTemplate({
438                 daysHeader: this.getDayHeaders(),
439                 wholeDayCols: this.getWholeDayCols()
440             }),
441             body: this.templates.body.applyTemplate({
442                 timeRows: this.getTimeRows(),
443                 dayColumns: this.getDayColumns()
444             })
445         });
446         
447         this.initElements();
448         this.getSelectionModel().init(this);
449     },
450     
451     /**
452      * fill the events into the view
453      */
454     afterRender: function() {
455         Tine.Calendar.DaysView.superclass.afterRender.apply(this, arguments);
456         
457         this.mon(this.el, 'click', this.onClick, this);
458         this.mon(this.el, 'dblclick', this.onDblClick, this);
459         this.mon(this.el, 'contextmenu', this.onContextMenu, this);
460         this.mon(this.el, 'mousedown', this.onMouseDown, this);
461         this.mon(this.el, 'mouseup', this.onMouseUp, this);
462         this.mon(this.el, 'keydown', this.onKeyDown, this);
463         
464         this.initDropZone();
465         this.initDragZone();
466         
467         this.updatePeriod({from: this.startDate});
468         
469         if (this.store.getCount()) {
470             this.onLoad.apply(this);
471         }
472         
473         // apply os specific scrolling space
474         Ext.fly(this.innerHd.firstChild.firstChild).setStyle('margin-right', Ext.getScrollBarWidth() + 'px');
475         
476         // crop daytime
477         if (this.cropDayTime) {
478             var cropStartPx = this.getTimeOffset(this.dayStart),
479                 cropHeightPx = this.getTimeOffset(this.dayEnd) +2;
480                 
481             this.mainBody.setStyle('margin-top', '-' + cropStartPx + 'px');
482             this.mainBody.setStyle('height', cropHeightPx + 'px');
483             this.mainBody.setStyle('overflow', 'hidden');
484             this.scroller.addClass('cal-daysviewpanel-body-cropDayTime');
485         }
486         
487         // scrollTo initial position
488         this.isScrolling = true;
489         try {
490             this.scrollTo(this.dayStart);
491         } catch (e) {
492             this.scrollTo();
493         }
494         
495         this.onLayout();
496         this.rendered = true;
497     },
498     
499     scrollTo: function(time) {
500         time = Ext.isDate(time) ? time : new Date();
501         this.scroller.dom.scrollTop = this.getTimeOffset(time);
502     },
503     
504     onBeforeScroll: function() {
505         if (! this.isScrolling) {
506             this.isScrolling = true;
507             
508             // walk all cols an hide hints
509             Ext.each(this.dayCols, function(dayCol, idx) {
510                 this.aboveHints.item(idx).setDisplayed(false);
511                 this.belowHints.item(idx).setDisplayed(false);
512             }, this);
513         }
514     },
515     
516     /**
517      * add hint if events are outside visible area
518      * 
519      * @param {} e
520      * @param {} t
521      * @param {} o
522      */
523     onScroll: function(e, t, o) {
524         var visibleHeight = this.scroller.dom.clientHeight,
525             visibleStart  = this.scroller.dom.scrollTop - this.mainBody.dom.offsetTop,
526             visibleEnd    = visibleStart + visibleHeight,
527             vStartMinutes = this.getHeightMinutes(visibleStart),
528             vEndMinutes   = this.getHeightMinutes(visibleEnd);
529             
530         
531         Ext.each(this.dayCols, function(dayCol, idx) {
532             var dayColEl    = Ext.get(dayCol),
533                 dayStart    = this.startDate.add(Date.DAY, idx),
534                 aboveEvents = this.parallelScrollerEventsRegistry.getEvents(dayStart, dayStart.add(Date.MINUTE, vStartMinutes)),
535                 belowEvents = this.parallelScrollerEventsRegistry.getEvents(dayStart.add(Date.MINUTE, vEndMinutes), dayStart.add(Date.DAY, 1));
536                 
537             if (aboveEvents.length) {
538                 var aboveHint = this.aboveHints.item(idx);
539                 aboveHint.setTop(visibleStart + 5);
540                 if (!aboveHint.isVisible()) {
541                     aboveHint.fadeIn({duration: 1.6});
542                 }
543             }
544             
545             if (belowEvents.length) {
546                 var belowHint = this.belowHints.item(idx);
547                 belowHint.setTop(visibleEnd - 14);
548                 if (!belowHint.isVisible()) {
549                     belowHint.fadeIn({duration: 1.6});
550                 }
551             }
552         }, this);
553         
554         this.isScrolling = false;
555     },
556     
557     onShow: function() {
558         this.onLayout();
559         this.scroller.dom.scrollTop = this.lastScrollPos || this.getTimeOffset(new Date());
560     },
561     
562     onBeforeHide: function() {
563         this.lastScrollPos = this.scroller.dom.scrollTop;
564     },
565     
566     /**
567      * renders a single event into this daysview
568      * @param {Tine.Calendar.Model.Event} event
569      * 
570      * @todo Add support vor Events spanning over a day boundary
571      */
572     insertEvent: function(event) {
573         event.ui = new Tine.Calendar.DaysViewEventUI(event);
574         event.ui.render(this);
575     },
576     
577     /**
578      * removes all events
579      */
580     removeAllEvents: function() {
581         this.store.each(function(event) {
582             if (event.ui) {
583                 event.ui.remove();
584             }
585         });
586     },
587     
588     /**
589      * removes a event from the dom
590      * @param {Tine.Calendar.Model.Event} event
591      */
592     removeEvent: function(event) {
593         if (event == this.activeEvent) {
594             this.activeEvent = null;
595         }
596
597         if(this.editing) {
598             this.abortCreateEvent(event);
599         }
600
601         if (event.ui) {
602             event.ui.remove();
603         }
604     },
605     
606     /**
607      * sets currentlcy active event
608      * 
609      * NOTE: active != selected
610      * @param {Tine.Calendar.Model.Event} event
611      */
612     setActiveEvent: function(event) {
613         this.activeEvent = event || null;
614     },
615     
616     /**
617      * gets currentlcy active event
618      * 
619      * @return {Tine.Calendar.Model.Event} event
620      */
621     getActiveEvent: function() {
622         return this.activeEvent;
623     },
624     
625     /**
626      * returns the selectionModel of the active panel
627      * @return {}
628      */
629     getSelectionModel: function() {
630         return this.selModel;
631     },
632     
633     /**
634      * creates a new event directly from this view
635      * @param {} event
636      */
637     createEvent: function(e, event) {
638         
639         // only add range events if mouse is down long enough
640         if (this.editing || (event.isRangeAdd && ! this.mouseDown)) {
641             return;
642         }
643         
644         // insert event silently into store
645         this.editing = event;
646         this.store.suspendEvents();
647         this.store.add(event);
648         this.store.resumeEvents();
649         
650         // draw event
651         var registry = event.get('is_all_day_event') ? this.parallelWholeDayEventsRegistry : this.parallelScrollerEventsRegistry;
652         registry.register(event);
653         this.insertEvent(event);
654         //this.setActiveEvent(event);
655         this.onLayout();
656         
657         //var eventEls = event.ui.getEls();
658         //eventEls[0].setStyle({'border-style': 'dashed'});
659         //eventEls[0].setOpacity(0.5);
660         
661         // start sizing for range adds
662         if (event.isRangeAdd) {
663             // don't create events with very small duration
664             event.ui.resizeable.on('resize', function() {
665                 if (event.get('is_all_day_event')) {
666                     var keep = true;
667                 } else {
668                     var keep = (event.get('dtend').getTime() - event.get('dtstart').getTime()) / Date.msMINUTE >= this.timeGranularity;
669                 }
670                 
671                 if (keep) {
672                     this.startEditSummary(event);
673                 } else {
674                     this.abortCreateEvent(event);
675                 }
676             }, this);
677             
678             var rzPos = event.get('is_all_day_event') ? 'east' : 'south';
679             
680             if (Ext.isIE) {
681                 e.browserEvent = {type: 'mousedown'};
682             }
683             
684             event.ui.resizeable[rzPos].onMouseDown.call(event.ui.resizeable[rzPos], e);
685             //event.ui.resizeable.startSizing.defer(2000, event.ui.resizeable, [e, event.ui.resizeable[rzPos]]);
686         } else {
687             this.startEditSummary(event);
688         }
689     },
690     
691     abortCreateEvent: function(event) {
692         this.store.remove(event);
693         this.editing = false;
694     },
695     
696     startEditSummary: function(event) {
697         if (event.summaryEditor) {
698             return false;
699         }
700         
701         var eventEls = event.ui.getEls();
702         
703         var bodyCls = event.get('is_all_day_event') ? 'cal-daysviewpanel-wholedayevent-body' : 'cal-daysviewpanel-event-body';
704         event.summaryEditor = new Ext.form.TextArea({
705             event: event,
706             renderTo: eventEls[0].down('div[class=' + bodyCls + ']'),
707             width: event.ui.getEls()[0].getWidth() -12,
708             height: Math.max(12, event.ui.getEls()[0].getHeight() -18),
709             style: 'background-color: transparent; background: 0: border: 0; position: absolute; top: 0px;',
710             value: this.newEventSummary,
711             maxLength: 255,
712             maxLengthText: this.app.i18n._('The summary must not be longer than 255 characters.'),
713             minLength: 1,
714             minLengthText: this.app.i18n._('The summary must have at least 1 character.'),
715             enableKeyEvents: true,
716             listeners: {
717                 scope: this,
718                 render: function(field) {
719                     field.focus(true, 100);
720                 },
721                 blur: this.endEditSummary,
722                 specialkey: this.endEditSummary,
723                 keydown: this.endEditSummary
724             }
725             
726         });
727     },
728     
729     endEditSummary: function(field, e) {
730         var event   = field.event;
731         var summary = field.getValue();
732
733         if (! this.editing || this.validateMsg || !Ext.isDefined(e)) {
734             return;
735         }
736
737         // abort edit on ESC key
738         if (e && (e.getKey() == e.ESC)) {
739             this.abortCreateEvent(event);
740             return;
741         }
742
743         // only commit edit on Enter & blur
744         if (e && e.getKey() != e.ENTER) {
745             return;
746         }
747         
748         // Validate Summary maxLength
749         if (summary.length > field.maxLength) {
750             field.markInvalid();
751             this.validateMsg = Ext.Msg.alert(this.app.i18n._('Summary too Long'), field.maxLengthText, function(){
752                 field.focus();
753                 this.validateMsg = false;
754                 }, this);
755             return;
756         }
757
758         // Validate Summary minLength
759         if (!summary || summary.match(/^\s{1,}$/) || summary.length < field.minLength) {
760             field.markInvalid();
761             this.validateMsg = Ext.Msg.alert(this.app.i18n._('Summary too Short'), field.minLengthText, function(){
762                 field.focus();
763                 this.validateMsg = false;
764                 }, this);
765             return;
766         }
767
768         this.editing = false;
769         event.summaryEditor = false;
770         
771         event.set('summary', summary);
772         
773         this.store.suspendEvents();
774         this.store.remove(event);
775         this.store.resumeEvents();
776         
777         var registry = event.get('is_all_day_event') ? this.parallelWholeDayEventsRegistry : this.parallelScrollerEventsRegistry;
778         registry.unregister(event);
779         this.removeEvent(event);
780         
781         event.dirty = true;
782         this.store.add(event);
783         this.fireEvent('addEvent', event);
784     },
785     
786     onAppActivate: function(app) {
787         if (app === this.app) {
788             this.redrawWholeDayEvents();
789         }
790     },
791     
792     onResize: function() {
793         Tine.Calendar.DaysView.superclass.onResize.apply(this, arguments);
794         
795         this.updateDayHeaders();
796         this.redrawWholeDayEvents.defer(50, this);
797     },
798     
799     redrawWholeDayEvents: function() {
800         this.store.each(function(event) {
801             if (event.ui && event.get('is_all_day_event')) {
802                 this.removeEvent(event);
803                 this.insertEvent(event);
804             }
805         }, this);
806     },
807     
808     onClick: function(e) {
809         // check for hint clicks first
810         var hint = e.getTarget('img[class^=cal-daysviewpanel-body-daycolumn-hint-]', 10, true);
811         if (hint) {
812             this.scroller.scroll(hint.hasClass('cal-daysviewpanel-body-daycolumn-hint-above') ? 't' : 'b', 10000, true);
813             return;
814         }
815         
816         var event = this.getTargetEvent(e);
817         if (event) {
818             this.fireEvent('click', event, e);
819         }
820     },
821     
822     onContextMenu: function(e) {
823         this.fireEvent('contextmenu', e);
824     },
825     
826     onKeyDown : function(e){
827         this.fireEvent("keydown", e);
828     },
829     
830     /**
831      * @private
832      */
833     onDblClick: function(e, target) {
834         e.stopEvent();
835         var event = this.getTargetEvent(e);
836         var dtStart = this.getTargetDateTime(e);
837         
838         if (event) {
839             this.fireEvent('dblclick', event, e);
840         } else if (dtStart && !this.editing) {
841             var newId = 'cal-daysviewpanel-new-' + Ext.id();
842             var dtend = dtStart.add(Date.HOUR, 1);
843             if (dtStart.is_all_day_event) {
844                 dtend = dtend.add(Date.HOUR, 23).add(Date.SECOND, -1);
845             }
846             
847             // do not create an event exceeding the crop day time limit
848             else if (this.cropDayTime) {
849                 var format = 'Hms';
850                 if (dtStart.format(format) >= this.dayEnd.format(format)) {
851                     return false;
852                 }
853                 
854                 if (dtend.format(format) >= this.dayEnd.format(format)) {
855                     dtend.setHours(this.dayEnd.getHours());
856                     dtend.setMinutes(this.dayEnd.getMinutes());
857                     dtend.setSeconds(this.dayEnd.getSeconds());
858                 }
859             }
860             
861             var event = new Tine.Calendar.Model.Event(Ext.apply(Tine.Calendar.Model.Event.getDefaultData(), {
862                 id: newId,
863                 dtstart: dtStart, 
864                 dtend: dtend,
865                 is_all_day_event: dtStart.is_all_day_event
866             }), newId);
867             
868             this.createEvent(e, event);
869             event.dirty = true;
870         } else if (target.className == 'cal-daysviewpanel-dayheader-day'){
871             var dayHeaders = Ext.DomQuery.select('div[class=cal-daysviewpanel-dayheader-day]', this.innerHd);
872             var date = this.startDate.add(Date.DAY, dayHeaders.indexOf(target));
873             this.fireEvent('changeView', 'day', date);
874         }
875     },
876     
877     /**
878      * @private
879      */
880     onMouseDown: function(e) {
881         // only care for left mouse button
882         if (e.button !== 0) {
883             return;
884         }
885         
886         if (! this.editing) {
887             this.focusEl.focus();
888         }
889         this.mouseDown = true;
890         
891         var targetEvent = this.getTargetEvent(e);
892         if (this.editing && this.editing.summaryEditor && (targetEvent != this.editing)) {
893             this.editing.summaryEditor.fireEvent('blur', this.editing.summaryEditor, null);
894             return;
895         }
896
897         var sm = this.getSelectionModel();
898         sm.select(targetEvent);
899         
900         var dtStart = this.getTargetDateTime(e);
901         if (dtStart) {
902             var newId = 'cal-daysviewpanel-new-' + Ext.id();
903             var event = new Tine.Calendar.Model.Event(Ext.apply(Tine.Calendar.Model.Event.getDefaultData(), {
904                 id: newId,
905                 dtstart: dtStart, 
906                 dtend: dtStart.is_all_day_event ? dtStart.add(Date.HOUR, 24).add(Date.SECOND, -1) : dtStart.add(Date.MINUTE, 2*this.timeGranularity/2),
907                 is_all_day_event: dtStart.is_all_day_event
908             }), newId);
909             event.isRangeAdd = true;
910             event.dirty = true;
911             
912             e.stopEvent();
913             this.createEvent.defer(100, this, [e, event]);
914         }
915     },
916     
917     /**
918      * @private
919      */
920     onMouseUp: function() {
921         this.mouseDown = false;
922     },
923     
924     /**
925      * @private
926      */
927     onBeforeEventResize: function(rz, e) {
928         var parts = rz.el.id.split(':');
929         var event = this.store.getById(parts[1]);
930         
931         rz.event = event;
932         rz.originalHeight = rz.el.getHeight();
933         rz.originalWidth  = rz.el.getWidth();
934
935         // NOTE: ext dosn't support move events via api
936         rz.onMouseMove = rz.onMouseMove.createSequence(function() {
937             var event = this.event;
938             if (! event) {
939                 //event already gone -> late event / busy brower?
940                 return;
941             }
942             var ui = event.ui;
943             var rzInfo = ui.getRzInfo(this);
944             
945             this.durationEl.update(rzInfo.dtend.format(event.get('is_all_day_event') ? Ext.form.DateField.prototype.format : 'H:i'));
946         }, rz);
947         
948         event.ui.markDirty();
949         
950         // NOTE: Ext keeps proxy if element is not destroyed (diff !=0)
951         if (! rz.durationEl) {
952             rz.durationEl = rz.el.insertFirst({
953                 'class': 'cal-daysviewpanel-event-rzduration',
954                 'style': 'position: absolute; bottom: 3px; right: 2px; z-index: 1000;'
955             });
956         }
957         rz.durationEl.update(event.get('dtend').format(event.get('is_all_day_event') ? Ext.form.DateField.prototype.format : 'H:i'));
958         
959         if (event) {
960             this.getSelectionModel().select(event);
961         } else {
962             this.getSelectionModel().clearSelections();
963         }
964     },
965     
966     /**
967      * @private
968      */
969     onEventResize: function(rz, width, height) {
970         var event = rz.event;
971         
972         if (! event) {
973             //event already gone -> late event / busy brower?
974             return;
975         }
976         
977         var rzInfo = event.ui.getRzInfo(rz, width, height);
978         
979         if (rzInfo.diff != 0) {
980             if (rzInfo.duration > 0) {
981                 event.set('dtend', rzInfo.dtend);
982             } else {
983                 // force event length to at least 1 minute
984                 var date = new Date(event.get('dtstart').getTime());
985                 date.setMinutes(date.getMinutes() + 1);
986                 event.set('dtend', date);
987             }
988         }
989         
990         if (event.summaryEditor) {
991             event.summaryEditor.setHeight(event.ui.getEls()[0].getHeight() -18);
992         }
993         
994         // don't fire update events on rangeAdd
995         if (rzInfo.diff != 0 && event != this.editing && ! event.isRangeAdd) {
996             this.fireEvent('updateEvent', event);
997         } else {
998             event.ui.clearDirty();
999         }
1000     },
1001     
1002     /**
1003      * @private
1004      */
1005     onUpdate : function(ds, event){
1006         // don't update events while being created
1007         if (event.get('id').match(/new/)) {
1008             return;
1009         }
1010         
1011         // relayout original context
1012         var originalRegistry = (event.modified.hasOwnProperty('is_all_day_event') ? event.modified.is_all_day_event : event.get('is_all_day_event')) ? 
1013             this.parallelWholeDayEventsRegistry : 
1014             this.parallelScrollerEventsRegistry;
1015
1016         var registry = event.get('is_all_day_event') ? this.parallelWholeDayEventsRegistry : this.parallelScrollerEventsRegistry;
1017         var originalDtstart = event.modified.hasOwnProperty('dtstart') ? event.modified.dtstart : event.get('dtstart');
1018         var originalDtend = event.modified.hasOwnProperty('dtend') ? event.modified.dtend : event.get('dtend');
1019
1020         var originalParallels = originalRegistry.getEvents(originalDtstart, originalDtend);
1021         for (var j=0; j<originalParallels.length; j++) {
1022             this.removeEvent(originalParallels[j]);
1023         }
1024         originalRegistry.unregister(event);
1025         
1026         var originalParallels = originalRegistry.getEvents(originalDtstart, originalDtend);
1027         for (var j=0; j<originalParallels.length; j++) {
1028             this.insertEvent(originalParallels[j]);
1029         }
1030         
1031         // relayout actual context
1032         var parallelEvents = registry.getEvents(event.get('dtstart'), event.get('dtend'));
1033         for (var j=0; j<parallelEvents.length; j++) {
1034             this.removeEvent(parallelEvents[j]);
1035         }
1036         
1037         registry.register(event);
1038         var parallelEvents = registry.getEvents(event.get('dtstart'), event.get('dtend'));
1039         for (var j=0; j<parallelEvents.length; j++) {
1040             this.insertEvent(parallelEvents[j]);
1041         }
1042         
1043         this.setActiveEvent(this.getActiveEvent());
1044         this.onLayout();
1045     },
1046
1047     /**
1048      * @private
1049      */
1050     onAdd : function(ds, records, index){
1051         //console.log('onAdd');
1052         for (var i=0; i<records.length; i++) {
1053             var event = records[i];
1054             
1055             var registry = event.get('is_all_day_event') ? this.parallelWholeDayEventsRegistry : this.parallelScrollerEventsRegistry;
1056             registry.register(event);
1057             
1058             var parallelEvents = registry.getEvents(event.get('dtstart'), event.get('dtend'));
1059             
1060             for (var j=0; j<parallelEvents.length; j++) {
1061                 this.removeEvent(parallelEvents[j]);
1062                 this.insertEvent(parallelEvents[j]);
1063             }
1064             
1065             //this.setActiveEvent(event);
1066         }
1067         
1068         this.onLayout();
1069     },
1070
1071     /**
1072      * @private
1073      */
1074     onRemove : function(ds, event, index, isUpdate) {
1075         if (!event || index == -1) {
1076             return;
1077         }
1078         
1079         if(isUpdate !== true){
1080             //this.fireEvent("beforeeventremoved", this, index, record);
1081         }
1082         var registry = event.get('is_all_day_event') ? this.parallelWholeDayEventsRegistry : this.parallelScrollerEventsRegistry;
1083         registry.unregister(event);
1084         this.removeEvent(event);
1085         this.getSelectionModel().unselect(event);
1086         this.onLayout();
1087     },
1088     
1089     onBeforeLoad: function(store, options) {
1090         if (! options.refresh) {
1091             this.store.each(this.removeEvent, this);
1092             this.transitionEvents = [];
1093         } else {
1094             this.transitionEvents = this.store.data.items;
1095         }
1096     },
1097     
1098     /**
1099      * @private
1100      */
1101     onLoad : function() {
1102         if(! this.rendered){
1103             return;
1104         }
1105         
1106         // remove old events
1107         Ext.each(this.transitionEvents, this.removeEvent, this);
1108         
1109         // setup registry
1110         this.parallelScrollerEventsRegistry = new Tine.Calendar.ParallelEventsRegistry({dtStart: this.startDate, dtEnd: this.endDate});
1111         this.parallelWholeDayEventsRegistry = new Tine.Calendar.ParallelEventsRegistry({dtStart: this.startDate, dtEnd: this.endDate});
1112         
1113         // todo: sort generic?
1114         this.store.fields = Tine.Calendar.Model.Event.prototype.fields;
1115         this.store.sortInfo = {field: 'dtstart', direction: 'ASC'};
1116         this.store.applySort();
1117         
1118         this.store.each(function(event) {
1119             var registry = event.get('is_all_day_event') ? this.parallelWholeDayEventsRegistry : this.parallelScrollerEventsRegistry;
1120             registry.register(event);
1121         }, this);
1122         
1123         // put the events in
1124         this.store.each(this.insertEvent, this);
1125         
1126         this.onLayout();
1127     },
1128     
1129     /**
1130      * print wrapper
1131      */
1132     print: function(printMode) {
1133         var renderer = new this.printRenderer({printMode: printMode});
1134         renderer.print(this);
1135     },
1136     
1137     hex2dec: function(hex) {
1138         var dec = 0;
1139         hex = hex.toString();
1140         var length = hex.length, multiplier, digit;
1141         for (var i=0; i<length; i++) {
1142             
1143             multiplier = Math.pow(16, (Math.abs(i - hex.length)-1));
1144             digit = parseInt(hex.toString().charAt([i]), 10);
1145             if (isNaN(digit)) {
1146                 switch (hex.toString().charAt([i]).toUpperCase()) {
1147                     case 'A': digit = 10;  break;
1148                     case 'B': digit = 11;  break;
1149                     case 'C': digit = 12;  break;
1150                     case 'D': digit = 13;  break;
1151                     case 'E': digit = 14;  break;
1152                     case 'F': digit = 15;  break;
1153                     default: return NaN;
1154                 }
1155             }
1156             dec = dec + (multiplier * digit);
1157         }
1158         
1159         return dec;
1160     },
1161     
1162     getPeriod: function() {
1163         return {
1164             from: this.startDate,
1165             until: this.startDate.add(Date.DAY, this.numOfDays)
1166         };
1167     },
1168     
1169     /**
1170      * get date of a (event) target
1171      * 
1172      * @param {Ext.EventObject} e
1173      * @return {Date}
1174      */
1175     getTargetDateTime: function(e) {
1176         var target = e.getTarget('div[class^=cal-daysviewpanel-datetime]');
1177         
1178         if (target && target.id.match(/^ext-gen\d+:\d+/)) {
1179             var parts = target.id.split(':');
1180             
1181             var date = this.startDate.add(Date.DAY, parseInt(parts[1], 10));
1182             date.is_all_day_event = true;
1183             
1184             if (parts[2] ) {
1185                 var timePart = this.timeScale.getAt(parts[2]);
1186                 date = date.add(Date.MINUTE, timePart.get('minutes'));
1187                 date.is_all_day_event = false;
1188             }
1189             
1190             return date;
1191         }
1192     },
1193     
1194     /**
1195      * gets event el of target
1196      * 
1197      * @param {Ext.EventObject} e
1198      * @return {Tine.Calendar.Model.Event}
1199      */
1200     getTargetEvent: function(e) {
1201         var target = e.getTarget();
1202         var el = Ext.fly(target);
1203         
1204         if (el.hasClass('cal-daysviewpanel-event') || (el = el.up('[id*=event:]', 10))) {
1205             var parts = el.dom.id.split(':');
1206             
1207             return this.store.getById(parts[1]);
1208         }
1209     },
1210     
1211     getTimeOffset: function(date) {
1212         var d = this.granularityUnitHeights / this.timeGranularity;
1213         
1214         return Math.round(d * ( 60 * date.getHours() + date.getMinutes()));
1215     },
1216     
1217     getTimeHeight: function(dtStart, dtEnd) {
1218         var d = this.granularityUnitHeights / this.timeGranularity;
1219         return Math.round(d * ((dtEnd.getTime() - dtStart.getTime()) / Date.msMINUTE));
1220     },
1221     
1222     getHeightMinutes: function(height) {
1223         return Math.round(height * this.timeGranularity / this.granularityUnitHeights);
1224     },
1225     
1226     /**
1227      * fetches elements from our generated dom
1228      */
1229     initElements : function(){
1230         var E = Ext.Element;
1231
1232 //        var el = this.el.dom.firstChild;
1233         var cs = this.el.dom.firstChild.childNodes;
1234
1235 //        this.el = new E(el);
1236
1237         this.mainWrap = new E(cs[0]);
1238         this.mainHd = new E(this.mainWrap.dom.firstChild);
1239
1240         this.innerHd = this.mainHd.dom.firstChild;
1241         
1242         this.wholeDayScroller = this.innerHd.firstChild.childNodes[1];
1243         this.wholeDayArea = this.wholeDayScroller.firstChild;
1244         
1245         this.scroller = new E(this.mainWrap.dom.childNodes[1]);
1246         this.scroller.setStyle('overflow-x', 'hidden');
1247         this.mon(this.scroller, 'scroll', this.onBeforeScroll, this);
1248         this.mon(this.scroller, 'scroll', this.onScroll, this, {buffer: 200});
1249         
1250         this.mainBody = new E(this.scroller.dom.firstChild);
1251         this.dayCols = this.mainBody.dom.firstChild.lastChild.childNodes;
1252
1253         this.focusEl = new E(this.el.dom.lastChild.lastChild);
1254         this.focusEl.swallowEvent("click", true);
1255         this.focusEl.swallowEvent("dblclick", true);
1256         this.focusEl.swallowEvent("contextmenu", true);
1257         
1258         this.aboveHints   = this.mainBody.select('img[class=cal-daysviewpanel-body-daycolumn-hint-above]');
1259         this.belowHints   = this.mainBody.select('img[class=cal-daysviewpanel-body-daycolumn-hint-below]');
1260     },
1261     
1262     /**
1263      * @TODO this returns wrong cols on DST boundaries:
1264      *  e.g. on DST switch form +2 to +1 an all day event is 25 hrs. long
1265      * 
1266      * @param {} date
1267      * @return {}
1268      */
1269     getColumnNumber: function(date) {
1270         return Math.floor((date.add(Date.SECOND, 1).getTime() - this.startDate.getTime()) / Date.msDAY);
1271     },
1272     
1273     getDateColumnEl: function(pos) {
1274         return this.dayCols[pos];
1275     },
1276     
1277     checkWholeDayEls: function() {
1278         var freeIdxs = [];
1279         for (var i=0; i<this.wholeDayArea.childNodes.length-1; i++) {
1280             if(this.wholeDayArea.childNodes[i].childNodes.length === 1) {
1281                 freeIdxs.push(i);
1282             }
1283         }
1284         
1285         for (var i=1; i<freeIdxs.length; i++) {
1286             Ext.fly(this.wholeDayArea.childNodes[freeIdxs[i]]).remove();
1287         }
1288     },
1289     
1290     /**
1291      * layouts the view
1292      */
1293     onLayout: function() {
1294         Tine.Calendar.DaysView.superclass.onLayout.apply(this, arguments);
1295         if(!this.mainBody){
1296             return; // not rendered
1297         }
1298         
1299         var csize = this.container.getSize(true);
1300         var vw = csize.width;
1301         
1302         this.el.setSize(csize.width, csize.height);
1303         
1304         // layout whole day area
1305         var wholeDayAreaEl = Ext.get(this.wholeDayArea);
1306         for (var i=0, bottom = wholeDayAreaEl.getTop(); i<this.wholeDayArea.childNodes.length -1; i++) {
1307             bottom = Math.max(parseInt(Ext.get(this.wholeDayArea.childNodes[i]).getBottom(), 10), bottom);
1308         }
1309         var wholeDayAreaHeight = bottom - wholeDayAreaEl.getTop() + 10;
1310         // take one third of the available height maximum
1311         wholeDayAreaEl.setHeight(wholeDayAreaHeight);
1312         Ext.fly(this.wholeDayScroller).setHeight(Math.min(Math.round(csize.height/3), wholeDayAreaHeight));
1313         
1314         var hdHeight = this.mainHd.getHeight();
1315         var vh = csize.height - (hdHeight);
1316         
1317         this.scroller.setSize(vw, vh);
1318         
1319         // force positioning on scroll hints
1320         this.onScroll.defer(100, this);
1321     },
1322     
1323     onDestroy: function() {
1324         this.removeAllEvents();
1325         this.initData(false);
1326         this.purgeListeners();
1327         
1328         Tine.Calendar.DaysView.superclass.onDestroy.apply(this, arguments);
1329     },
1330     
1331     /**
1332      * returns HTML frament of the day headers
1333      */
1334     getDayHeaders: function() {
1335         var html = '';
1336         var width = 100/this.numOfDays;
1337         
1338         for (var i=0, date; i<this.numOfDays; i++) {
1339             var day = this.startDate.add(Date.DAY, i);
1340             html += this.templates.dayHeader.applyTemplate({
1341                 day: String.format(this.dayFormatString, day.format('l'), day.format('j'), day.format('F')),
1342                 height: this.granularityUnitHeights,
1343                 width: width + '%',
1344                 left: i * width + '%'
1345             });
1346         }
1347         return html;
1348     },
1349     
1350     /**
1351      * updates HTML of day headers
1352      */
1353     updateDayHeaders: function() {
1354         if (! this.rendered) {
1355             return;
1356         }
1357         var dayHeaders = Ext.DomQuery.select('div[class=cal-daysviewpanel-dayheader-day]', this.innerHd),
1358             dayWidth = Ext.get(dayHeaders[0]).getWidth(),
1359             headerString;
1360             
1361         for (var i=0, date, isToDay, headerEl, dayColEl; i<dayHeaders.length; i++) {
1362             
1363             date = this.startDate.add(Date.DAY, i);
1364             isToDay = date.getTime() == new Date().clearTime().getTime();
1365             
1366             headerEl = Ext.get(dayHeaders[i]);
1367             
1368             if (dayWidth > 150) {
1369                 headerString = String.format(this.dayFormatString, date.format('l'), date.format('j'), date.format('F'));
1370             } else if (dayWidth > 60){
1371                 headerString = date.format('D') + ', ' + date.format('j') + '.' + date.format('n');
1372             } else {
1373                 headerString = date.format('j') + '.' + date.format('n');
1374             }
1375             
1376             headerEl.update(headerString);
1377             headerEl.parent()[(isToDay ? 'add' : 'remove') + 'Class']('cal-daysviewpanel-dayheader-today');
1378             Ext.get(this.dayCols[i])[(isToDay ? 'add' : 'remove') + 'Class']('cal-daysviewpanel-body-daycolumn-today');
1379         }
1380     },
1381     
1382     /**
1383      * returns HTML fragment of the whole day cols
1384      */
1385     getWholeDayCols: function() {
1386         var html = '';
1387         var width = 100/this.numOfDays;
1388         
1389         var baseId = Ext.id();
1390         for (var i=0; i<this.numOfDays; i++) {
1391             html += this.templates.wholeDayCol.applyTemplate({
1392                 //day: date.get('dateString'),
1393                 //height: this.granularityUnitHeights,
1394                 id: baseId + ':' + i,
1395                 width: width + '%',
1396                 left: i * width + '%'
1397             });
1398         };
1399         
1400         return html;
1401     },
1402     
1403     /**
1404      * gets HTML fragment of the horizontal time rows
1405      */
1406     getTimeRows: function() {
1407         var html = '';
1408         this.timeScale.each(function(time){
1409             var index = time.get('index');
1410             html += this.templates.timeRow.applyTemplate({
1411                 cls: index%2 ? 'cal-daysviewpanel-timeRow-off' : 'cal-daysviewpanel-timeRow-on',
1412                 height: this.granularityUnitHeights + 'px',
1413                 top: index * this.granularityUnitHeights + 'px',
1414                 time: index%2 ? '' : time.get('time')
1415             });
1416         }, this);
1417         
1418         return html;
1419     },
1420     
1421     /**
1422      * gets HTML fragment of the day columns
1423      */
1424     getDayColumns: function() {
1425         var html = '';
1426         var width = 100/this.numOfDays;
1427         
1428         for (var i=0; i<this.numOfDays; i++) {
1429             html += this.templates.dayColumn.applyTemplate({
1430                 width: width + '%',
1431                 left: i * width + '%',
1432                 overRows: this.getOverRows(i)
1433             });
1434         }
1435         
1436         return html;
1437     },
1438     
1439     /**
1440      * gets HTML fragment of the time over rows
1441      */
1442     getOverRows: function(dayIndex) {
1443         var html = '';
1444         var baseId = Ext.id();
1445         
1446         this.timeScale.each(function(time){
1447             var index = time.get('index');
1448             html += this.templates.overRow.applyTemplate({
1449                 id: baseId + ':' + dayIndex + ':' + index,
1450                 cls: 'cal-daysviewpanel-daycolumn-row-' + (index%2 ? 'off' : 'on'),
1451                 height: this.granularityUnitHeights + 'px',
1452                 time: time.get('time')
1453             });
1454         }, this);
1455         
1456         return html;
1457     },
1458     
1459     /**
1460      * inits all tempaltes of this view
1461      */
1462     initTemplates: function() {
1463         var ts = this.templates || {};
1464     
1465         ts.master = new Ext.XTemplate(
1466             '<div class="cal-daysviewpanel" hidefocus="true">',
1467                 '<div class="cal-daysviewpanel-viewport">',
1468                     '<div class="cal-daysviewpanel-header"><div class="cal-daysviewpanel-header-inner"><div class="cal-daysviewpanel-header-offset">{header}</div></div><div class="x-clear"></div></div>',
1469                     '<div class="cal-daysviewpanel-scroller"><div class="cal-daysviewpanel-body">{body}</div></div>',
1470                 '</div>',
1471                 '<a href="#" class="cal-daysviewpanel-focus" tabIndex="-1"></a>',
1472             '</div>'
1473         );
1474         
1475         ts.header = new Ext.XTemplate(
1476             '<div class="cal-daysviewpanel-daysheader">{daysHeader}</div>',
1477             '<div class="cal-daysviewpanel-wholedayheader-scroller">',
1478                 '<div class="cal-daysviewpanel-wholedayheader">',
1479                     '<div class="cal-daysviewpanel-wholedayheader-daycols">{wholeDayCols}</div>',
1480                 '</div>',
1481             '</div>'
1482         );
1483         
1484         ts.dayHeader = new Ext.XTemplate(
1485             '<div class="cal-daysviewpanel-dayheader" style="height: {height}; width: {width}; left: {left};">' + 
1486                 '<div class="cal-daysviewpanel-dayheader-day-wrap">' +
1487                     '<div class="cal-daysviewpanel-dayheader-day">{day}</div>' +
1488                 '</div>',
1489             '</div>'
1490         );
1491         
1492         ts.wholeDayCol = new Ext.XTemplate(
1493             '<div class="cal-daysviewpanel-body-wholedaycolumn" style="left: {left}; width: {width};">' +
1494                 '<div id="{id}" class="cal-daysviewpanel-datetime cal-daysviewpanel-body-wholedaycolumn-over">&#160;</div>' +
1495             '</div>'
1496         );
1497         
1498         ts.body = new Ext.XTemplate(
1499             '<div class="cal-daysviewpanel-body-inner">' +
1500                 '{timeRows}' +
1501                 '<div class="cal-daysviewpanel-body-daycolumns">{dayColumns}</div>' +
1502             '</div>'
1503         );
1504         
1505         ts.timeRow = new Ext.XTemplate(
1506             '<div class="{cls}" style="height: {height}; top: {top};">',
1507                 '<div class="cal-daysviewpanel-timeRow-time">{time}</div>',
1508             '</div>'
1509         );
1510         
1511         ts.dayColumn = new Ext.XTemplate(
1512             '<div class="cal-daysviewpanel-body-daycolumn" style="left: {left}; width: {width};">',
1513                 '<div class="cal-daysviewpanel-body-daycolumn-inner">&#160;</div>',
1514                 '{overRows}',
1515                 '<img src="', Ext.BLANK_IMAGE_URL, '" class="cal-daysviewpanel-body-daycolumn-hint-above" />',
1516                 '<img src="', Ext.BLANK_IMAGE_URL, '" class="cal-daysviewpanel-body-daycolumn-hint-below" />',
1517             '</div>'
1518         );
1519         
1520         ts.overRow = new Ext.XTemplate(
1521             '<div id="{id}" class="cal-daysviewpanel-datetime cal-daysviewpanel-daycolumn-row" style="height: {height};">' +
1522                 '<div class="{cls}" >{time}</div>'+
1523             '</div>'
1524         );
1525         
1526         ts.event = new Ext.XTemplate(
1527             '<div id="{id}" class="cal-daysviewpanel-event {extraCls}" style="width: {width}; height: {height}; left: {left}; top: {top}; z-index: {zIndex}; background-color: {bgColor}; border-color: {color};">',
1528                 '<div class="cal-daysviewpanel-event-header" style="background-color: {color};">',
1529                     '<div class="cal-daysviewpanel-event-header-inner" style="color: {textColor}; background-color: {color}; z-index: {zIndex};">{startTime}</div>',
1530                     '<div class="cal-daysviewpanel-event-header-icons">',
1531                         '<tpl for="statusIcons">',
1532                             '<img src="', Ext.BLANK_IMAGE_URL, '" class="cal-status-icon {status}-{[parent.textColor == \'#FFFFFF\' ? \'white\' : \'black\']}" ext:qtip="{[this.encode(values.text)]}" />',
1533                         '</tpl>',
1534                     '</div>',
1535                 '</div>',
1536                 '<div class="cal-daysviewpanel-event-body">{[Ext.util.Format.nl2br(Ext.util.Format.htmlEncode(values.summary))]}</div>',
1537                 '<div class="cal-daysviewpanel-event-tags">{tagsHtml}</div>',
1538             '</div>',
1539             {
1540                 encode: function(v) { return Tine.Tinebase.common.doubleEncode(v); }
1541             }
1542         );
1543         
1544         ts.wholeDayEvent = new Ext.XTemplate(
1545             '<div id="{id}" class="cal-daysviewpanel-event {extraCls}" style="width: {width}; height: {height}; left: {left}; top: {top}; z-index: {zIndex}; background-color: {bgColor}; border-color: {color};">' +
1546             '<div class="cal-daysviewpanel-wholedayevent-tags">{tagsHtml}</div>' +
1547                 '<div class="cal-daysviewpanel-wholedayevent-body">{[Ext.util.Format.nl2br(Ext.util.Format.htmlEncode(values.summary))]}</div>' +
1548                 '<div class="cal-daysviewpanel-event-header-icons" style="background-color: {bgColor};" >' +
1549                     '<tpl for="statusIcons">' +
1550                         '<img src="', Ext.BLANK_IMAGE_URL, '" class="cal-status-icon {status}-black" ext:qtip="{[this.encode(values.text)]}" />',
1551                     '</tpl>' +
1552                 '</div>' +
1553             '</div>',
1554             {
1555                 encode: function(v) { return Tine.Tinebase.common.doubleEncode(v); }
1556             }
1557         );
1558         
1559         for(var k in ts){
1560             var t = ts[k];
1561             if(t && typeof t.compile == 'function' && !t.compiled){
1562                 t.disableFormats = true;
1563                 t.compile();
1564             }
1565         }
1566
1567         this.templates = ts;
1568     }
1569 });
1570
1571 Ext.reg('Tine.Calendar.DaysView', Tine.Calendar.DaysView);