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