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