11876: Fix Resource invite for splitview
[tine20] / tine20 / Calendar / js / CalendarPanelSplitPlugin.js
1 Tine.Calendar.CalendarPanelSplitPlugin = function() {
2     
3 };
4
5 /**
6  * @TODO ui for active view?
7  * @TODO we could also create views beforeload for better performace
8  * 
9  */
10 Tine.Calendar.CalendarPanelSplitPlugin.prototype = {
11     /**
12      * @property app
13      * @type Tine.Calendar.Application
14      */
15     app: null,
16     
17     /**
18      * @property attendeeViews
19      * @type Ext.util.MixedCollection
20      */
21     attendeeViews: null,
22     
23     /**
24      * @property mainStore store of main view
25      * @type Ext.Store
26      */
27     mainStore: null,
28     
29     init: function(calPanel) {
30
31         if (! Tine.Tinebase.appMgr.get('Calendar').featureEnabled('featureSplitView')) {
32             return;
33         }
34
35         this.calPanel = calPanel;
36         this.app = Tine.Tinebase.appMgr.get('Calendar');
37         this.attendeeViews = new Ext.util.MixedCollection();
38         
39         // NOTE: we can't use a normal hbox layout as it can't deal with minWidth and overflow.
40         //       As ext has no suiteable layout for this, we do a little hack
41         this.calPanel.layout = new Ext.layout.HBoxLayout({
42             align : 'stretch',
43             pack  : 'start'
44         });
45         this.calPanel.layout.onLayout = this.calPanel.layout.onLayout.createInterceptor(function() {
46             var viewCount = this.attendeeViews.getCount(),
47                 availableWidth = this.calPanel.getWidth(),
48                 minViewWidth = this.calPanel.view.boxMinWidth;
49
50             var width = availableWidth/viewCount < minViewWidth ? minViewWidth * viewCount : availableWidth;
51             this.calPanel.body.setWidth(width);
52             this.calPanel.body.setStyle('overflow-x', width > availableWidth ? 'scroll' : 'hidden');
53         }, this);
54         this.calPanel.on('afterlayout', function() {
55             this.calPanel.body.setWidth(this.calPanel.getWidth());
56             this.resizeWholeDayArea.defer(this.attendeeViews.items.length * 120, this, [true]);
57         }, this);
58         
59         this.mainStore = this.calPanel.view.store;
60         this.mainStore.on('load', this.onMainStoreLoad, this);
61         this.mainStore.on('beforeload', this.onMainStoreBeforeLoad, this);
62         
63         // NOTE: we remove from items to avoid autoDestroy
64         this.calPanel.items.remove(this.calPanel.view);
65         
66         this.calPanel.getView = this.getActiveView.createDelegate(this);
67         
68         this.calPanel.on('show', this.onCalPanelShow, this);
69         this.calPanel.on('hide', this.onCalPanelHide, this);
70         
71         this.calPanel.on('afterrender', function() {
72             this.attendeeFilterGrid = this.app.getMainScreen().getWestPanel().getAttendeeFilter();
73             this.attendeeFilterGrid.on('sortchange', this.onAttendeeFilterSortChange, this);
74         }, this);
75         
76         // hook into getDefaultData 
77         this.originalGetDefaultData = Tine.Calendar.Model.Event.getDefaultData;
78         Tine.Calendar.Model.Event.getDefaultData = this.getDefaultData.createDelegate(this);
79     },
80     
81     getDefaultData: function() {
82         var defaultData = this.originalGetDefaultData(),
83             useSplit = Tine.Calendar.CalendarPanelSplitPlugin.splitBtn.pressed,
84             centerPanel = this.app.getMainScreen().getCenterPanel(),
85             activeCalPanel = centerPanel.getCalendarPanel(centerPanel.activeView),
86             activeView = this.getActiveView(),
87             attendee = activeView && activeView.ownerCt ? activeView.ownerCt.attendee : null;
88         
89         if (useSplit && attendee && this.calPanel == activeCalPanel) {
90             Ext.apply(attendee.data, {
91                 status: 'ACCEPTED'
92             });
93             defaultData.attendee = [attendee.data];
94         }
95         
96         return defaultData;
97     },
98     
99     onCalPanelShow: function() {
100         if (Tine.Calendar.CalendarPanelSplitPlugin.splitBtn) {
101             Tine.Calendar.CalendarPanelSplitPlugin.splitBtn.enable();
102         } else {
103             Tine.Calendar.CalendarPanelSplitPlugin.SplitBtn.prototype.disabled = false;
104         }
105     },
106     
107     onCalPanelHide: function() {
108         if (Tine.Calendar.CalendarPanelSplitPlugin.splitBtn) {
109             Tine.Calendar.CalendarPanelSplitPlugin.splitBtn.disable();
110         }
111     },
112     
113     onMainStoreBeforeLoad: function(store, options) {
114         this.attendeeViews.each(function(attendeeView) {
115             attendeeView.store.fireEvent('beforeload', store, options);
116         }, this);
117     },
118     
119     onMainStoreLoad: function(store, options) {
120         var cp = this.app.getMainScreen().getCenterPanel();
121         
122         if (store !== cp.getCalendarPanel(cp.activeView).getStore()) {
123             Tine.log.debug('Tine.Calendar.CalendarPanelSplitPlugin::onMainStoreLoad try again with active subview');
124             cp.onStoreLoad(this.getActiveView().store, options);
125         }
126         
127         // create view for each attendee
128         var filteredAttendee = this.attendeeFilterGrid.getValue(),
129             attendeeStore = Tine.Calendar.Model.Attender.getAttendeeStore(filteredAttendee),
130             useSplit = Tine.Calendar.CalendarPanelSplitPlugin.splitBtn.pressed;
131             
132         // remove views not longer in filter
133         this.calPanel.items.each(function(view) {
134             if (view.attendee && (! useSplit || ! Tine.Calendar.Model.Attender.getAttendeeStore.getAttenderRecord(attendeeStore, view.attendee))) {
135                 this.removeAttendeeView(view.attendee);
136             }
137             
138         }, this);
139         
140         this.manageSplitViews(filteredAttendee);
141         
142         // manage main (main is shown if no split criteria is present)
143         if (! filteredAttendee.length || ! useSplit) {
144             if (! this.attendeeViews.get('main')) {
145                 var view = this.createView({
146                     store: this.cloneStore(false)
147                 });
148                 this.attendeeViews.add('main', view);
149                 
150                 this.calPanel.add({
151                     border: false,
152                     flex: 1,
153                     items: view
154                 });
155             } else {
156                 this.attendeeViews.get('main').initData(this.cloneStore(false));
157                 this.attendeeViews.get('main').onLoad();
158             }
159         } else {
160             var main = this.attendeeViews.get('main');
161             if (main && main.ownerCt) {
162                 this.calPanel.remove(main.ownerCt);
163             }
164             
165             this.attendeeViews.removeKey('main');
166         }
167         
168         this.calPanel.doLayout();
169     },
170     
171     addAttendeeView: function(attendee, pos) {
172         var attendeeName = Tine.Calendar.AttendeeGridPanel.prototype.renderAttenderName.call(Tine.Calendar.AttendeeGridPanel.prototype, attendee.get('user_id'), false, attendee),
173             attendeeViewId = this.getAttendeeViewId(attendee);
174         
175         var filter = function(r) {
176             var attendeeStore = Tine.Calendar.Model.Attender.getAttendeeStore(r.get('attendee'));
177             return Tine.Calendar.Model.Attender.getAttendeeStore.getAttenderRecord(attendeeStore, attendee);
178         };
179         
180         var store = this.cloneStore(filter);
181         
182         var view = this.createView({
183             title: attendeeName,
184             store: store,
185             print: this.onPrint.createDelegate(this)
186         });
187         
188         // manage active views
189         view.on('render', function(v){
190             v.mon(v.getEl(), 'mousedown', this.setActiveAttendeeView.createDelegate(this, [attendeeViewId]), this);
191         }, this);
192         
193         this.attendeeViews.add(attendeeViewId, view);
194         this.calPanel.insert(pos, {
195             xtype: 'tabpanel',
196             style: 'padding: 5px;',
197             id: attendeeViewId,
198             attendee: attendee,
199             plain: true,
200             flex: 1,
201             activeItem: 0,
202             items: view
203         });
204     },
205     
206     onAttendeeFilterSortChange: function() {
207         var filteredAttendee = this.attendeeFilterGrid.getValue();
208         
209         this.manageSplitViews(filteredAttendee);
210         this.calPanel.doLayout();
211     },
212     
213     manageSplitViews: function(filteredAttendee) {
214         // create view for each attendee
215         var useSplit = Tine.Calendar.CalendarPanelSplitPlugin.splitBtn.pressed;
216         
217         if (useSplit) {
218             // add subviews new to filter
219             // NOTE: we don't iterate the attendeeStore as we would loose attendee order than
220             Ext.each(filteredAttendee, function(attendeeData, idx) {
221                 var attendee = new Tine.Calendar.Model.Attender(attendeeData, attendeeData.id);
222                 var attendeeView = this.attendeeViews.get(this.getAttendeeViewId(attendee));
223                 if (! attendeeView) {
224                     this.addAttendeeView(attendee, idx);
225                 } else {
226                     //assert position
227                     this.calPanel.items.remove(attendeeView.ownerCt);
228                     this.calPanel.items.insert(idx, attendeeView.ownerCt);
229
230                     var filterFn = attendeeView.store.filterFn;
231                     attendeeView.initData(this.cloneStore(filterFn));
232                     attendeeView.onLoad();
233                 }
234             }, this);
235
236             this.resizeWholeDayArea.defer(this.attendeeViews.items.length * 120, this, [true]);
237         }
238     },
239     
240     getAttendeeViewId: function(attendee) {
241         return this.calPanel.id + '-' + attendee.get('user_type') + '-' + attendee.getUserId();
242     },
243     
244     removeAttendeeView: function(attendee) {
245         var attendeeViewId = this.getAttendeeViewId(attendee);
246         
247         //@TODO remove relayed events?
248         
249         this.attendeeViews.removeKey(attendeeViewId);
250         this.calPanel.remove(attendeeViewId);
251     },
252     
253     createView: function(config) {
254         var view = Ext.create(Ext.apply({
255             xtype: this.calPanel.view.getXType(),
256             startDate: this.calPanel.getTopToolbar().periodPicker.getPeriod().from,
257             numOfDays: this.calPanel.view.numOfDays,
258             period: this.calPanel.getTopToolbar().periodPicker.getPeriod(),
259             updatePeriod: this.updatePeriod.createDelegate(this)
260         }, config));
261         
262         this.calPanel.relayEvents(view, ['changeView', 'changePeriod', 'addEvent', 'updateEvent', 'click', 'dblclick', 'contextmenu', 'keydown']);
263         this.calPanel.view.relayEvents(view, ['changeView', 'changePeriod', 'addEvent', 'updateEvent', 'click', 'dblclick', 'contextmenu', 'keydown']);
264         this.calPanel.view.getSelectionModel().relayEvents(view.getSelectionModel(), 'selectionchange');
265         view.getSelectionModel().on('selectionchange', this.onSelectionChange.createDelegate(this, [view]));
266         
267         if (view.onBeforeScroll) {
268             view.onBeforeScroll = view.onBeforeScroll.createSequence(this.onScroll.createDelegate(this, [view], 0));
269         }
270         
271         return view;
272     },
273     
274     /**
275      * is called on scroll of a grid, scrolls the other grids
276      * 
277      * @param {Object} activeView
278      * @param {Object} e Event
279      */
280     onScroll: function(activeView, e) {
281         if (! (activeView && activeView.scroller && activeView.scroller.dom)) {
282             return;
283         }
284         
285         var scrollTop = activeView.scroller.dom.scrollTop;
286
287         this.attendeeViews.each(function(view) {
288             if (activeView.id != view.id && view.scroller) {
289                 view.scroller.dom.scrollTop = scrollTop;
290             }
291         }, this);
292     },
293
294     getActiveView: function() {
295         if (! this.attendeeViews.getCount()) {
296             return this.calPanel.view;
297         }
298
299         if (! this.activeAttendeeView || this.attendeeViews.indexOf(this.activeAttendeeView) < 0) {
300             this.activeAttendeeView = this.attendeeViews.itemAt(0);
301         }
302         return this.activeAttendeeView;
303     },
304     
305     onPrint: function(printMode) { 
306         var renderer = new Tine.Calendar.Printer.SplitViewRenderer({printMode: printMode});
307         renderer.print(this);
308     },
309     
310     onSelectionChange: function(view) {
311         view = Ext.isString(view) ? this.attendeeViews.get(view) : view;
312         this.setActiveAttendeeView(view);
313         
314         this.attendeeViews.each(function(v) {
315             if (v !== view) {
316                 v.getSelectionModel().clearSelections(true);
317             }
318         }, this);
319         
320         this.resizeWholeDayArea.defer(this.attendeeViews.items.length * 120, this);
321
322         if (this.calPanel.mainScreen) {
323             this.calPanel.mainScreen.updateEventActions.call(this.calPanel.mainScreen);
324         }
325     },
326     
327     setActiveAttendeeView: function(view) {
328         view = Ext.isString(view) ? this.attendeeViews.get(view) : view;
329         
330         this.activeAttendeeView = view;
331     },
332     
333     updatePeriod: function() {
334         var origArgs = arguments;
335         
336         this.attendeeViews.each(function(view) {
337             view.constructor.prototype.updatePeriod.apply(view, origArgs);
338         }, this);
339         
340         this.resizeWholeDayArea.defer(this.attendeeViews.items.length * 120, this, [true]);
341     },
342     
343     /**
344      * set whole day area to the height of the highest whole day area
345      */
346     resizeWholeDayArea: function(onupdate) {
347         if (this.attendeeViews.items.length > 1) {
348             var maxWholeDayAreaSize = 10;
349             var scrollerSize = this.getActiveView().scroller ? this.getActiveView().scroller.getHeight() : 0;
350             this.attendeeViews.each(function(view) {
351                 if (! view.wholeDayArea) {
352                     return;
353                 }
354
355                 if (onupdate) {
356                     view.wholeDayArea.heightIsCalculated = false;
357                 }
358                 if (view.wholeDayArea.children.length > 1
359                     && ((view.wholeDayArea.hasOwnProperty('heightIsCalculated')
360                     && view.wholeDayArea.heightIsCalculated == false)
361                     || (! view.wholeDayArea.hasOwnProperty('heightIsCalculated'))))
362                 {
363                     if (parseInt(view.wholeDayArea.clientHeight) > maxWholeDayAreaSize) {
364                         maxWholeDayAreaSize = view.wholeDayArea.clientHeight;
365                         scrollerSize = view.scroller.getHeight();
366                     }
367                 }
368
369                 if (parseInt(view.wholeDayArea.clientHeight) > maxWholeDayAreaSize) {
370                     maxWholeDayAreaSize = view.wholeDayArea.clientHeight;
371                     scrollerSize = this.container.getSize(true);
372                 }
373             });
374             this.attendeeViews.each(function(view) {
375                 if (! view.wholeDayArea) {
376                     return;
377                 }
378                 var wholeDayAreaEl = Ext.get(view.wholeDayArea);
379                 if (wholeDayAreaEl.getHeight() != maxWholeDayAreaSize) {
380                     wholeDayAreaEl.setHeight(maxWholeDayAreaSize);
381                     view.wholeDayArea.heightIsCalculated = true;
382                 } else {
383                     view.wholeDayArea.heightIsCalculated = false;
384                 }
385                 Ext.fly(view.wholeDayScroller).setHeight(maxWholeDayAreaSize);
386
387                 var hdHeight = this.mainHd.getHeight();
388                 var vh = this.container.getSize(true).height - (hdHeight);
389
390                 Ext.fly(view.scroller).setHeight(vh);
391             });
392         }
393     },
394     
395     cloneStore: function(filterFn) {
396         var clone = new Ext.data.Store({
397             fields: Tine.Calendar.Model.Event,
398             load: this.mainStore.load.createDelegate(this.mainStore),
399             proxy: this.mainStore.proxy,
400             filterFn: filterFn,
401             replaceRecord: function(o, n) {
402                 var idx = this.indexOf(o);
403                 this.remove(o);
404                 this.insert(idx, n);
405             }
406         });
407         
408         var rs = [];
409         this.mainStore.each(function(r) {
410             if (! filterFn || filterFn(r)) {
411                 rs.push(r.copy());
412             }
413         }, this);
414         
415         clone.add(rs);
416         
417         clone.on('add', this.onCloneStoreAdd, this);
418         clone.on('update', this.onCloneStoreUpdate, this);
419         clone.on('remove', this.onCloneStoreRemove, this);
420         return clone;
421     },
422     
423     onCloneStoreAdd: function(store, rs) {
424         Ext.each(rs, function(r){
425             this.attendeeViews.each(function(view) {
426                 if (view.store != store) {
427                     if (! view.store.filterFn || view.store.filterFn(r)) {
428                         view.store.un('add', this.onCloneStoreAdd, this);
429                         view.store.add([r.copy()]);
430                         view.store.on('add', this.onCloneStoreAdd, this);
431                     }
432                 }
433             }, this);
434             
435             // check if events fits into view @see Tine.Calendar.MainScreenCenterPanel::congruenceFilterCheck
436             if (store.filterFn && !store.filterFn(r)) {
437                 (function() {
438                     if (this.ui && this.ui.rendered) {
439                         this.ui.markOutOfFilter();
440                     }
441                 }).defer(25, r)
442             }
443         }, this);
444     },
445     
446     onCloneStoreUpdate: function(store, r) {
447         this.attendeeViews.each(function(view) {
448             if (view.store != store) {
449                 view.store.un('add', this.onCloneStoreAdd, this);
450                 view.store.un('remove', this.onCloneStoreRemove, this);
451                 
452                 var cr = view.store.getById(r.id);
453                 if (cr) {
454                     view.store.remove(cr);
455                     view.store.add([r.copy()]);
456                 }
457                 
458                 view.store.on('add', this.onCloneStoreAdd, this);
459                 view.store.on('remove', this.onCloneStoreRemove, this);
460             }
461         }, this);
462     },
463     
464     onCloneStoreRemove: function(store, r) {
465         this.attendeeViews.each(function(view) {
466             if (view.store != store) {
467                 view.store.un('remove', this.onCloneStoreRemove, this);
468                 view.store.remove(view.store.getById(r.id));
469                 view.store.on('remove', this.onCloneStoreRemove, this);
470             }
471         }, this);
472     }
473 };
474
475 Ext.preg('Calendar.CalendarPanelSplitPlugin', Tine.Calendar.CalendarPanelSplitPlugin);
476
477 Tine.Calendar.CalendarPanelSplitPlugin.SplitBtn = Ext.extend(Ext.Button, {
478     enableToggle: true,
479     pressed: true,
480     disabled: true,
481     scale: 'medium',
482     rowspan: 2,
483     icon: 'images/oxygen/22x22/actions/fileview-column.png',
484     iconAlign: 'top',
485     text: 'Split',
486     stateful: true,
487     stateId: 'cal-calpanel-split-btn',
488     stateEvents: ['toggle'],
489     
490     initComponent: function() {
491         if (! Tine.Tinebase.appMgr.get('Calendar').featureEnabled('featureSplitView')) {
492             // hide button and make sure it isn't pressed
493             Tine.log.info('Split view feature is deactivated');
494             this.hidden = true;
495             this.pressed = false;
496         }
497
498         Tine.Calendar.CalendarPanelSplitPlugin.SplitBtn.superclass.initComponent.apply(this, arguments);
499         Tine.Calendar.CalendarPanelSplitPlugin.splitBtn = this;
500     },
501     
502     handler: function() {
503         Tine.Tinebase.appMgr.get('Calendar').getMainScreen().getCenterPanel().refresh();
504     },
505     
506     getState: function() {
507         return {pressed: this.pressed}
508     }
509 });
510
511 Tine.Calendar.Printer.SplitViewRenderer = Ext.extend(Tine.Calendar.Printer.BaseRenderer, {
512     getAdditionalHeaders: Tine.Calendar.Printer.DaysViewRenderer.prototype.getAdditionalHeaders,
513     generateBody: function(splitView) {
514         var viewRenderer = splitView.calPanel.view.printRenderer,
515         htmlArray = [];
516         
517         this.paperHeight = viewRenderer.paperHeight;
518         
519         splitView.attendeeViews.each(function(v, i) {
520             var renderer = new v.printRenderer({printMode: this.printMode});
521             renderer.extraTitle = v.title + ' // ';
522             renderer.titleStyle = i > 0 ? 'page-break-before:always' : '';
523
524             htmlArray.push('<div class="page">' + renderer.generateBody(v) + '</div>');
525         }, this);
526         
527         return htmlArray.join('');
528     }
529 });
530
531 // self register
532 Tine.Calendar.CalendarPanel.prototype.plugins = '[{"ptype": "Calendar.CalendarPanelSplitPlugin"}]';
533 Ext.ux.ItemRegistry.registerItem('Calendar-MainScreenPanel-ViewBtnGrp', Tine.Calendar.CalendarPanelSplitPlugin.SplitBtn, -10);