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