Merge branch '2013.03'
[tine20] / tine20 / Calendar / js / MainScreenCenterPanel.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-2013 Metaways Infosystems GmbH (http://www.metaways.de)
7  */
8
9 /* global Ext, Tine */
10
11 Ext.ns('Tine.Calendar');
12
13 Tine.Calendar.MainScreenCenterPanel = Ext.extend(Ext.Panel, {
14     /**
15      * @cfg {String} activeView
16      */
17     activeView: 'weekSheet',
18     
19     startDate: new Date().clearTime(),
20     
21     /**
22      * $property Object view -> startdate
23      */
24     startDates: null,
25     
26     /**
27      * @cfg {String} loadMaskText
28      * _('Loading events, please wait...')
29      */
30     loadMaskText: 'Loading events, please wait...',
31     
32     /**
33      * @cfg {Number} autoRefreshInterval (seconds)
34      */
35     autoRefreshInterval: 300,
36     
37     /**
38      * @property autoRefreshTask
39      * @type Ext.util.DelayedTask
40      */
41     autoRefreshTask: null,
42     
43     /**
44      * add records from other applications using the split add button
45      * - activated by default
46      * 
47      * @type Bool
48      * @property splitAddButton
49      */
50     splitAddButton: true,
51     
52     periodRe: /^(day|week|month)/i,
53     presentationRe: /(sheet|grid)$/i,
54     
55     calendarPanels: {},
56     
57     border: false,
58     layout: 'border',
59     
60     stateful: true,
61     stateId: 'cal-mainscreen',
62     stateEvents: ['changeview'],
63     
64     getState: function () {
65         return Ext.copyTo({}, this, 'activeView');
66     },
67     
68     applyState: Ext.emptyFn,
69     
70     initComponent: function () {
71         var me = this;
72         
73         this.addEvents(
74         /**
75          * @event changeview
76          * fired if an event got clicked
77          * @param {Tine.Calendar.MainScreenCenterPanel} mspanel
78          * @param {String} view
79          */
80         'changeview');
81         
82         this.recordClass = Tine.Calendar.Model.Event;
83         
84         this.app = Tine.Tinebase.appMgr.get('Calendar');
85         
86         // init some translations
87         this.i18nRecordName = this.app.i18n.n_hidden(this.recordClass.getMeta('recordName'), this.recordClass.getMeta('recordsName'), 1);
88         this.i18nRecordsName = this.app.i18n._hidden(this.recordClass.getMeta('recordsName'));
89         this.i18nContainerName = this.app.i18n.n_hidden(this.recordClass.getMeta('containerName'), this.recordClass.getMeta('containersName'), 1);
90         this.i18nContainersName = this.app.i18n._hidden(this.recordClass.getMeta('containersName'));
91         
92         this.loadMaskText = this.app.i18n._hidden(this.loadMaskText);
93         
94         var state = Ext.state.Manager.get(this.stateId, {});
95         Ext.apply(this, state);
96         
97         this.defaultFilters = [
98             {field: 'attender', operator: 'in', value: [Ext.apply(Tine.Calendar.Model.Attender.getDefaultData(), {
99                 user_id: Tine.Tinebase.registry.get('currentAccount')
100             })]},
101             {field: 'attender_status', operator: 'notin', value: ['DECLINED']}
102         ];
103         this.filterToolbar = this.getFilterToolbar({
104             onFilterChange: this.refresh.createDelegate(this, [false]),
105             getAllFilterData: this.getAllFilterData.createDelegate(this)
106         });
107         
108         this.filterToolbar.getQuickFilterPlugin().criteriaIgnores.push(
109             {field: 'period'},
110             {field: 'grants'}
111         );
112         
113         this.startDates = [];
114         this.initActions();
115         this.initLayout();
116         
117         // init autoRefresh
118         this.autoRefreshTask = new Ext.util.DelayedTask(this.refresh, this, [{
119             refresh: true,
120             autoRefresh: true
121         }]);
122         
123         Tine.Calendar.MainScreenCenterPanel.superclass.initComponent.call(this);
124     },
125     
126     initActions: function () {
127         this.action_editInNewWindow = new Ext.Action({
128             requiredGrant: 'readGrant',
129             text: this.i18nEditActionText ? this.app.i18n._hidden(this.i18nEditActionText) : String.format(Tine.Tinebase.translation._hidden('Edit {0}'), this.i18nRecordName),
130             disabled: true,
131             handler: this.onEditInNewWindow.createDelegate(this, ["edit"]),
132             iconCls: 'action_edit'
133         });
134         
135         this.action_addInNewWindow = new Ext.Action({
136             requiredGrant: 'addGrant',
137             text: this.i18nAddActionText ? this.app.i18n._hidden(this.i18nAddActionText) : String.format(Tine.Tinebase.translation._hidden('Add {0}'), this.i18nRecordName),
138             handler: this.onEditInNewWindow.createDelegate(this, ["add"]),
139             iconCls: 'action_add'
140         });
141         
142         // note: unprecise plural form here, but this is hard to change
143         this.action_deleteRecord = new Ext.Action({
144             requiredGrant: 'deleteGrant',
145             allowMultiple: true,
146             singularText: this.i18nDeleteActionText ? i18nDeleteActionText[0] : String.format(Tine.Tinebase.translation.n_hidden('Delete {0}', 'Delete {0}', 1), this.i18nRecordName),
147             pluralText: this.i18nDeleteActionText ? i18nDeleteActionText[1] : String.format(Tine.Tinebase.translation.n_hidden('Delete {0}', 'Delete {0}', 1), this.i18nRecordsName),
148             translationObject: this.i18nDeleteActionText ? this.app.i18n : Tine.Tinebase.translation,
149             text: this.i18nDeleteActionText ? this.i18nDeleteActionText[0] : String.format(Tine.Tinebase.translation.n_hidden('Delete {0}', 'Delete {0}', 1), this.i18nRecordName),
150             handler: this.onDeleteRecords,
151             disabled: true,
152             iconCls: 'action_delete',
153             scope: this
154         });
155         
156         this.actions_print = new Ext.Action({
157             requiredGrant: 'readGrant',
158             text: this.app.i18n._('Print Page'),
159             handler: this.onPrint,
160             iconCls:'action_print',
161             scope: this
162         });
163         
164         this.showSheetView = new Ext.Button({
165             pressed: String(this.activeView).match(/sheet$/i),
166             scale: 'medium',
167             minWidth: 60,
168             rowspan: 2,
169             iconAlign: 'top',
170             requiredGrant: 'readGrant',
171             text: this.app.i18n._('Sheet'),
172             handler: this.changeView.createDelegate(this, ["sheet"]),
173             iconCls:'cal-sheet-view-type',
174             xtype: 'tbbtnlockedtoggle',
175             toggleGroup: 'Calendar_Toolbar_tgViewTypes',
176             scope: this
177         });
178         
179         this.showGridView = new Ext.Button({
180             pressed: String(this.activeView).match(/grid$/i),
181             scale: 'medium',
182             minWidth: 60,
183             rowspan: 2,
184             iconAlign: 'top',
185             requiredGrant: 'readGrant',
186             text: this.app.i18n._('Grid'),
187             handler: this.changeView.createDelegate(this, ["grid"]),
188             iconCls:'cal-grid-view-type',
189             xtype: 'tbbtnlockedtoggle',
190             toggleGroup: 'Calendar_Toolbar_tgViewTypes',
191             scope: this
192         });
193         
194         this.showDayView = new Ext.Toolbar.Button({
195             pressed: String(this.activeView).match(/^day/i),
196             text: this.app.i18n._('Day'),
197             iconCls: 'cal-day-view',
198             xtype: 'tbbtnlockedtoggle',
199             handler: this.changeView.createDelegate(this, ["day"]),
200             enableToggle: true,
201             toggleGroup: 'Calendar_Toolbar_tgViews'
202         });
203         this.showWeekView = new Ext.Toolbar.Button({
204             pressed: String(this.activeView).match(/^week/i),
205             text: this.app.i18n._('Week'),
206             iconCls: 'cal-week-view',
207             xtype: 'tbbtnlockedtoggle',
208             handler: this.changeView.createDelegate(this, ["week"]),
209             enableToggle: true,
210             toggleGroup: 'Calendar_Toolbar_tgViews'
211         });
212         this.showMonthView = new Ext.Toolbar.Button({
213             pressed: String(this.activeView).match(/^month/i),
214             text: this.app.i18n._('Month'),
215             iconCls: 'cal-month-view',
216             xtype: 'tbbtnlockedtoggle',
217             handler: this.changeView.createDelegate(this, ["month"]),
218             enableToggle: true,
219             toggleGroup: 'Calendar_Toolbar_tgViews'
220         });
221         
222         this.changeViewActions = [
223             this.showDayView,
224             this.showWeekView,
225             this.showMonthView
226         ];
227         
228         this.recordActions = [
229             this.action_editInNewWindow,
230             this.action_deleteRecord
231         ];
232         
233         this.actionUpdater = new  Tine.widgets.ActionUpdater({
234             actions: this.recordActions,
235             grantsProperty: false,
236             containerProperty: false
237         });
238     },
239     
240         
241     getActionToolbar: Tine.widgets.grid.GridPanel.prototype.getActionToolbar,
242     
243     getActionToolbarItems: function() {
244         return {
245             xtype: 'buttongroup',
246             plugins: [{
247                 ptype: 'ux.itemregistry',
248                 key:   'Calendar-MainScreenPanel-ViewBtnGrp'
249             }],
250             items: [
251                 this.showSheetView,
252                 this.showGridView
253             ]
254         };
255     },
256     
257     /**
258      * @private
259      * 
260      * NOTE: Order of items matters! Ext.Layout.Border.SplitRegion.layout() does not
261      *       fence the rendering correctly, as such it's impotant, so have the ftb
262      *       defined after all other layout items
263      */
264     initLayout: function () {
265         this.items = [{
266             region: 'center',
267             layout: 'card',
268             activeItem: 0,
269             border: false,
270             items: [this.getCalendarPanel(this.activeView)]
271         }];
272         
273         // add detail panel
274         if (this.detailsPanel) {
275             this.items.push({
276                 region: 'south',
277                 border: false,
278                 collapsible: true,
279                 collapseMode: 'mini',
280                 header: false,
281                 split: true,
282                 layout: 'fit',
283                 height: this.detailsPanel.defaultHeight ? this.detailsPanel.defaultHeight : 125,
284                 items: this.detailsPanel
285                 
286             });
287             //this.detailsPanel.doBind(this.activeView);
288         }
289         
290         // add filter toolbar
291         if (this.filterToolbar) {
292             this.items.push({
293                 region: 'north',
294                 border: false,
295                 items: this.filterToolbar,
296                 listeners: {
297                     scope: this,
298                     afterlayout: function (ct) {
299                         ct.suspendEvents();
300                         ct.setHeight(this.filterToolbar.getHeight());
301                         ct.ownerCt.layout.layout();
302                         ct.resumeEvents();
303                     }
304                 }
305             });
306         }
307     },
308     
309     /**
310      * @private
311      */
312     onRender: function(ct, position) {
313         Tine.Calendar.MainScreenCenterPanel.superclass.onRender.apply(this, arguments);
314         
315         var defaultFavorite = Tine.widgets.persistentfilter.model.PersistentFilter.getDefaultFavorite(this.app.appName, this.recordClass.prototype.modelName),
316             favoritesPanel  = this.app.getMainScreen().getWestPanel().getFavoritesPanel();
317         
318         this.loadMask = new Ext.LoadMask(this.body, {msg: this.loadMaskText});
319         
320         if (defaultFavorite) {
321             favoritesPanel.selectFilter(defaultFavorite);
322         } else {
323             this.refresh();
324         }
325     },
326     
327     getViewParts: function (view) {
328         view = String(view);
329         
330         var activeView = String(this.activeView),
331             periodMatch = view.match(this.periodRe),
332             period = Ext.isArray(periodMatch) ? periodMatch[0] : null,
333             activePeriodMatch = activeView.match(this.periodRe),
334             activePeriod = Ext.isArray(activePeriodMatch) ? activePeriodMatch[0] : 'week',
335             presentationMatch = view.match(this.presentationRe),
336             presentation = Ext.isArray(presentationMatch) ? presentationMatch[0] : null,
337             activePresentationMatch = activeView.match(this.presentationRe),
338             activePresentation = Ext.isArray(activePresentationMatch) ? activePresentationMatch[0] : 'sheet';
339             
340         return {
341             period: period ? period : activePeriod,
342             presentation: presentation ? presentation : activePresentation,
343             toString: function() {return this.period + Ext.util.Format.capitalize(this.presentation);}
344         };
345     },
346     
347     changeView: function (view, startDate) {
348         // autocomplete view
349         var viewParts = this.getViewParts(view);
350         view = viewParts.toString();
351         
352         Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::changeView(' + view + ',' + startDate + ')');
353         
354         // save current startDate
355         this.startDates[this.activeView] = this.startDate.clone();
356         
357         if (startDate && Ext.isDate(startDate)) {
358             this.startDate = startDate.clone();
359         } else {
360             // see if a recent startDate of that view fits
361             var lastStartDate = this.startDates[view],
362                 currentPeriod = this.getCalendarPanel(this.activeView).getView().getPeriod();
363                 
364             if (Ext.isDate(lastStartDate) && lastStartDate.between(currentPeriod.from, currentPeriod.until)) {
365                 this.startDate = this.startDates[view].clone();
366             }
367         }
368         
369         var panel = this.getCalendarPanel(view);
370         var cardPanel = this.items.first();
371         
372         if (panel.rendered) {
373             cardPanel.layout.setActiveItem(panel.id);
374         } else {
375             cardPanel.add(panel);
376             cardPanel.layout.setActiveItem(panel.id);
377             cardPanel.doLayout();
378         }
379         
380         this.activeView = view;
381         
382         // move around changeViewButtons
383         var rightRow = Ext.get(Ext.DomQuery.selectNode('tr[class=x-toolbar-right-row]', panel.tbar.dom));
384         
385         for (var i = this.changeViewActions.length - 1; i >= 0; i--) {
386             rightRow.insertFirst(this.changeViewActions[i].getEl().parent().dom);
387         }
388         this['show' + Ext.util.Format.capitalize(viewParts.period) +  'View'].toggle(true);
389         this['show' + Ext.util.Format.capitalize(viewParts.presentation) +  'View'].toggle(true);
390         
391         // update actions
392         this.updateEventActions();
393         
394         // update data
395         panel.getView().updatePeriod({from: this.startDate});
396         panel.getStore().load({});
397         
398         this.fireEvent('changeview', this, view);
399     },
400     
401     /**
402      * returns all filter data for current view
403      */
404     getAllFilterData: function () {
405         var store = this.getCalendarPanel(this.activeView).getStore();
406         
407         var options = {
408             refresh: true, // ommit loadMask
409             noPeriodFilter: true
410         };
411         this.onStoreBeforeload(store, options);
412         
413         return options.params.filter;
414     },
415     
416     getCustomfieldFilters: Tine.widgets.grid.GridPanel.prototype.getCustomfieldFilters,
417     
418     getFilterToolbar: Tine.widgets.grid.GridPanel.prototype.getFilterToolbar,
419     
420     /**
421      * returns store of currently active view
422      */
423     getStore: function () {
424         return this.getCalendarPanel(this.activeView).getStore();
425     },
426     
427     /**
428      * onContextMenu
429      * 
430      * @param {Event} e
431      */
432     onContextMenu: function (e) {
433         e.stopEvent();
434         
435         var view = this.getCalendarPanel(this.activeView).getView();
436         var event = view.getTargetEvent(e);
437         var datetime = view.getTargetDateTime(e);
438         
439         var addAction, responseAction, copyAction;
440         if (datetime || event) {
441             var dtStart = datetime || event.get('dtstart').clone();
442             if (dtStart.format('H:i') === '00:00') {
443                 dtStart = dtStart.add(Date.HOUR, 9);
444             }
445             
446             addAction = {
447                 text: this.i18nAddActionText ? this.app.i18n._hidden(this.i18nAddActionText) : String.format(Tine.Tinebase.translation._hidden('Add {0}'), this.i18nRecordName),
448                 handler: this.onEditInNewWindow.createDelegate(this, ["add", null, null, {dtStart: dtStart, is_all_day_event: datetime && datetime.is_all_day_event}]),
449                 iconCls: 'action_add'
450             };
451             
452             // assemble event actions
453             if (event) {
454                 responseAction = this.getResponseAction(event);
455                 copyAction = this.getCopyAction(event);
456             }
457         
458         } else {
459             addAction = this.action_addInNewWindow;
460         }
461         
462         if (event) {
463             view.getSelectionModel().select(event, e, e.ctrlKey);
464         } else {
465             view.getSelectionModel().clearSelections();
466         }
467         
468         var ctxMenu = new Ext.menu.Menu({
469             plugins: [{
470                 ptype: 'ux.itemregistry',
471                 key:   'Calendar-MainScreenPanel-ContextMenu',
472                 config: {
473                     event: event,
474                     datetime: datetime
475                 }
476             },
477             // to allow gridpanel hooks (like email compose)
478             {
479                 ptype: 'ux.itemregistry',
480                 key:   'Calendar-GridPanel-ContextMenu'
481             }],
482             items: this.recordActions.concat(addAction, responseAction || [], copyAction || [])
483         });
484         ctxMenu.showAt(e.getXY());
485     },
486     
487     /**
488      * get response action
489      * 
490      * @param {Tine.Calendar.Model.Event} event
491      * @return {Object}
492      */
493     getResponseAction: function(event) {
494         var statusStore = Tine.Tinebase.widgets.keyfield.StoreMgr.get('Calendar', 'attendeeStatus'),
495             myAttenderRecord = event.getMyAttenderRecord(),
496             myAttenderStatus = myAttenderRecord ? myAttenderRecord.get('status') : null,
497             myAttenderStatusRecord = statusStore.getById(myAttenderStatus),
498             responseAction = null;
499         
500         if (myAttenderRecord) {
501             responseAction = {
502                 text: this.app.i18n._('Set my response'),
503                 icon: myAttenderStatusRecord ? myAttenderStatusRecord.get('icon') : false,
504                 menu: []
505             };
506             
507             statusStore.each(function(status) {
508                 var isCurrent = myAttenderRecord.get('status') === status.id;
509                 
510                 // NOTE: we can't use checked items here as we use icons already
511                 responseAction.menu.push({
512                     text: status.get('i18nValue'),
513                     handler: this.setResponseStatus.createDelegate(this, [event, status.id]),
514                     icon: status.get('icon'),
515                     disabled: myAttenderRecord.get('status') === status.id
516                 });
517             }, this);
518         }
519         
520         return responseAction;
521     },
522
523     /**
524      * get copy action
525      * 
526      * @param {Tine.Calendar.Model.Event} event
527      * @return {Object}
528      */
529     getCopyAction: function(event) {
530         var copyAction = {
531             text: String.format(_('Copy {0}'), this.i18nRecordName),
532             handler: this.onEditInNewWindow.createDelegate(this, ["copy", event]),
533             iconCls: 'action_editcopy',
534             // TODO allow to copy recurring events / exceptions
535             disabled: event.isRecurInstance() || event.isRecurException() || event.isRecurBase()
536         };
537         
538         return copyAction;
539     },
540     
541     checkPastEvent: function(event, checkBusyConflicts, actionType) {
542         var start = event.get('dtstart').getTime();
543         var morning = new Date().clearTime().getTime();
544
545         switch (actionType) {
546             case 'update':
547                 var title = this.app.i18n._('Updating event in the past'),
548                     optionYes = this.app.i18n._('Update this event'),
549                     optionNo = this.app.i18n._('Do not update this event');
550                 break;
551             case 'add':
552             default:
553                 var title = this.app.i18n._('Creating event in the past'),
554                     optionYes = this.app.i18n._('Create this event'),
555                     optionNo = this.app.i18n._('Do not create this event');
556         }
557         
558         if(start < morning) {
559             Tine.widgets.dialog.MultiOptionsDialog.openWindow({
560                 title: title,
561                 height: 170,
562                 scope: this,
563                 options: [
564                     {text: optionYes, name: 'yes'},
565                     {text: optionNo, name: 'no'}
566                 ],
567                 
568                 handler: function(option) {
569                     try {
570                         switch (option) {
571                             case 'yes':
572                                 if (actionType == 'update') this.onUpdateEvent(event, true);
573                                 else this.onAddEvent(event, checkBusyConflicts, true);
574                                 break;
575                             case 'no':
576                             default:
577                                 try {
578                                     var panel = this.getCalendarPanel(this.activeView);
579                                     if(panel) {
580                                         var store = panel.getStore(),
581                                             view = panel.getView();
582                                     }
583                                 } catch(e) {
584                                     var panel = null, 
585                                         store = null,
586                                         view = null;
587                                 }
588                                 
589                                 if (actionType == 'add') {
590                                     if(store) store.remove(event);
591                                 } else {
592                                     if (view && view.rendered) {
593                                         this.loadMask.show();
594                                         store.reload();
595 //                                        TODO: restore original event so no reload is needed
596 //                                        var updatedEvent = event;
597 //                                        updatedEvent.dirty = false;
598 //                                        updatedEvent.editing = false;
599 //                                        updatedEvent.modified = null;
600 //                                        
601 //                                        store.replaceRecord(event, updatedEvent);
602 //                                        view.getSelectionModel().select(event);
603                                     }
604                                     this.setLoading(false);
605                                 }
606                         }
607                     } catch (e) {
608                         Tine.log.error('Tine.Calendar.MainScreenCenterPanel::checkPastEvent::handler');
609                         Tine.log.error(e);
610                     }
611                 }             
612             });
613         } else {
614             if (actionType == 'update') this.onUpdateEvent(event, true);
615             else this.onAddEvent(event, checkBusyConflicts, true);
616         }
617     },
618     
619     onAddEvent: function(event, checkBusyConflicts, pastChecked) {
620
621         if(!pastChecked) {
622             this.checkPastEvent(event, checkBusyConflicts, 'add');
623             return;
624         }
625         
626         this.setLoading(true);
627         
628         // remove temporary id
629         if (event.get('id').match(/new/)) {
630             event.set('id', '');
631         }
632         
633         if (event.isRecurBase()) {
634             if (this.loadMask) {
635                 this.loadMask.show();
636             }
637         }
638         
639         var panel = this.getCalendarPanel(this.activeView),
640             store = event.store,
641             view = panel.getView();
642         
643         Tine.Calendar.backend.saveRecord(event, {
644             scope: this,
645             success: function(createdEvent) {
646                 if (createdEvent.isRecurBase()) {
647                     store.load({refresh: true});
648                 } else {
649                     store.replaceRecord(event, createdEvent);
650                     this.setLoading(false);
651                     if (view && view.rendered) {
652                         view.getSelectionModel().select(createdEvent);
653                     }
654                 }
655             },
656             failure: this.onProxyFail.createDelegate(this, [event], true)
657         }, {
658             checkBusyConflicts: checkBusyConflicts === false ? 0 : 1
659         });
660     },
661     
662     onUpdateEvent: function(event, pastChecked) {
663         this.setLoading(true);
664         
665         if(!pastChecked) {
666             this.checkPastEvent(event, null, 'update');
667             return;
668         }
669         
670         if (event.isRecurBase() && this.loadMask) {
671            this.loadMask.show();
672         }
673         
674         if (event.id && (event.isRecurInstance() || event.isRecurException() || (event.isRecurBase() && ! event.get('rrule').newrule))) {
675             Tine.widgets.dialog.MultiOptionsDialog.openWindow({
676                 title: this.app.i18n._('Update Event'),
677                 height: 170,
678                 scope: this,
679                 options: [
680                     {text: this.app.i18n._('Update this event only'), name: 'this'},
681                     {text: this.app.i18n._('Update this and all future events'), name: (event.isRecurBase() && ! event.get('rrule').newrule) ? 'series' : 'future'},
682                     {text: this.app.i18n._('Update whole series'), name: 'series'},
683                     {text: this.app.i18n._('Update nothing'), name: 'cancel'}
684                     
685                 ],
686                 handler: function(option) {
687                     var store = event.store;
688
689                     switch (option) {
690                         case 'series':
691                             if (this.loadMask) {
692                                 this.loadMask.show();
693                             }
694                             
695                             var options = {
696                                 scope: this,
697                                 success: function() {
698                                     store.load({refresh: true});
699                                 }
700                             };
701                             options.failure = this.onProxyFail.createDelegate(this, [event, Tine.Calendar.backend.updateRecurSeries.createDelegate(Tine.Calendar.backend, [event, false, options])], true);
702                             
703                             if (event.isRecurException()) {
704                                 Tine.Calendar.backend.saveRecord(event, options, {range: 'ALL', checkBusyConflicts: true});
705                             } else {
706                                 Tine.Calendar.backend.updateRecurSeries(event, true, options);
707                             }
708                             break;
709                             
710                         case 'this':
711                         case 'future':
712                             var options = {
713                                 scope: this,
714                                 success: function(updatedEvent) {
715                                     // NOTE: we also need to refresh for 'this' as otherwise
716                                     //       baseevent is not refreshed -> concurrency failures
717                                     store.load({refresh: true});
718                                 },
719                                 failure: this.onProxyFail.createDelegate(this, [event], true)
720                             };
721                             
722                             
723                             if (event.isRecurException()) {
724                                 var range = (option === 'this' ? 'THIS' : 'THISANDFUTURE');
725                                 options.failure = this.onProxyFail.createDelegate(this, [event, Tine.Calendar.backend.saveRecord.createDelegate(Tine.Calendar.backend, [event, options, {range: range, checkBusyConflicts: false}])], true);
726                                 Tine.Calendar.backend.saveRecord(event, options, {range: range, checkBusyConflicts: true});
727                             } else {
728                                 options.failure = this.onProxyFail.createDelegate(this, [event, Tine.Calendar.backend.createRecurException.createDelegate(Tine.Calendar.backend, [event, false, option == 'future', false, options])], true);
729                                 Tine.Calendar.backend.createRecurException(event, false, option === 'future', true, options);
730                             }
731                             break;
732                             
733                         default:
734                             if(this.loadMask) { //no loadMask called from another app
735                                 this.loadMask.show();
736                                 store.load({refresh: true});
737                             }
738                             break;
739                     }
740
741                 } 
742             });
743         } else {
744             this.onUpdateEventAction(event);
745         }
746     },
747     
748     onUpdateEventAction: function(event) {
749         var panel = this.getCalendarPanel(this.activeView),
750             store = panel.getStore(),
751             view = panel.getView();
752             
753         Tine.Calendar.backend.saveRecord(event, {
754             scope: this,
755             success: function(updatedEvent) {
756                 if (updatedEvent.isRecurBase()) {
757                     store.load({refresh: true});
758                 } else {
759                     this.congruenceFilterCheck(event, updatedEvent);
760                 }
761             },
762             failure: this.onProxyFail.createDelegate(this, [event], true)
763         }, {
764             checkBusyConflicts: 1
765         });
766     },
767
768     /**
769      * checks, if the last filter still matches after update
770      * 
771      * @param {Tine.Calendar.Model.Event} event
772      * @param {Tine.Calendar.Model.Event} updatedEvent
773      */
774     congruenceFilterCheck: function(event, updatedEvent) {
775         if (! (event.ui && event.ui.rendered)) {
776             event.store.replaceRecord(event, updatedEvent);
777             this.setLoading(false);
778             return
779         }
780         
781         var filterData = this.getAllFilterData(),
782             store = event.store;
783         
784         filterData[0].filters[0].filters.push({field: 'id', operator: 'in', value: [ updatedEvent.get('id') ]});
785         
786         Tine.Calendar.searchEvents(filterData, {}, function(r) {
787             if(r.totalcount == 0) {
788                 updatedEvent.outOfFilter = true;
789             }
790             
791             store.replaceRecord(event, updatedEvent);
792             this.setLoading(false);
793         }, this);
794     },
795     
796     onDeleteRecords: function () {
797         var panel = this.getCalendarPanel(this.activeView);
798         var selection = panel.getSelectionModel().getSelectedEvents();
799         
800         var containsRecurBase = false,
801             containsRecurInstance = false,
802             containsRecurException = false;
803         
804         Ext.each(selection, function (event) {
805             if(event.ui) event.ui.markDirty();
806             if (event.isRecurInstance()) {
807                 containsRecurInstance = true;
808             }
809             if (event.isRecurBase()) {
810                 containsRecurBase = true;
811             }
812             if (event.isRecurException()) {
813                 containsRecurException = true;
814             }
815         });
816         
817         if (selection.length > 1 && (containsRecurBase || containsRecurInstance)) {
818             Ext.Msg.show({
819                 title: this.app.i18n._('Please Change Selection'), 
820                 msg: this.app.i18n._('Your selection contains recurring events. Recuring events must be deleted seperatly!'),
821                 icon: Ext.MessageBox.INFO,
822                 buttons: Ext.Msg.OK,
823                 scope: this,
824                 fn: function () {
825                     this.onDeleteRecordsConfirmFail(panel, selection);
826                 }
827             });
828             return;
829         }
830         
831         if (selection.length === 1 && (containsRecurBase || containsRecurInstance || containsRecurException)) {
832             this.deleteMethodWin = Tine.widgets.dialog.MultiOptionsDialog.openWindow({
833                 title: this.app.i18n._('Delete Event'),
834                 scope: this,
835                 height: 170,
836                 options: [
837                     {text: this.app.i18n._('Delete this event only'), name: 'this'},
838                     {text: this.app.i18n._('Delete this and all future events'), name: containsRecurBase ? 'all' : 'future'},
839                     {text: this.app.i18n._('Delete whole series'), name: 'all'},
840                     {text: this.app.i18n._('Delete nothing'), name: 'nothing'}
841                 ],
842                 handler: function (option) {
843                     try {
844                         switch (option) {
845                             case 'all':
846                             case 'this':
847                             case 'future':
848                                 panel.getTopToolbar().beforeLoad();
849                                 if (option !== 'this') {
850                                     this.loadMask.show();
851                                 }
852                                 
853                                 var options = {
854                                     scope: this,
855                                     success: function () {
856                                         if (option === 'this') {
857                                             Ext.each(selection, function (event) {
858                                                 panel.getStore().remove(event);
859                                             });
860                                         }
861                                         this.refresh(true);
862                                     }
863                                 };
864                                 
865                                 if (containsRecurException) {
866                                     var range = (option === 'future') ? 'THISANDFUTURE' : Ext.util.Format.uppercase(option);
867                                     Tine.Calendar.backend.deleteRecords([selection[0]], options, {range: range});
868                                 } else {
869                                     if (option === 'all') {
870                                         Tine.Calendar.backend.deleteRecurSeries(selection[0], options);
871                                     } else {
872                                         Tine.Calendar.backend.createRecurException(selection[0], true, option === 'future', false, options);
873                                     }
874                                 }
875                                 break;
876                             default:
877                                 this.onDeleteRecordsConfirmFail(panel, selection);
878                                 break;
879                     }
880                     
881                     } catch (e) {
882                         Tine.log.error('Tine.Calendar.MainScreenCenterPanel::onDeleteRecords::handle');
883                         Tine.log.error(e);
884                     }
885                 }
886             });
887             return;
888         }
889         
890         // else
891         var i18nQuestion = String.format(this.app.i18n.ngettext('Do you really want to delete this event?', 'Do you really want to delete the {0} selected events?', selection.length), selection.length);
892         Ext.MessageBox.confirm(Tine.Tinebase.translation._hidden('Confirm'), i18nQuestion, function (btn) {
893             if (btn === 'yes') {
894                 this.onDeleteRecordsConfirmNonRecur(panel, selection);
895             } else {
896                 this.onDeleteRecordsConfirmFail(panel, selection);
897             }
898         }, this);
899         
900     },
901     
902     onDeleteRecordsConfirmNonRecur: function (panel, selection) {
903         panel.getTopToolbar().beforeLoad();
904         
905         // create a copy of selection so selection changes don't affect this
906         var sel = Ext.unique(selection);
907                 
908         var options = {
909             scope: this,
910             success: function () {
911                 panel.getTopToolbar().onLoad();
912                 Ext.each(sel, function (event) {
913                     panel.getStore().remove(event);
914                 });
915             },
916             failure: function () {
917                 panel.getTopToolbar().onLoad();
918                 Ext.MessageBox.alert(Tine.Tinebase.translation._hidden('Failed'), String.format(this.app.i18n.n_('Failed to delete event', 'Failed to delete the {0} events', selection.length), selection.length));
919             }
920         };
921         
922         Tine.Calendar.backend.deleteRecords(selection, options);
923     },
924     
925     onDeleteRecordsConfirmFail: function (panel, selection) {
926         Ext.each(selection, function (event) {
927             event.ui.clearDirty();
928         });
929     },
930     
931     /**
932      * open event in new window
933      * 
934      * @param {String} action  add|edit|copy
935      * @param {String} event   edit given event instead of selected event
936      * @param {Object} plugins event dlg plugins
937      * @param {Object} default properties for new items
938      */
939     onEditInNewWindow: function (action, event, plugins, defaults) {
940         if (! event) {
941             event = null;
942         }
943         // needed for addToEventPanel
944         if (Ext.isObject(action)) {
945             action = action.actionType;
946         }
947         
948         if (action === 'edit' && ! event) {
949             var panel = this.getCalendarPanel(this.activeView);
950             var selection = panel.getSelectionModel().getSelectedEvents();
951             if (Ext.isArray(selection) && selection.length === 1) {
952                 event = selection[0];
953             }
954         }
955
956         if (action === 'edit' && (! event || event.dirty)) {
957             return;
958         }
959         
960         if (! event) {
961             event = new Tine.Calendar.Model.Event(Ext.apply(Tine.Calendar.Model.Event.getDefaultData(), defaults), 0);
962             if (defaults && Ext.isDate(defaults.dtStart)) {
963                 event.set('dtstart', defaults.dtStart);
964                 event.set('dtend', defaults.dtStart.add(Date.HOUR, 1));
965             }
966         }
967         
968         Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::onEditInNewWindow() - Opening event edit dialog with action: ' + action);
969         
970         Tine.Calendar.EventEditDialog.openWindow({
971             plugins: plugins ? Ext.encode(plugins) : null,
972             record: Ext.encode(event.data),
973             recordId: event.data.id,
974             copyRecord: (action == 'copy'),
975             listeners: {
976                 scope: this,
977                 update: function (eventJson) {
978                     var updatedEvent = Tine.Calendar.backend.recordReader({responseText: eventJson});
979                     
980                     updatedEvent.dirty = true;
981                     updatedEvent.modified = {};
982                     event.phantom = (action === 'edit');
983                     var panel = this.getCalendarPanel(this.activeView);
984                     var store = panel.getStore();
985                     event = store.getById(event.id);
986                     
987                     if (event && action !== 'copy') {
988                         store.replaceRecord(event, updatedEvent);
989                     } else {
990                         store.add(updatedEvent);
991                     }
992                     
993                     this.onUpdateEvent(updatedEvent, false);
994                 }
995             }
996         });
997     },
998     
999     onKeyDown: function (e) {
1000         if (e.ctrlKey) {
1001             switch (e.getKey()) 
1002             {
1003             case e.A:
1004                 // select only current page
1005                 //this.grid.getSelectionModel().selectAll(true);
1006                 e.preventDefault();
1007                 break;
1008             case e.E:
1009                 if (!this.action_editInNewWindow.isDisabled()) {
1010                     this.onEditInNewWindow('edit');
1011                 }
1012                 e.preventDefault();
1013                 break;
1014             case e.N:
1015                 if (!this.action_addInNewWindow.isDisabled()) {
1016                     this.onEditInNewWindow('add');
1017                 }
1018                 e.preventDefault();
1019                 break;
1020             }
1021         } else if (e.getKey() === e.DELETE) {
1022             if (! this.action_deleteRecord.isDisabled()) {
1023                 this.onDeleteRecords.call(this);
1024             }
1025         }
1026     },
1027     
1028     onPrint: function() {
1029         var panel = this.getCalendarPanel(this.activeView),
1030             view = panel ? panel.getView() : null;
1031             
1032         if (view && Ext.isFunction(view.print)) {
1033             view.print();
1034         } else {
1035             Ext.Msg.alert(this.app.i18n._('Could not Print'), this.app.i18n._('Sorry, your current view does not support printing.'));
1036         }
1037     },
1038     
1039     /**
1040      * called before store queries for data
1041      */
1042     onStoreBeforeload: function (store, options) {
1043         options.params = options.params || {};
1044         
1045         // define a transaction
1046         this.lastStoreTransactionId = options.transactionId = Ext.id();
1047         
1048         // allways start with an empty filter set!
1049         // this is important for paging and sort header!
1050         options.params.filter = [];
1051         
1052         if (! options.refresh && this.rendered) {
1053             // defer to have the loadMask centered in case of rendering actions
1054             this.loadMask.show.defer(50, this.loadMask);
1055         }
1056         
1057         // note, we can't use the 'normal' plugin approach here, cause we have to deal with n stores
1058         //var calendarSelectionPlugin = this.app.getMainScreen().getWestPanel().getContainerTreePanel().getFilterPlugin();
1059         //calendarSelectionPlugin.onBeforeLoad.call(calendarSelectionPlugin, store, options);
1060         
1061         this.filterToolbar.onBeforeLoad.call(this.filterToolbar, store, options);
1062         
1063         // add period filter as last filter to not conflict with first OR filter
1064         if (! options.noPeriodFilter) {
1065             options.params.filter.push({field: 'period', operator: 'within', value: this.getCalendarPanel(this.activeView).getView().getPeriod() });
1066         }
1067     },
1068     
1069     /**
1070      * fence against loading of wrong data set
1071      */
1072     onStoreBeforeLoadRecords: function(o, options, success) {
1073         return this.lastStoreTransactionId === options.transactionId;
1074     },
1075     
1076     /**
1077      * called when store loaded data
1078      */
1079     onStoreLoad: function (store, options) {
1080         if (this.rendered) {
1081             this.loadMask.hide();
1082         }
1083         
1084         // reset autoRefresh
1085         if (window.isMainWindow && this.autoRefreshInterval) {
1086             this.autoRefreshTask.delay(this.autoRefreshInterval * 1000);
1087         }
1088         
1089         // check if store is current store
1090         if (store !== this.getCalendarPanel(this.activeView).getStore()) {
1091             Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::onStoreLoad view is not active anymore');
1092             return;
1093         }
1094         
1095         if (this.rendered) {
1096             // update filtertoolbar
1097             if(this.filterToolbar) {
1098                 this.filterToolbar.setValue(store.proxy.jsonReader.jsonData.filter);
1099             }
1100             // update container tree
1101             Tine.Tinebase.appMgr.get('Calendar').getMainScreen().getWestPanel().getContainerTreePanel().getFilterPlugin().setValue(store.proxy.jsonReader.jsonData.filter);
1102             
1103             // update attendee filter grid
1104             Tine.Tinebase.appMgr.get('Calendar').getMainScreen().getWestPanel().getAttendeeFilter().setFilterValue(store.proxy.jsonReader.jsonData.filter);
1105         }
1106     },
1107     
1108     /**
1109      * on store load exception
1110      * 
1111      * @param {Tine.Tinebase.data.RecordProxy} proxy
1112      * @param {String} type
1113      * @param {Object} error
1114      * @param {Object} options
1115      */
1116     onStoreLoadException: function(proxy, type, error, options) {
1117         // reset autoRefresh
1118         if (window.isMainWindow && this.autoRefreshInterval) {
1119             this.autoRefreshTask.delay(this.autoRefreshInterval * 5000);
1120         }
1121         
1122         this.setLoading(false);
1123         this.loadMask.hide();
1124         
1125         if (! options.autoRefresh) {
1126             this.onProxyFail(error);
1127         }
1128     },
1129     
1130     onProxyFail: function(error, event, ignoreFn) {
1131         this.setLoading(false);
1132         if(this.loadMask) this.loadMask.hide();
1133         
1134         if (error.code == 901) {
1135            
1136             // resort fbInfo to combine all events of a attender
1137             var busyAttendee = [];
1138             var conflictEvents = {};
1139             var attendeeStore = Tine.Calendar.Model.Attender.getAttendeeStore(error.event.attendee);
1140             
1141             Ext.each(error.freebusyinfo, function(fbinfo) {
1142                 attendeeStore.each(function(a) {
1143                     var userType = a.get('user_type');
1144                     userType = userType == 'groupmember' ? 'user' : userType;
1145                     if (userType == fbinfo.user_type && a.getUserId() == fbinfo.user_id) {
1146                         if (busyAttendee.indexOf(a) < 0) {
1147                             busyAttendee.push(a);
1148                             conflictEvents[a.id] = [];
1149                         }
1150                         conflictEvents[a.id].push(fbinfo);
1151                     }
1152                 });
1153             }, this);
1154             
1155             // generate html for each busy attender
1156             var busyAttendeeHTML = '';
1157             Ext.each(busyAttendee, function(busyAttender) {
1158                 // TODO refactore name handling of attendee
1159                 //      -> attender model needs knowlege of how to get names!
1160                 //var attenderName = a.getName();
1161                 var attenderName = Tine.Calendar.AttendeeGridPanel.prototype.renderAttenderName.call(Tine.Calendar.AttendeeGridPanel.prototype, busyAttender.get('user_id'), false, busyAttender);
1162                 busyAttendeeHTML += '<div class="cal-conflict-attendername">' + attenderName + '</div>';
1163                 
1164                 var eventInfos = [];
1165                 Ext.each(conflictEvents[busyAttender.id], function(fbInfo) {
1166                     var format = 'H:i';
1167                     var dateFormat = Ext.form.DateField.prototype.format;
1168                     if (event.get('dtstart').format(dateFormat) != event.get('dtend').format(dateFormat) ||
1169                         Date.parseDate(fbInfo.dtstart, Date.patterns.ISO8601Long).format(dateFormat) != Date.parseDate(fbInfo.dtend, Date.patterns.ISO8601Long).format(dateFormat))
1170                     {
1171                         format = dateFormat + ' ' + format;
1172                     }
1173                     
1174                     var eventInfo = Date.parseDate(fbInfo.dtstart, Date.patterns.ISO8601Long).format(format) + ' - ' + Date.parseDate(fbInfo.dtend, Date.patterns.ISO8601Long).format(format);
1175                     if (fbInfo.event && fbInfo.event.summary) {
1176                         eventInfo += ' : ' + fbInfo.event.summary;
1177                     }
1178                     eventInfos.push(eventInfo);
1179                 }, this);
1180                 busyAttendeeHTML += '<div class="cal-conflict-eventinfos">' + eventInfos.join(', <br />') + '</div>';
1181                 
1182             });
1183             
1184             this.conflictConfirmWin = Tine.widgets.dialog.MultiOptionsDialog.openWindow({
1185                 modal: true,
1186                 allowCancel: false,
1187                 height: 180 + 15*error.freebusyinfo.length,
1188                 title: this.app.i18n._('Scheduling Conflict'),
1189                 questionText: '<div class = "cal-conflict-heading">' +
1190                                    this.app.i18n._('The following attendee are busy at the requested time:') + 
1191                                '</div>' +
1192                                busyAttendeeHTML,
1193                 options: [
1194                     {text: this.app.i18n._('Ignore Conflict'), name: 'ignore'},
1195                     {text: this.app.i18n._('Edit Event'), name: 'edit', checked: true},
1196                     {text: this.app.i18n._('Cancel this action'), name: 'cancel'}
1197                 ],
1198                 scope: this,
1199                 handler: function(option) {
1200                     var panel = this.getCalendarPanel(this.activeView),
1201                         store = panel.getStore();
1202
1203                     switch (option) {
1204                         case 'ignore':
1205                             if (Ext.isFunction(ignoreFn)) {
1206                                 ignoreFn();
1207                             } else {
1208                                 this.onAddEvent(event, false, true);
1209                             }
1210                             this.conflictConfirmWin.close();
1211                             break;
1212                         
1213                         case 'edit':
1214                             
1215                             var presentationMatch = this.activeView.match(this.presentationRe),
1216                                 presentation = Ext.isArray(presentationMatch) ? presentationMatch[0] : null;
1217                             
1218                             if (presentation != 'Grid') {
1219                                 var view = panel.getView();
1220                                 view.getSelectionModel().select(event);
1221                                 // mark event as not dirty to allow edit dlg
1222                                 event.dirty = false;
1223                                 view.fireEvent('dblclick', view, event);
1224                             } else {
1225                                 // add or edit?
1226                                 this.onEditInNewWindow(null, event);
1227                             }
1228                             
1229                             this.conflictConfirmWin.close();
1230                             break;
1231                         case 'cancel':
1232                         default:
1233                             this.conflictConfirmWin.close();
1234                             this.loadMask.show();
1235                             store.load({refresh: true});
1236                             break;
1237                     }
1238                 }
1239             });
1240             
1241         } else {
1242             Tine.Tinebase.ExceptionHandler.handleRequestException(error);
1243         }
1244     },
1245     
1246     refresh: function (options) {
1247         // convert old boolean argument
1248         options = Ext.isObject(options) ? options : {
1249             refresh: !!options
1250         };
1251         Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::refresh(' + options.refresh + ')');
1252         var panel = this.getCalendarPanel(this.activeView);
1253         panel.getStore().load(options);
1254         
1255         // clear favorites
1256         Tine.Tinebase.appMgr.get('Calendar').getMainScreen().getWestPanel().getFavoritesPanel().getSelectionModel().clearSelections();
1257     },
1258     
1259     setLoading: function(bool) {
1260         if (this.rendered) {
1261             var panel = this.getCalendarPanel(this.activeView),
1262                 tbar = panel.getTopToolbar();
1263                 
1264             if (tbar && tbar.loading) {
1265                 tbar.loading[bool ? 'disable' : 'enable']();
1266             }
1267         }
1268     },
1269     
1270     setResponseStatus: function(event, status) {
1271         var myAttenderRecord = event.getMyAttenderRecord();
1272         if (myAttenderRecord) {
1273             myAttenderRecord.set('status', status);
1274             event.dirty = true;
1275             event.modified = {};
1276             
1277             var panel = this.getCalendarPanel(this.activeView);
1278             var store = panel.getStore();
1279             
1280             store.replaceRecord(event, event);
1281             
1282             this.onUpdateEvent(event);
1283         }
1284     },
1285     
1286     updateEventActions: function () {
1287         var panel = this.getCalendarPanel(this.activeView);
1288         var selection = panel.getSelectionModel().getSelectedEvents();
1289         
1290         this.actionUpdater.updateActions(selection);
1291         if (this.detailsPanel) {
1292             this.detailsPanel.onDetailsUpdate(panel.getSelectionModel());
1293         }
1294     },
1295     
1296     updateView: function (which) {
1297         Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::updateView(' + which + ')');
1298         var panel = this.getCalendarPanel(which);
1299         var period = panel.getTopToolbar().getPeriod();
1300         
1301         panel.getView().updatePeriod(period);
1302         panel.getStore().load({});
1303         //this.updateMiniCal();
1304     },
1305     
1306     /**
1307      * returns requested CalendarPanel
1308      * 
1309      * @param {String} which
1310      * @return {Tine.Calendar.CalendarPanel}
1311      */
1312     getCalendarPanel: function (which) {
1313         var whichParts = this.getViewParts(which);
1314         which = whichParts.toString();
1315         
1316         if (! this.calendarPanels[which]) {
1317             Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::getCalendarPanel creating new calender panel for view ' + which);
1318             
1319             var store = new Ext.data.JsonStore({
1320                 //autoLoad: true,
1321                 id: 'id',
1322                 fields: Tine.Calendar.Model.Event,
1323                 proxy: Tine.Calendar.backend,
1324                 reader: new Ext.data.JsonReader({}), //Tine.Calendar.backend.getReader(),
1325                 listeners: {
1326                     scope: this,
1327                     'beforeload': this.onStoreBeforeload,
1328                     'beforeloadrecords' : this.onStoreBeforeLoadRecords,
1329                     'load': this.onStoreLoad,
1330                     'loadexception': this.onStoreLoadException
1331                 },
1332                 replaceRecord: function(o, n) {
1333                     var idx = this.indexOf(o);
1334                     this.remove(o);
1335                     this.insert(idx, n);
1336                 }
1337             });
1338             
1339             var tbar = new Tine.Calendar.PagingToolbar({
1340                 view: whichParts.period,
1341                 store: store,
1342                 dtStart: this.startDate,
1343                 listeners: {
1344                     scope: this,
1345                     // NOTE: only render the button once for the toolbars
1346                     //       the buttons will be moved on chageView later
1347                     render: function (tbar) {
1348                         for (var i = 0; i < this.changeViewActions.length; i += 1) {
1349                             if (! this.changeViewActions[i].rendered) {
1350                                 tbar.addButton(this.changeViewActions[i]);
1351                             }
1352                         }
1353                     }
1354                 }
1355             });
1356             
1357             tbar.on('change', this.updateView.createDelegate(this, [which]), this, {buffer: 200});
1358             tbar.on('refresh', this.refresh.createDelegate(this, [true]), this, {buffer: 200});
1359             
1360             if (whichParts.presentation.match(/sheet/i)) {
1361                 var view;
1362                 switch (which) {
1363                     case 'daySheet':
1364                         view = new Tine.Calendar.DaysView({
1365                             store: store,
1366                             startDate: tbar.getPeriod().from,
1367                             numOfDays: 1
1368                         });
1369                         break;
1370                     case 'monthSheet':
1371                         view = new Tine.Calendar.MonthView({
1372                             store: store,
1373                             period: tbar.getPeriod()
1374                         });
1375                         break;
1376                     default:
1377                     case 'weekSheet':
1378                         view = new Tine.Calendar.DaysView({
1379                             store: store,
1380                             startDate: tbar.getPeriod().from,
1381                             numOfDays: 7
1382                         });
1383                         break;
1384                 }
1385                 
1386                 view.on('changeView', this.changeView, this);
1387                 view.on('changePeriod', function (period) {
1388                     this.startDate = period.from;
1389                     this.startDates[which] = this.startDate.clone();
1390                     this.updateMiniCal();
1391                 }, this);
1392                 
1393                 // quick add/update actions
1394                 view.on('addEvent', this.onAddEvent, this);
1395                 view.on('updateEvent', this.onUpdateEvent, this);
1396             
1397                 this.calendarPanels[which] = new Tine.Calendar.CalendarPanel({
1398                     tbar: tbar,
1399                     view: view
1400                 });
1401             } else if (whichParts.presentation.match(/grid/i)) {
1402                 this.calendarPanels[which] = new Tine.Calendar.GridView({
1403                     tbar: tbar,
1404                     store: store
1405                 });
1406             }
1407             
1408             this.calendarPanels[which].on('dblclick', this.onEditInNewWindow.createDelegate(this, ["edit"]));
1409             this.calendarPanels[which].on('contextmenu', this.onContextMenu, this);
1410             this.calendarPanels[which].getSelectionModel().on('selectionchange', this.updateEventActions, this);
1411             this.calendarPanels[which].on('keydown', this.onKeyDown, this);
1412             
1413             this.calendarPanels[which].relayEvents(this, ['show', 'beforehide']);
1414         }
1415         
1416         return this.calendarPanels[which];
1417     },
1418     
1419     updateMiniCal: function () {
1420         var miniCal = Ext.getCmp('cal-mainscreen-minical');
1421         var weekNumbers = null;
1422         var period = this.getCalendarPanel(this.activeView).getView().getPeriod();
1423         
1424         switch (this.activeView) 
1425         {
1426         case 'week' :
1427             weekNumbers = [period.from.add(Date.DAY, 1).getWeekOfYear()];
1428             break;
1429         case 'month' :
1430             weekNumbers = [];
1431             var startWeek = period.from.add(Date.DAY, 1).getWeekOfYear();
1432             var numWeeks = Math.round((period.until.getTime() - period.from.getTime()) / Date.msWEEK);
1433             for (var i = 0; i < numWeeks; i += 1) {
1434                 weekNumbers.push(startWeek + i);
1435             }
1436             break;
1437         }
1438         miniCal.update(this.startDate, true, weekNumbers);
1439     }
1440 });