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