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