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