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