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