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