Fix 24:00 again
[tine20] / tine20 / Calendar / js / EventUI.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) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
7  * 
8  * @note: lot to do here, i just started to move stuff from views here
9  */
10  
11 Ext.ns('Tine.Calendar');
12
13 Tine.Calendar.EventUI = function(event) {
14     this.event = event;
15     this.domIds = [];
16     this.app = Tine.Tinebase.appMgr.get('Calendar');
17     this.init();
18 };
19
20 Tine.Calendar.EventUI.prototype = {
21     addClass: function(cls) {
22         Ext.each(this.getEls(), function(el){
23             el.addClass(cls);
24         });
25     },
26     
27     blur: function() {
28         Ext.each(this.getEls(), function(el){
29             el.blur();
30         });
31     },
32     
33     clearDirty: function() {
34         Ext.each(this.getEls(), function(el) {
35             el.setOpacity(1, 1);
36         });
37     },
38     
39     focus: function() {
40         Ext.each(this.getEls(), function(el){
41             el.focus();
42         });
43     },
44     
45     /**
46      * returns events dom
47      * @return {Array} of Ext.Element
48      */
49     getEls: function() {
50         var domEls = [];
51         for (var i=0; i<this.domIds.length; i++) {
52             var el = Ext.get(this.domIds[i]);
53             if (el) {
54                 domEls.push(el);
55             }
56         }
57         return domEls;
58     },
59     
60     init: function() {
61         // shortcut
62         //this.colMgr = Tine.Calendar.colorMgr;
63     },
64     
65     markDirty: function() {
66         Ext.each(this.getEls(), function(el) {
67             el.setOpacity(0.5, 1);
68         });
69     },
70     
71     markOutOfFilter: function() {
72         Ext.each(this.getEls(), function(el) {
73             el.setOpacity(0.5, 0);
74             el.setStyle({'background-color': '#aaa', 'border-color': '#888'});
75             Ext.DomHelper.applyStyles(el.dom.firstChild, {'background-color': '#888'});
76             if (el.dom.firstChild.hasOwnProperty('firstChild')) {
77                 Ext.DomHelper.applyStyles(el.dom.firstChild.firstChild, {'background-color': '#888'});
78             }
79         });
80     },
81     
82     onSelectedChange: function(state){
83         if(state){
84             //this.focus();
85             this.addClass('cal-event-active');
86             this.setStyle({'z-index': 1000});
87             
88         }else{
89             //this.blur();
90             this.removeClass('cal-event-active');
91             this.setStyle({'z-index': 100});
92         }
93     },
94     
95     /**
96      * removes a event from the dom
97      */
98     remove: function() {
99         var eventEls = this.getEls();
100         for (var i=0; i<eventEls.length; i++) {
101             if (eventEls[i] && typeof eventEls[i].remove == 'function') {
102                 eventEls[i].remove();
103             }
104         }
105         if (this.resizeable) {
106             this.resizeable.destroy();
107             this.resizeable = null;
108         }
109         this.domIds = [];
110     },
111     
112     removeClass: function(cls) {
113         Ext.each(this.getEls(), function(el){
114             el.removeClass(cls);
115         });
116     },
117     
118     render: function() {
119         // do nothing
120     },
121     
122     setOpacity: function(v) {
123         Ext.each(this.getEls(), function(el){
124             el.setStyle(v);
125         });
126     },
127     
128     setStyle: function(style) {
129         Ext.each(this.getEls(), function(el){
130             el.setStyle(style);
131         });
132     }
133     
134 };
135
136
137
138 Tine.Calendar.DaysViewEventUI = Ext.extend(Tine.Calendar.EventUI, {
139     
140     clearDirty: function() {
141         Tine.Calendar.DaysViewEventUI.superclass.clearDirty.call(this);
142         
143         Ext.each(this.getEls(), function(el) {
144             el.setStyle({'border-style': 'solid'});
145         });
146     },
147     /**
148      * get diff of resizeable
149      * 
150      * @param {Ext.Resizeable} rz
151      */
152     getRzInfo: function(rz, width, height) {
153         var rzInfo = {};
154         
155         var event = rz.event;
156         var view = event.view;
157         
158         // NOTE proxy might be gone after resize
159         var box = rz.proxy.getBox();
160         var width = width ? width: box.width;
161         var height =  height? height : box.height;
162         
163         var originalDuration = (event.get('dtend').getTime() - event.get('dtstart').getTime()) / Date.msMINUTE;
164         
165         if(event.get('is_all_day_event')) {
166             var dayWidth = Ext.fly(view.wholeDayArea).getWidth() / view.numOfDays;
167             rzInfo.diff = Math.round((width - rz.originalWidth) / dayWidth);
168             
169         } else {
170             rzInfo.diff = Math.round((height - rz.originalHeight) * (view.timeGranularity / view.granularityUnitHeights));
171             // neglegt diffs due to borders etc.
172             rzInfo.diff = Math.round(rzInfo.diff/15) * 15;
173         }
174         rzInfo.duration = originalDuration + rzInfo.diff;
175         
176         if(event.get('is_all_day_event')) {
177             rzInfo.dtend = event.get('dtend').add(Date.DAY, rzInfo.diff);
178         } else {
179             rzInfo.dtend = event.get('dtstart').add(Date.MINUTE, rzInfo.duration);
180         }
181         
182         return rzInfo;
183     },
184     
185     markDirty: function() {
186         Tine.Calendar.DaysViewEventUI.superclass.markDirty.call(this);
187         
188         Ext.each(this.getEls(), function(el) {
189             el.setStyle({'border-style': 'dashed'});
190         });
191     },
192     
193     onSelectedChange: function(state){
194         Tine.Calendar.DaysViewEventUI.superclass.onSelectedChange.call(this, state);
195         if(state){
196             this.addClass('cal-daysviewpanel-event-active');
197             
198         }else{
199             this.removeClass('cal-daysviewpanel-event-active');
200         }
201     },
202     
203     render: function(view) {
204         this.event.view = view;
205
206         this.colorSet = Tine.Calendar.colorMgr.getColor(this.event);
207         this.event.colorSet = this.colorSet;
208         
209         this.dtStart = this.event.get('dtstart');
210         this.startColNum = view.getColumnNumber(this.dtStart);
211         
212         this.dtEnd = this.event.get('dtend');
213         
214         if (this.event.get('editGrant')) {
215             this.extraCls = 'cal-daysviewpanel-event-editgrant';
216         }
217         
218         this.extraCls += ' cal-status-' + this.event.get('status');
219         
220         // 00:00 in users timezone is a spechial case where the user expects
221         // something like 24:00 and not 00:00
222         if (this.dtEnd.format('H:i') == '00:00') {
223             this.dtEnd = this.dtEnd.add(Date.MINUTE, -1);
224         }
225         this.endColNum = view.getColumnNumber(this.dtEnd);
226         
227         // skip dates not in our diplay range
228         if (this.endColNum < 0 || this.startColNum > view.numOfDays-1) {
229             return;
230         }
231         
232         // compute status icons
233         this.statusIcons = [];
234         if (this.event.get('class') === 'PRIVATE') {
235             this.statusIcons.push({
236                 status: 'private',
237                 text: this.app.i18n._('private classification')
238             });
239         }
240         
241         if (this.event.get('rrule')) {
242             this.statusIcons.push({
243                 status: 'recur',
244                 text: this.app.i18n._('recurring event')
245             });
246         } else if (this.event.isRecurException()) {
247             this.statusIcons.push({
248                 status: 'recurex',
249                 text: this.app.i18n._('recurring event exception')
250             });
251         }
252
253         
254         
255         if (! Ext.isEmpty(this.event.get('alarms'))) {
256             this.statusIcons.push({
257                 status: 'alarm',
258                 text: this.app.i18n._('has alarm')
259             });
260         }
261         
262         if (! Ext.isEmpty(this.event.get('attachments'))) {
263             this.statusIcons.push({
264                 status: 'attachment',
265                 text: this.app.i18n._('has attachments')
266             });
267         }
268         
269         var myAttenderRecord = this.event.getMyAttenderRecord(),
270             myAttenderStatusRecord = myAttenderRecord ? Tine.Tinebase.widgets.keyfield.StoreMgr.get('Calendar', 'attendeeStatus').getById(myAttenderRecord.get('status')) : null;
271             
272         if (myAttenderStatusRecord && myAttenderStatusRecord.get('system')) {
273             this.statusIcons.push({
274                 status: myAttenderRecord.get('status'),
275                 text: myAttenderStatusRecord.get('i18nValue')
276             });
277         }
278         
279         var registry = this.event.get('is_all_day_event') ? view.parallelWholeDayEventsRegistry : view.parallelScrollerEventsRegistry;
280         
281         var position = registry.getPosition(this.event);
282         var maxParallels = registry.getMaxParalles(this.dtStart, this.dtEnd);
283         
284         if (this.event.get('is_all_day_event')) {
285             this.renderAllDayEvent(view, maxParallels, position);
286         } else {
287             this.renderScrollerEvent(view, maxParallels, position);
288         }
289         
290         if (this.event.dirty) {
291             // the event was selected before
292             this.onSelectedChange(true);
293         }
294         
295         if (this.event.outOfFilter) {
296             this.markOutOfFilter();
297         }
298         
299         this.rendered = true;
300     },
301     
302     renderAllDayEvent: function(view, parallels, pos) {
303         // lcocal COPY!
304         var extraCls = this.extraCls;
305         
306         var offsetWidth = Ext.fly(view.wholeDayArea).getWidth();
307         
308         var width = Math.round(offsetWidth * (this.dtEnd.getTime() - this.dtStart.getTime()) / (view.numOfDays * Date.msDAY)) -5;
309         var left = Math.round(offsetWidth * (this.dtStart.getTime() - view.startDate.getTime()) / (view.numOfDays * Date.msDAY));
310         
311         if (left < 0) {
312             width = width + left;
313             left = 0;
314             extraCls = extraCls + ' cal-daysviewpanel-event-cropleft';
315         }
316         
317         if (left + width > offsetWidth) {
318             width = offsetWidth - left;
319             extraCls = extraCls + ' cal-daysviewpanel-event-cropright';
320         }
321         
322         var domId = Ext.id() + '-event:' + this.event.get('id');
323         this.domIds.push(domId);
324         
325         var eventEl = view.templates.wholeDayEvent.insertFirst(view.wholeDayArea, {
326             id: domId,
327             tagsHtml: Tine.Tinebase.common.tagsRenderer(this.event.get('tags')),
328             summary: this.event.get('summary'),
329             startTime: this.dtStart.format('H:i'),
330             extraCls: extraCls,
331             color: this.colorSet.color,
332             bgColor: this.colorSet.light,
333             textColor: this.colorSet.text,
334             zIndex: 100,
335             width: width  +'px',
336             height: '15px',
337             left: left + 'px',
338             top: pos * 18 + 'px',//'1px'
339             statusIcons: this.statusIcons
340         }, true);
341         
342         if (this.event.dirty) {
343             eventEl.setStyle({'border-style': 'dashed'});
344             eventEl.setOpacity(0.5);
345         }
346         
347         if (! (this.endColNum > view.numOfDays) && this.event.get('editGrant')) {
348             this.resizeable = new Ext.Resizable(eventEl, {
349                 handles: 'e',
350                 disableTrackOver: true,
351                 dynamic: true,
352                 //dynamic: !!this.event.isRangeAdd,
353                 widthIncrement: Math.round(offsetWidth / view.numOfDays),
354                 minWidth: Math.round(offsetWidth / view.numOfDays),
355                 listeners: {
356                     scope: view,
357                     resize: view.onEventResize,
358                     beforeresize: view.onBeforeEventResize
359                 }
360             });
361         }
362         //console.log([eventEl.dom, parallels, pos])
363     },
364     
365     renderScrollerEvent: function(view, parallels, pos) {
366         var scrollerHeight = view.granularityUnitHeights * ((24 * 60)/view.timeGranularity);
367         
368         for (var currColNum=this.startColNum; currColNum<=this.endColNum; currColNum++) {
369             
370             // lcocal COPY!
371             var extraCls = this.extraCls;
372             
373             if (currColNum < 0 || currColNum >= view.numOfDays) {
374                 continue;
375             }
376             
377             var top = view.getTimeOffset(this.dtStart);
378             var height = this.startColNum == this.endColNum ? view.getTimeHeight(this.dtStart, this.dtEnd) : view.getTimeOffset(this.dtEnd);
379             
380             if (currColNum != this.startColNum) {
381                 top = 0;
382                 extraCls = extraCls + ' cal-daysviewpanel-event-croptop';
383             }
384             
385             if (this.endColNum != currColNum) {
386                 height = view.getTimeHeight(this.dtStart, this.dtStart.add(Date.DAY, 1));
387                 extraCls = extraCls + ' cal-daysviewpanel-event-cropbottom';
388             }
389             
390             var domId = Ext.id() + '-event:' + this.event.get('id');
391             this.domIds.push(domId);
392             
393             // minimal height
394             if (height <= 12) {
395                 height = 12;
396             }
397             
398             // minimal top
399             if (top > scrollerHeight -12) {
400                 top = scrollerHeight -12;
401             }
402             
403             var eventEl = view.templates.event.append(view.getDateColumnEl(currColNum), {
404                 id: domId,
405                 summary: height >= 24 ? this.event.get('summary') : '',
406                 tagsHtml: height >= 24 ? Tine.Tinebase.common.tagsRenderer(this.event.get('tags')) : '',
407                 startTime: (height >= 24 && top <= scrollerHeight-24) ? this.dtStart.format('H:i') : this.dtStart.format('H:i') + ' ' +  this.event.get('summary'),
408                 extraCls: extraCls,
409                 color: this.colorSet.color,
410                 bgColor: this.colorSet.light,
411                 textColor: this.colorSet.text,
412                 zIndex: 100,
413                 height: height + 'px',
414                 left: Math.round(pos * 90 * 1/parallels) + '%',
415                 width: Math.round(90 * 1/parallels) + '%',
416                 // max shift to 20+gap
417                 //left: 80 - 80/Math.sqrt(pos+1) + 10*Math.sqrt(pos) + '%',
418                 //width: 80/Math.sqrt(pos+1) + '%',
419                 top: top + 'px',
420                 statusIcons: this.statusIcons
421             }, true);
422             
423             if (this.event.dirty) {
424                 eventEl.setStyle({'border-style': 'dashed'});
425                 eventEl.setOpacity(0.5);
426             }
427             
428             if (currColNum == this.endColNum && this.event.get('editGrant')) {
429                 var maxHeight = 10000;
430                 
431                 if (view.cropDayTime) { 
432                     maxHeight = view.dayEndPx - top;
433                 }
434                 
435                 this.resizeable = new Ext.Resizable(eventEl, {
436                     handles: 's',
437                     disableTrackOver: true,
438                     dynamic: true,
439                     //dynamic: !!this.event.isRangeAdd,
440                     heightIncrement: view.granularityUnitHeights/2,
441                     maxHeight: maxHeight,
442                     listeners: {
443                         scope: view,
444                         resize: view.onEventResize,
445                         beforeresize: view.onBeforeEventResize
446                     }
447                 });
448             }
449         }
450     }
451 });
452
453 Tine.Calendar.MonthViewEventUI = Ext.extend(Tine.Calendar.EventUI, {
454     onSelectedChange: function(state){
455         Tine.Calendar.MonthViewEventUI.superclass.onSelectedChange.call(this, state);
456         if (state){
457             this.addClass('cal-monthview-active');
458             this.setStyle({
459                 'background-color': this.color,
460                 'color':            (this.colorSet) ? this.colorSet.text : '#000000'
461             });
462             
463         } else {
464             this.removeClass('cal-monthview-active');
465             this.setStyle({
466                 'background-color': this.is_all_day_event ? this.bgColor : '',
467                 'color':            this.is_all_day_event ? '#000000' : this.color
468             });
469         }
470     }
471 });