0010866: Frozen whole day events
[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             // check if event is currently visible by looking into ui.domIds
842             if (event.ui && event.ui.domIds.length > 0 && event.get('is_all_day_event')) {
843                 this.removeEvent(event);
844                 this.insertEvent(event);
845             }
846         }, this);
847     },
848     
849     onClick: function(e) {
850         // check for hint clicks first
851         var hint = e.getTarget('img[class^=cal-daysviewpanel-body-daycolumn-hint-]', 10, true);
852         if (hint) {
853             this.scroller.scroll(hint.hasClass('cal-daysviewpanel-body-daycolumn-hint-above') ? 't' : 'b', 10000, true);
854             return;
855         }
856         
857         var event = this.getTargetEvent(e);
858         if (event) {
859             this.fireEvent('click', event, e);
860         }
861     },
862     
863     onContextMenu: function(e) {
864         this.fireEvent('contextmenu', e);
865     },
866     
867     onKeyDown : function(e){
868         this.fireEvent("keydown", e);
869     },
870     
871     /**
872      * @private
873      */
874     onDblClick: function(e, target) {
875         e.stopEvent();
876         var event = this.getTargetEvent(e);
877         var dtStart = this.getTargetDateTime(e);
878         
879         if (event) {
880             this.fireEvent('dblclick', event, e);
881         } else if (dtStart && !this.editing) {
882             var newId = 'cal-daysviewpanel-new-' + Ext.id();
883             var dtend = dtStart.add(Date.HOUR, 1);
884             if (dtStart.is_all_day_event) {
885                 dtend = dtend.add(Date.HOUR, 23).add(Date.SECOND, -1);
886             }
887             
888             // do not create an event exceeding the crop day time limit
889             else if (this.cropDayTime) {
890                 var format = 'Hms';
891                 if (dtStart.format(format) >= this.dayEnd.format(format)) {
892                     return false;
893                 }
894                 
895                 if (dtend.format(format) >= this.dayEnd.format(format)) {
896                     dtend.setHours(this.dayEnd.getHours());
897                     dtend.setMinutes(this.dayEnd.getMinutes());
898                     dtend.setSeconds(this.dayEnd.getSeconds());
899                 }
900             }
901             
902             var event = new Tine.Calendar.Model.Event(Ext.apply(Tine.Calendar.Model.Event.getDefaultData(), {
903                 id: newId,
904                 dtstart: dtStart, 
905                 dtend: dtend,
906                 is_all_day_event: dtStart.is_all_day_event
907             }), newId);
908             
909             this.createEvent(e, event);
910             event.dirty = true;
911         } else if (target.className == 'cal-daysviewpanel-dayheader-day'){
912             var dayHeaders = Ext.DomQuery.select('div[class=cal-daysviewpanel-dayheader-day]', this.innerHd);
913             var date = this.startDate.add(Date.DAY, dayHeaders.indexOf(target));
914             this.fireEvent('changeView', 'day', date);
915         }
916     },
917     
918     /**
919      * @private
920      */
921     onMouseDown: function(e) {
922         // only care for left mouse button
923         if (e.button !== 0) {
924             return;
925         }
926         
927         if (! this.editing) {
928             this.focusEl.focus();
929         }
930         this.mouseDown = true;
931         
932         var targetEvent = this.getTargetEvent(e);
933         if (this.editing && this.editing.summaryEditor && (targetEvent != this.editing)) {
934             this.editing.summaryEditor.fireEvent('blur', this.editing.summaryEditor, null);
935             return;
936         }
937
938         var sm = this.getSelectionModel();
939         sm.select(targetEvent);
940         
941         var dtStart = this.getTargetDateTime(e);
942         if (dtStart) {
943             var newId = 'cal-daysviewpanel-new-' + Ext.id();
944             var event = new Tine.Calendar.Model.Event(Ext.apply(Tine.Calendar.Model.Event.getDefaultData(), {
945                 id: newId,
946                 dtstart: dtStart, 
947                 dtend: dtStart.is_all_day_event ? dtStart.add(Date.HOUR, 24).add(Date.SECOND, -1) : dtStart.add(Date.MINUTE, 2*this.timeGranularity/2),
948                 is_all_day_event: dtStart.is_all_day_event
949             }), newId);
950             event.isRangeAdd = true;
951             event.dirty = true;
952             
953             e.stopEvent();
954             this.createEvent.defer(100, this, [e, event]);
955         }
956     },
957     
958     /**
959      * @private
960      */
961     onMouseUp: function() {
962         this.mouseDown = false;
963     },
964     
965     /**
966      * @private
967      */
968     onBeforeEventResize: function(rz, e) {
969         var parts = rz.el.id.split(':');
970         var event = this.store.getById(parts[1]);
971         
972         rz.event = event;
973         rz.originalHeight = rz.el.getHeight();
974         rz.originalWidth  = rz.el.getWidth();
975
976         // NOTE: ext dosn't support move events via api
977         rz.onMouseMove = rz.onMouseMove.createSequence(function() {
978             var event = this.event;
979             if (! event) {
980                 //event already gone -> late event / busy brower?
981                 return;
982             }
983             var ui = event.ui;
984             var rzInfo = ui.getRzInfo(this);
985             
986             this.durationEl.update(rzInfo.dtend.format(event.get('is_all_day_event') ? Ext.form.DateField.prototype.format : 'H:i'));
987         }, rz);
988         
989         event.ui.markDirty();
990         
991         // NOTE: Ext keeps proxy if element is not destroyed (diff !=0)
992         if (! rz.durationEl) {
993             rz.durationEl = rz.el.insertFirst({
994                 'class': 'cal-daysviewpanel-event-rzduration',
995                 'style': 'position: absolute; bottom: 3px; right: 2px; z-index: 1000;'
996             });
997         }
998         rz.durationEl.update(event.get('dtend').format(event.get('is_all_day_event') ? Ext.form.DateField.prototype.format : 'H:i'));
999         
1000         if (event) {
1001             this.getSelectionModel().select(event);
1002         } else {
1003             this.getSelectionModel().clearSelections();
1004         }
1005     },
1006     
1007     /**
1008      * @private
1009      */
1010     onEventResize: function(rz, width, height) {
1011         var event = rz.event;
1012         
1013         if (! event) {
1014             //event already gone -> late event / busy brower?
1015             return;
1016         }
1017         
1018         var rzInfo = event.ui.getRzInfo(rz, width, height);
1019         
1020         if (rzInfo.diff != 0) {
1021             if (rzInfo.duration > 0) {
1022                 event.set('dtend', rzInfo.dtend);
1023             } else {
1024                 // force event length to at least 1 minute
1025                 var date = new Date(event.get('dtstart').getTime());
1026                 date.setMinutes(date.getMinutes() + 1);
1027                 event.set('dtend', date);
1028             }
1029         }
1030         
1031         if (event.summaryEditor) {
1032             event.summaryEditor.setHeight(event.ui.getEls()[0].getHeight() -18);
1033         }
1034         
1035         // don't fire update events on rangeAdd
1036         if (rzInfo.diff != 0 && event != this.editing && ! event.isRangeAdd) {
1037             this.fireEvent('updateEvent', event);
1038         } else {
1039             event.ui.clearDirty();
1040         }
1041     },
1042     
1043     /**
1044      * @private
1045      */
1046     onUpdate : function(ds, event){
1047         // don't update events while being created
1048         if (event.get('id').match(/new/)) {
1049             return;
1050         }
1051         
1052         // relayout original context
1053         var originalRegistry = (event.modified.hasOwnProperty('is_all_day_event') ? event.modified.is_all_day_event : event.get('is_all_day_event')) ? 
1054             this.parallelWholeDayEventsRegistry : 
1055             this.parallelScrollerEventsRegistry;
1056
1057         var registry = event.get('is_all_day_event') ? this.parallelWholeDayEventsRegistry : this.parallelScrollerEventsRegistry;
1058         var originalDtstart = event.modified.hasOwnProperty('dtstart') ? event.modified.dtstart : event.get('dtstart');
1059         var originalDtend = event.modified.hasOwnProperty('dtend') ? event.modified.dtend : event.get('dtend');
1060
1061         var originalParallels = originalRegistry.getEvents(originalDtstart, originalDtend);
1062         for (var j=0; j<originalParallels.length; j++) {
1063             this.removeEvent(originalParallels[j]);
1064         }
1065         originalRegistry.unregister(event);
1066         
1067         var originalParallels = originalRegistry.getEvents(originalDtstart, originalDtend);
1068         for (var j=0; j<originalParallels.length; j++) {
1069             this.insertEvent(originalParallels[j]);
1070         }
1071         
1072         // relayout actual context
1073         var parallelEvents = registry.getEvents(event.get('dtstart'), event.get('dtend'));
1074         for (var j=0; j<parallelEvents.length; j++) {
1075             this.removeEvent(parallelEvents[j]);
1076         }
1077         
1078         registry.register(event);
1079         var parallelEvents = registry.getEvents(event.get('dtstart'), event.get('dtend'));
1080         for (var j=0; j<parallelEvents.length; j++) {
1081             this.insertEvent(parallelEvents[j]);
1082         }
1083         
1084         this.setActiveEvent(this.getActiveEvent());
1085         this.onLayout();
1086     },
1087
1088     /**
1089      * @private
1090      */
1091     onAdd : function(ds, records, index){
1092         //console.log('onAdd');
1093         for (var i=0; i<records.length; i++) {
1094             var event = records[i];
1095             
1096             var registry = event.get('is_all_day_event') ? this.parallelWholeDayEventsRegistry : this.parallelScrollerEventsRegistry;
1097             registry.register(event);
1098             
1099             var parallelEvents = registry.getEvents(event.get('dtstart'), event.get('dtend'));
1100             
1101             for (var j=0; j<parallelEvents.length; j++) {
1102                 this.removeEvent(parallelEvents[j]);
1103                 this.insertEvent(parallelEvents[j]);
1104             }
1105             
1106             //this.setActiveEvent(event);
1107         }
1108         
1109         this.onLayout();
1110     },
1111
1112     /**
1113      * @private
1114      */
1115     onRemove : function(ds, event, index, isUpdate) {
1116         if (!event || index == -1) {
1117             return;
1118         }
1119         
1120         if(isUpdate !== true){
1121             //this.fireEvent("beforeeventremoved", this, index, record);
1122         }
1123         var registry = event.get('is_all_day_event') ? this.parallelWholeDayEventsRegistry : this.parallelScrollerEventsRegistry;
1124         registry.unregister(event);
1125         this.removeEvent(event);
1126         this.getSelectionModel().unselect(event);
1127         this.onLayout();
1128     },
1129     
1130     onBeforeLoad: function(store, options) {
1131         if (! options.refresh) {
1132             this.store.each(this.removeEvent, this);
1133             this.transitionEvents = [];
1134         } else {
1135             this.transitionEvents = this.store.data.items;
1136         }
1137     },
1138     
1139     /**
1140      * @private
1141      */
1142     onLoad : function() {
1143         if(! this.rendered){
1144             return;
1145         }
1146         
1147         // remove old events
1148         Ext.each(this.transitionEvents, this.removeEvent, this);
1149         
1150         // setup registry
1151         this.parallelScrollerEventsRegistry = new Tine.Calendar.ParallelEventsRegistry({dtStart: this.startDate, dtEnd: this.endDate});
1152         this.parallelWholeDayEventsRegistry = new Tine.Calendar.ParallelEventsRegistry({dtStart: this.startDate, dtEnd: this.endDate});
1153         
1154         // todo: sort generic?
1155         this.store.fields = Tine.Calendar.Model.Event.prototype.fields;
1156         this.store.sortInfo = {field: 'dtstart', direction: 'ASC'};
1157         this.store.applySort();
1158         
1159         this.store.each(function(event) {
1160             var registry = event.get('is_all_day_event') ? this.parallelWholeDayEventsRegistry : this.parallelScrollerEventsRegistry;
1161             registry.register(event);
1162         }, this);
1163         
1164         // put the events in
1165         this.store.each(this.insertEvent, this);
1166         
1167         this.onLayout();
1168     },
1169     
1170     /**
1171      * print wrapper
1172      */
1173     print: function(printMode) {
1174         var renderer = new this.printRenderer({printMode: printMode});
1175         renderer.print(this);
1176     },
1177     
1178     hex2dec: function(hex) {
1179         var dec = 0;
1180         hex = hex.toString();
1181         var length = hex.length, multiplier, digit;
1182         for (var i=0; i<length; i++) {
1183             
1184             multiplier = Math.pow(16, (Math.abs(i - hex.length)-1));
1185             digit = parseInt(hex.toString().charAt([i]), 10);
1186             if (isNaN(digit)) {
1187                 switch (hex.toString().charAt([i]).toUpperCase()) {
1188                     case 'A': digit = 10;  break;
1189                     case 'B': digit = 11;  break;
1190                     case 'C': digit = 12;  break;
1191                     case 'D': digit = 13;  break;
1192                     case 'E': digit = 14;  break;
1193                     case 'F': digit = 15;  break;
1194                     default: return NaN;
1195                 }
1196             }
1197             dec = dec + (multiplier * digit);
1198         }
1199         
1200         return dec;
1201     },
1202     
1203     getPeriod: function() {
1204         return {
1205             from: this.startDate,
1206             until: this.startDate.add(Date.DAY, this.numOfDays)
1207         };
1208     },
1209     
1210     /**
1211      * get date of a (event) target
1212      * 
1213      * @param {Ext.EventObject} e
1214      * @return {Date}
1215      */
1216     getTargetDateTime: function(e) {
1217         var target = e.getTarget('div[class^=cal-daysviewpanel-datetime]');
1218         
1219         if (target && target.id.match(/^ext-gen\d+:\d+/)) {
1220             var parts = target.id.split(':');
1221             
1222             var date = this.startDate.add(Date.DAY, parseInt(parts[1], 10));
1223             date.is_all_day_event = true;
1224             
1225             if (parts[2] ) {
1226                 var timePart = this.timeScale.getAt(parts[2]);
1227                 date = date.add(Date.MINUTE, timePart.get('minutes'));
1228                 date.is_all_day_event = false;
1229             }
1230             
1231             return date;
1232         }
1233     },
1234     
1235     /**
1236      * gets event el of target
1237      * 
1238      * @param {Ext.EventObject} e
1239      * @return {Tine.Calendar.Model.Event}
1240      */
1241     getTargetEvent: function(e) {
1242         var target = e.getTarget();
1243         var el = Ext.fly(target);
1244         
1245         if (el.hasClass('cal-daysviewpanel-event') || (el = el.up('[id*=event:]', 10))) {
1246             var parts = el.dom.id.split(':');
1247             
1248             return this.store.getById(parts[1]);
1249         }
1250     },
1251     
1252     getTimeOffset: function(date) {
1253         var d = this.granularityUnitHeights / this.timeGranularity;
1254         
1255         return Math.round(d * ( 60 * date.getHours() + date.getMinutes()));
1256     },
1257     
1258     getTimeHeight: function(dtStart, dtEnd) {
1259         var d = this.granularityUnitHeights / this.timeGranularity;
1260         return Math.round(d * ((dtEnd.getTime() - dtStart.getTime()) / Date.msMINUTE));
1261     },
1262     
1263     getHeightMinutes: function(height) {
1264         return Math.round(height * this.timeGranularity / this.granularityUnitHeights);
1265     },
1266     
1267     /**
1268      * fetches elements from our generated dom
1269      */
1270     initElements : function(){
1271         var E = Ext.Element;
1272
1273 //        var el = this.el.dom.firstChild;
1274         var cs = this.el.dom.firstChild.childNodes;
1275
1276 //        this.el = new E(el);
1277
1278         this.mainWrap = new E(cs[0]);
1279         this.mainHd = new E(this.mainWrap.dom.firstChild);
1280
1281         this.innerHd = this.mainHd.dom.firstChild;
1282         
1283         this.wholeDayScroller = this.innerHd.firstChild.childNodes[1];
1284         this.wholeDayArea = this.wholeDayScroller.firstChild;
1285         
1286         this.scroller = new E(this.mainWrap.dom.childNodes[1]);
1287         this.scroller.setStyle('overflow-x', 'hidden');
1288
1289
1290         this.mon(this.scroller, 'mousewheel', this.onMouseWheel, this);
1291         this.mon(this.scroller, 'scroll', this.onBeforeScroll, this);
1292         this.mon(this.scroller, 'scroll', this.onScroll, this, {buffer: 200});
1293
1294         this.mainBody = new E(this.scroller.dom.firstChild);
1295         this.dayCols = this.mainBody.dom.firstChild.lastChild.childNodes;
1296
1297         this.focusEl = new E(this.el.dom.lastChild.lastChild);
1298         this.focusEl.swallowEvent("click", true);
1299         this.focusEl.swallowEvent("dblclick", true);
1300         this.focusEl.swallowEvent("contextmenu", true);
1301         
1302         this.aboveHints   = this.mainBody.select('img[class=cal-daysviewpanel-body-daycolumn-hint-above]');
1303         this.belowHints   = this.mainBody.select('img[class=cal-daysviewpanel-body-daycolumn-hint-below]');
1304     },
1305     
1306     /**
1307      * @TODO this returns wrong cols on DST boundaries:
1308      *  e.g. on DST switch form +2 to +1 an all day event is 25 hrs. long
1309      * 
1310      * @param {} date
1311      * @return {}
1312      */
1313     getColumnNumber: function(date) {
1314         return Math.floor((date.add(Date.SECOND, 1).getTime() - this.startDate.getTime()) / Date.msDAY);
1315     },
1316     
1317     getDateColumnEl: function(pos) {
1318         return this.dayCols[pos];
1319     },
1320     
1321     checkWholeDayEls: function() {
1322         var freeIdxs = [];
1323         for (var i=0; i<this.wholeDayArea.childNodes.length-1; i++) {
1324             if(this.wholeDayArea.childNodes[i].childNodes.length === 1) {
1325                 freeIdxs.push(i);
1326             }
1327         }
1328         
1329         for (var i=1; i<freeIdxs.length; i++) {
1330             Ext.fly(this.wholeDayArea.childNodes[freeIdxs[i]]).remove();
1331         }
1332     },
1333
1334     /**
1335      * buffered version of this.unbufferedOnLayout
1336      * @see this.initComponent
1337      */
1338     onLayout: Ext.emptyFn,
1339
1340     /**
1341      * layouts the view
1342      */
1343     unbufferedOnLayout: function() {
1344         Tine.Calendar.DaysView.superclass.onLayout.apply(this, arguments);
1345         if(!this.mainBody){
1346             return; // not rendered
1347         }
1348         
1349         var csize = this.container.getSize(true);
1350         var vw = csize.width;
1351         
1352         this.el.setSize(csize.width, csize.height);
1353         
1354         // layout whole day area
1355         var wholeDayAreaEl = Ext.get(this.wholeDayArea);
1356         for (var i=0, bottom = wholeDayAreaEl.getTop(); i<this.wholeDayArea.childNodes.length -1; i++) {
1357             bottom = Math.max(parseInt(Ext.get(this.wholeDayArea.childNodes[i]).getBottom(), 10), bottom);
1358         }
1359         var wholeDayAreaHeight = bottom - wholeDayAreaEl.getTop() + 10;
1360         // take one third of the available height maximum
1361         wholeDayAreaEl.setHeight(wholeDayAreaHeight);
1362         Ext.fly(this.wholeDayScroller).setHeight(Math.min(Math.round(csize.height/3), wholeDayAreaHeight));
1363         
1364         var hdHeight = this.mainHd.getHeight();
1365         var vh = csize.height - (hdHeight);
1366         
1367         this.scroller.setSize(vw, vh);
1368         
1369         // force positioning on scroll hints
1370         this.onScroll.defer(100, this);
1371     },
1372     
1373     onDestroy: function() {
1374         this.removeAllEvents();
1375         this.initData(false);
1376         this.purgeListeners();
1377         
1378         Tine.Calendar.DaysView.superclass.onDestroy.apply(this, arguments);
1379     },
1380     
1381     /**
1382      * returns HTML frament of the day headers
1383      */
1384     getDayHeaders: function() {
1385         var html = '';
1386         var width = 100/this.numOfDays;
1387         
1388         for (var i=0, date; i<this.numOfDays; i++) {
1389             var day = this.startDate.add(Date.DAY, i);
1390             html += this.templates.dayHeader.applyTemplate({
1391                 day: String.format(this.dayFormatString, day.format('l'), day.format('j'), day.format('F')),
1392                 height: this.granularityUnitHeights,
1393                 width: width + '%',
1394                 left: i * width + '%'
1395             });
1396         }
1397         return html;
1398     },
1399     
1400     /**
1401      * updates HTML of day headers
1402      */
1403     updateDayHeaders: function() {
1404         if (! this.rendered) {
1405             return;
1406         }
1407         var dayHeaders = Ext.DomQuery.select('div[class=cal-daysviewpanel-dayheader-day]', this.innerHd),
1408             dayWidth = Ext.get(dayHeaders[0]).getWidth(),
1409             headerString;
1410             
1411         for (var i=0, date, isToDay, headerEl, dayColEl; i<dayHeaders.length; i++) {
1412             
1413             date = this.startDate.add(Date.DAY, i);
1414             isToDay = date.getTime() == new Date().clearTime().getTime();
1415             
1416             headerEl = Ext.get(dayHeaders[i]);
1417             
1418             if (dayWidth > 150) {
1419                 headerString = String.format(this.dayFormatString, date.format('l'), date.format('j'), date.format('F'));
1420             } else if (dayWidth > 60){
1421                 headerString = date.format('D') + ', ' + date.format('j') + '.' + date.format('n');
1422             } else {
1423                 headerString = date.format('j') + '.' + date.format('n');
1424             }
1425             
1426             headerEl.update(headerString);
1427             headerEl.parent()[(isToDay ? 'add' : 'remove') + 'Class']('cal-daysviewpanel-dayheader-today');
1428             Ext.get(this.dayCols[i])[(isToDay ? 'add' : 'remove') + 'Class']('cal-daysviewpanel-body-daycolumn-today');
1429         }
1430     },
1431     
1432     /**
1433      * returns HTML fragment of the whole day cols
1434      */
1435     getWholeDayCols: function() {
1436         var html = '';
1437         var width = 100/this.numOfDays;
1438         
1439         var baseId = Ext.id();
1440         for (var i=0; i<this.numOfDays; i++) {
1441             html += this.templates.wholeDayCol.applyTemplate({
1442                 //day: date.get('dateString'),
1443                 //height: this.granularityUnitHeights,
1444                 id: baseId + ':' + i,
1445                 width: width + '%',
1446                 left: i * width + '%'
1447             });
1448         };
1449         
1450         return html;
1451     },
1452     
1453     /**
1454      * gets HTML fragment of the horizontal time rows
1455      */
1456     getTimeRows: function() {
1457         var html = '';
1458         this.timeScale.each(function(time){
1459             var index = time.get('index');
1460             html += this.templates.timeRow.applyTemplate({
1461                 cls: index%2 ? 'cal-daysviewpanel-timeRow-off' : 'cal-daysviewpanel-timeRow-on',
1462                 height: this.granularityUnitHeights + 'px',
1463                 top: index * this.granularityUnitHeights + 'px',
1464                 time: index%2 ? '' : time.get('time')
1465             });
1466         }, this);
1467         
1468         return html;
1469     },
1470     
1471     /**
1472      * gets HTML fragment of the day columns
1473      */
1474     getDayColumns: function() {
1475         var html = '';
1476         var width = 100/this.numOfDays;
1477         
1478         for (var i=0; i<this.numOfDays; i++) {
1479             html += this.templates.dayColumn.applyTemplate({
1480                 width: width + '%',
1481                 left: i * width + '%',
1482                 overRows: this.getOverRows(i)
1483             });
1484         }
1485         
1486         return html;
1487     },
1488     
1489     /**
1490      * gets HTML fragment of the time over rows
1491      */
1492     getOverRows: function(dayIndex) {
1493         var html = '';
1494         var baseId = Ext.id();
1495         
1496         this.timeScale.each(function(time){
1497             var index = time.get('index');
1498             html += this.templates.overRow.applyTemplate({
1499                 id: baseId + ':' + dayIndex + ':' + index,
1500                 cls: 'cal-daysviewpanel-daycolumn-row-' + (index%2 ? 'off' : 'on'),
1501                 height: this.granularityUnitHeights + 'px',
1502                 time: time.get('time')
1503             });
1504         }, this);
1505         
1506         return html;
1507     },
1508     
1509     /**
1510      * inits all tempaltes of this view
1511      */
1512     initTemplates: function() {
1513         var ts = this.templates || {};
1514     
1515         ts.master = new Ext.XTemplate(
1516             '<div class="cal-daysviewpanel" hidefocus="true">',
1517                 '<div class="cal-daysviewpanel-viewport">',
1518                     '<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>',
1519                     '<div class="cal-daysviewpanel-scroller"><div class="cal-daysviewpanel-body">{body}</div></div>',
1520                 '</div>',
1521                 '<a href="#" class="cal-daysviewpanel-focus" tabIndex="-1"></a>',
1522             '</div>'
1523         );
1524         
1525         ts.header = new Ext.XTemplate(
1526             '<div class="cal-daysviewpanel-daysheader">{daysHeader}</div>',
1527             '<div class="cal-daysviewpanel-wholedayheader-scroller">',
1528                 '<div class="cal-daysviewpanel-wholedayheader">',
1529                     '<div class="cal-daysviewpanel-wholedayheader-daycols">{wholeDayCols}</div>',
1530                 '</div>',
1531             '</div>'
1532         );
1533         
1534         ts.dayHeader = new Ext.XTemplate(
1535             '<div class="cal-daysviewpanel-dayheader" style="height: {height}; width: {width}; left: {left};">' + 
1536                 '<div class="cal-daysviewpanel-dayheader-day-wrap">' +
1537                     '<div class="cal-daysviewpanel-dayheader-day">{day}</div>' +
1538                 '</div>',
1539             '</div>'
1540         );
1541         
1542         ts.wholeDayCol = new Ext.XTemplate(
1543             '<div class="cal-daysviewpanel-body-wholedaycolumn" style="left: {left}; width: {width};">' +
1544                 '<div id="{id}" class="cal-daysviewpanel-datetime cal-daysviewpanel-body-wholedaycolumn-over">&#160;</div>' +
1545             '</div>'
1546         );
1547         
1548         ts.body = new Ext.XTemplate(
1549             '<div class="cal-daysviewpanel-body-inner">' +
1550                 '{timeRows}' +
1551                 '<div class="cal-daysviewpanel-body-daycolumns">{dayColumns}</div>' +
1552             '</div>'
1553         );
1554         
1555         ts.timeRow = new Ext.XTemplate(
1556             '<div class="{cls}" style="height: {height}; top: {top};">',
1557                 '<div class="cal-daysviewpanel-timeRow-time">{time}</div>',
1558             '</div>'
1559         );
1560         
1561         ts.dayColumn = new Ext.XTemplate(
1562             '<div class="cal-daysviewpanel-body-daycolumn" style="left: {left}; width: {width};">',
1563                 '<div class="cal-daysviewpanel-body-daycolumn-inner">&#160;</div>',
1564                 '{overRows}',
1565                 '<img src="', Ext.BLANK_IMAGE_URL, '" class="cal-daysviewpanel-body-daycolumn-hint-above" />',
1566                 '<img src="', Ext.BLANK_IMAGE_URL, '" class="cal-daysviewpanel-body-daycolumn-hint-below" />',
1567             '</div>'
1568         );
1569         
1570         ts.overRow = new Ext.XTemplate(
1571             '<div id="{id}" class="cal-daysviewpanel-datetime cal-daysviewpanel-daycolumn-row" style="height: {height};">' +
1572                 '<div class="{cls}" >{time}</div>'+
1573             '</div>'
1574         );
1575         
1576         ts.event = new Ext.XTemplate(
1577             '<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};">',
1578                 '<div class="cal-daysviewpanel-event-header" style="background-color: {color};">',
1579                     '<div class="cal-daysviewpanel-event-header-inner" style="color: {textColor}; background-color: {color}; z-index: {zIndex};">{startTime}</div>',
1580                     '<div class="cal-daysviewpanel-event-header-icons">',
1581                         '<tpl for="statusIcons">',
1582                             '<img src="', Ext.BLANK_IMAGE_URL, '" class="cal-status-icon {status}-{[parent.textColor == \'#FFFFFF\' ? \'white\' : \'black\']}" ext:qtip="{[this.encode(values.text)]}" />',
1583                         '</tpl>',
1584                     '</div>',
1585                 '</div>',
1586                 '<div class="cal-daysviewpanel-event-body">{[Ext.util.Format.nl2br(Ext.util.Format.htmlEncode(values.summary))]}</div>',
1587                 '<div class="cal-daysviewpanel-event-tags">{tagsHtml}</div>',
1588             '</div>',
1589             {
1590                 encode: function(v) { return Tine.Tinebase.common.doubleEncode(v); }
1591             }
1592         );
1593         
1594         ts.wholeDayEvent = new Ext.XTemplate(
1595             '<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};">' +
1596             '<div class="cal-daysviewpanel-wholedayevent-tags">{tagsHtml}</div>' +
1597                 '<div class="cal-daysviewpanel-wholedayevent-body">{[Ext.util.Format.nl2br(Ext.util.Format.htmlEncode(values.summary))]}</div>' +
1598                 '<div class="cal-daysviewpanel-event-header-icons" style="background-color: {bgColor};" >' +
1599                     '<tpl for="statusIcons">' +
1600                         '<img src="', Ext.BLANK_IMAGE_URL, '" class="cal-status-icon {status}-black" ext:qtip="{[this.encode(values.text)]}" />',
1601                     '</tpl>' +
1602                 '</div>' +
1603             '</div>',
1604             {
1605                 encode: function(v) { return Tine.Tinebase.common.doubleEncode(v); }
1606             }
1607         );
1608         
1609         for(var k in ts){
1610             var t = ts[k];
1611             if(t && typeof t.compile == 'function' && !t.compiled){
1612                 t.disableFormats = true;
1613                 t.compile();
1614             }
1615         }
1616
1617         this.templates = ts;
1618     }
1619 });
1620
1621 Ext.reg('Tine.Calendar.DaysView', Tine.Calendar.DaysView);