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