0010866: Frozen whole day events
[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         var ids = Tine.Tinebase.data.Clipboard.getIds('Calendar', 'Event');
291         
292         if (ids.indexOf(this.event.get('id')) > -1) {
293             this.markDirty(true);
294         } else if (this.event.dirty) {
295             // the event was selected before
296             this.onSelectedChange(true);
297         }
298         
299         if (this.event.outOfFilter) {
300             this.markOutOfFilter();
301         }
302         
303         this.rendered = true;
304     },
305     
306     renderAllDayEvent: function(view, parallels, pos) {
307         // lcocal COPY!
308         var extraCls = this.extraCls;
309         
310         var offsetWidth = Ext.fly(view.wholeDayArea).getWidth();
311         
312         //var width = Math.round(offsetWidth * (this.dtEnd.getTime() - this.dtStart.getTime()) / (view.numOfDays * Date.msDAY)) -5;
313         //var left = Math.round(offsetWidth * (this.dtStart.getTime() - view.startDate.getTime()) / (view.numOfDays * Date.msDAY));
314
315         var width = Math.floor(1000 * (this.dtEnd.getTime() - this.dtStart.getTime()) / (view.numOfDays * Date.msDAY) -5) /10;
316         var left = 100 * (this.dtStart.getTime() - view.startDate.getTime()) / (view.numOfDays * Date.msDAY);
317
318
319         if (left < 0) {
320             width = width + left;
321             left = 0;
322             extraCls = extraCls + ' cal-daysviewpanel-event-cropleft';
323         }
324         
325         if (left + width > offsetWidth) {
326             width = offsetWidth - left;
327             extraCls = extraCls + ' cal-daysviewpanel-event-cropright';
328         }
329         
330         var domId = Ext.id() + '-event:' + this.event.get('id');
331         this.domIds.push(domId);
332         
333         var eventEl = view.templates.wholeDayEvent.insertFirst(view.wholeDayArea, {
334             id: domId,
335             tagsHtml: Tine.Tinebase.common.tagsRenderer(this.event.get('tags')),
336             summary: this.event.get('summary'),
337             startTime: this.dtStart.format('H:i'),
338             extraCls: extraCls,
339             color: this.colorSet.color,
340             bgColor: this.colorSet.light,
341             textColor: this.colorSet.text,
342             zIndex: 100,
343             width: width  +'%',
344             height: '15px',
345             left: left + '%',
346             top: pos * 18 + 'px',//'1px'
347             statusIcons: this.statusIcons
348         }, true);
349         
350         if (this.event.dirty) {
351             eventEl.setStyle({'border-style': 'dashed'});
352             eventEl.setOpacity(0.5);
353         }
354         
355         if (! (this.endColNum > view.numOfDays) && this.event.get('editGrant')) {
356             this.resizeable = new Ext.Resizable(eventEl, {
357                 handles: 'e',
358                 disableTrackOver: true,
359                 dynamic: true,
360                 //dynamic: !!this.event.isRangeAdd,
361                 widthIncrement: Math.round(offsetWidth / view.numOfDays),
362                 minWidth: Math.round(offsetWidth / view.numOfDays),
363                 listeners: {
364                     scope: view,
365                     resize: view.onEventResize,
366                     beforeresize: view.onBeforeEventResize
367                 }
368             });
369         }
370         //console.log([eventEl.dom, parallels, pos])
371     },
372     
373     renderScrollerEvent: function(view, parallels, pos) {
374         var scrollerHeight = view.granularityUnitHeights * ((24 * 60)/view.timeGranularity);
375         
376         for (var currColNum=this.startColNum; currColNum<=this.endColNum; currColNum++) {
377             
378             // lcocal COPY!
379             var extraCls = this.extraCls;
380             
381             if (currColNum < 0 || currColNum >= view.numOfDays) {
382                 continue;
383             }
384             
385             var top = view.getTimeOffset(this.dtStart);
386             var height = this.startColNum == this.endColNum ? view.getTimeHeight(this.dtStart, this.dtEnd) : view.getTimeOffset(this.dtEnd);
387             
388             if (currColNum != this.startColNum) {
389                 top = 0;
390                 extraCls = extraCls + ' cal-daysviewpanel-event-croptop';
391             }
392             
393             if (this.endColNum != currColNum) {
394                 height = view.getTimeHeight(this.dtStart, this.dtStart.add(Date.DAY, 1));
395                 extraCls = extraCls + ' cal-daysviewpanel-event-cropbottom';
396             }
397             
398             var domId = Ext.id() + '-event:' + this.event.get('id');
399             this.domIds.push(domId);
400             
401             // minimal height
402             if (height <= 12) {
403                 height = 12;
404             }
405             
406             // minimal top
407             if (top > scrollerHeight -12) {
408                 top = scrollerHeight -12;
409             }
410             
411             var eventEl = view.templates.event.append(view.getDateColumnEl(currColNum), {
412                 id: domId,
413                 summary: height >= 24 ? this.event.get('summary') : '',
414                 tagsHtml: height >= 24 ? Tine.Tinebase.common.tagsRenderer(this.event.get('tags')) : '',
415                 startTime: (height >= 24 && top <= scrollerHeight-24) ? this.dtStart.format('H:i') : this.dtStart.format('H:i') + ' ' +  this.event.get('summary'),
416                 extraCls: extraCls,
417                 color: this.colorSet.color,
418                 bgColor: this.colorSet.light,
419                 textColor: this.colorSet.text,
420                 zIndex: 100,
421                 height: height + 'px',
422                 left: Math.round(pos * 90 * 1/parallels) + '%',
423                 width: Math.round(90 * 1/parallels) + '%',
424                 // max shift to 20+gap
425                 //left: 80 - 80/Math.sqrt(pos+1) + 10*Math.sqrt(pos) + '%',
426                 //width: 80/Math.sqrt(pos+1) + '%',
427                 top: top + 'px',
428                 statusIcons: this.statusIcons
429             }, true);
430             
431             if (this.event.dirty) {
432                 eventEl.setStyle({'border-style': 'dashed'});
433                 eventEl.setOpacity(0.5);
434             }
435             
436             if (currColNum == this.endColNum && this.event.get('editGrant')) {
437                 var maxHeight = 10000;
438                 
439                 if (view.cropDayTime) { 
440                     maxHeight = view.dayEndPx - top;
441                 }
442                 
443                 this.resizeable = new Ext.Resizable(eventEl, {
444                     handles: 's',
445                     disableTrackOver: true,
446                     dynamic: true,
447                     //dynamic: !!this.event.isRangeAdd,
448                     heightIncrement: view.granularityUnitHeights/2,
449                     maxHeight: maxHeight,
450                     listeners: {
451                         scope: view,
452                         resize: view.onEventResize,
453                         beforeresize: view.onBeforeEventResize
454                     }
455                 });
456             }
457         }
458     }
459 });
460
461 Tine.Calendar.MonthViewEventUI = Ext.extend(Tine.Calendar.EventUI, {
462     onSelectedChange: function(state){
463         Tine.Calendar.MonthViewEventUI.superclass.onSelectedChange.call(this, state);
464         if (state){
465             this.addClass('cal-monthview-active');
466             this.setStyle({
467                 'background-color': this.color,
468                 'color':            (this.colorSet) ? this.colorSet.text : '#000000'
469             });
470             
471         } else {
472             this.removeClass('cal-monthview-active');
473             this.setStyle({
474                 'background-color': this.is_all_day_event ? this.bgColor : '',
475                 'color':            this.is_all_day_event ? '#000000' : this.color
476             });
477         }
478     }
479 });