Revert "Crop daytimes"
[tine20] / tine20 / Calendar / js / EventEditDialog.js
1 /*
2  * Tine 2.0
3  * 
4  * @package     Calendar
5  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
6  * @author      Cornelius Weiss <c.weiss@metaways.de>
7  * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
8  *
9  */
10  
11 Ext.ns('Tine.Calendar');
12
13 /**
14  * @namespace Tine.Calendar
15  * @class Tine.Calendar.EventEditDialog
16  * @extends Tine.widgets.dialog.EditDialog
17  * Calendar Edit Dialog <br>
18  * 
19  * @author      Cornelius Weiss <c.weiss@metaways.de>
20  */
21 Tine.Calendar.EventEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
22     /**
23      * @cfg {Number} containerId initial container id
24      */
25     containerId: -1,
26     
27     labelAlign: 'side',
28     windowNamePrefix: 'EventEditWindow_',
29     appName: 'Calendar',
30     recordClass: Tine.Calendar.Model.Event,
31     recordProxy: Tine.Calendar.backend,
32     showContainerSelector: false,
33     tbarItems: [{xtype: 'widget-activitiesaddbutton'}],
34     
35     /**
36      * hide relations panel because we can't load them on demand yet
37      * 
38      * @see 0009412: event loses saved relations on reload
39      * @type Boolean
40      */
41     hideRelationsPanel: true,
42     
43     mode: 'local',
44     
45     // note: we need up use new action updater here or generally in the widget!
46     evalGrants: false,
47     
48     onResize: function() {
49         Tine.Calendar.EventEditDialog.superclass.onResize.apply(this, arguments);
50         this.setTabHeight.defer(100, this);
51     },
52     
53     /**
54      * returns dialog
55      * 
56      * NOTE: when this method gets called, all initalisation is done.
57      * @return {Object} components this.itmes definition
58      */
59     getFormItems: function() {
60         return {
61             xtype: 'tabpanel',
62             border: false,
63             plugins: [{
64                 ptype : 'ux.tabpanelkeyplugin'
65             }],
66             defaults: {
67                 hideMode: 'offsets'
68             },
69             plain:true,
70             activeTab: 0,
71             border: false,
72             items:[{
73                 title: this.app.i18n.n_('Event', 'Events', 1),
74                 border: false,
75                 frame: true,
76                 layout: 'border',
77                 items: [{
78                     region: 'center',
79                     layout: 'hfit',
80                     border: false,
81                     items: [{
82                         layout: 'hbox',
83                         items: [{
84                             margins: '5',
85                             width: 100,
86                             xtype: 'label',
87                             text: this.app.i18n._('Summary')
88                         }, {
89                             flex: 1,
90                             xtype:'textfield',
91                             name: 'summary',
92                             listeners: {render: function(field){field.focus(false, 250);}},
93                             allowBlank: false,
94                             requiredGrant: 'editGrant',
95                             maxLength: 255
96                         }]
97                     }, {
98                         layout: 'hbox',
99                         items: [{
100                             margins: '5',
101                             width: 100,
102                             xtype: 'label',
103                             text: this.app.i18n._('View')
104                         }, Ext.apply(this.perspectiveCombo, {
105                             flex: 1
106                         })]
107                     }, {
108                         layout: 'hbox',
109                         height: 115,
110                         layoutConfig: {
111                             align : 'stretch',
112                             pack  : 'start'
113                         },
114                         items: [{
115                             flex: 1,
116                             xtype: 'fieldset',
117                             layout: 'hfit',
118                             margins: '0 5 0 0',
119                             title: this.app.i18n._('Details'),
120                             items: [{
121                                 xtype: 'columnform',
122                                 labelAlign: 'side',
123                                 labelWidth: 100,
124                                 formDefaults: {
125                                     xtype:'textfield',
126                                     anchor: '100%',
127                                     labelSeparator: '',
128                                     columnWidth: .7
129                                 },
130                                 items: [[{
131                                     columnWidth: 1,
132                                     fieldLabel: this.app.i18n._('Location'),
133                                     name: 'location',
134                                     requiredGrant: 'editGrant',
135                                     maxLength: 255
136                                 }], [{
137                                     xtype: 'datetimefield',
138                                     fieldLabel: this.app.i18n._('Start Time'),
139                                     listeners: {scope: this, change: this.onDtStartChange},
140                                     name: 'dtstart',
141                                     requiredGrant: 'editGrant'
142                                 }, {
143                                     columnWidth: .19,
144                                     xtype: 'checkbox',
145                                     hideLabel: true,
146                                     boxLabel: this.app.i18n._('whole day'),
147                                     listeners: {scope: this, check: this.onAllDayChange},
148                                     name: 'is_all_day_event',
149                                     requiredGrant: 'editGrant'
150                                 }], [{
151                                     xtype: 'datetimefield',
152                                     fieldLabel: this.app.i18n._('End Time'),
153                                     listeners: {scope: this, change: this.onDtEndChange},
154                                     name: 'dtend',
155                                     requiredGrant: 'editGrant'
156                                 }, {
157                                     columnWidth: .3,
158                                     xtype: 'combo',
159                                     hideLabel: true,
160                                     readOnly: true,
161                                     hideTrigger: true,
162                                     disabled: true,
163                                     name: 'originator_tz',
164                                     requiredGrant: 'editGrant'
165                                 }], [ this.containerSelectCombo = new Tine.widgets.container.selectionComboBox({
166                                     columnWidth: 1,
167                                     id: this.app.appName + 'EditDialogContainerSelector' + Ext.id(),
168                                     fieldLabel: _('Saved in'),
169                                     ref: '../../../../../../../../containerSelect',
170                                     //width: 300,
171                                     //listWidth: 300,
172                                     name: this.recordClass.getMeta('containerProperty'),
173                                     recordClass: this.recordClass,
174                                     containerName: this.app.i18n.n_hidden(this.recordClass.getMeta('containerName'), this.recordClass.getMeta('containersName'), 1),
175                                     containersName: this.app.i18n._hidden(this.recordClass.getMeta('containersName')),
176                                     appName: this.app.appName,
177                                     requiredGrant: this.record.data.id ? ['editGrant'] : ['addGrant'],
178                                     disabled: true
179                                 }), Ext.apply(this.perspectiveCombo.getAttendeeContainerField(), {
180                                     columnWidth: 1
181                                 })]]
182                             }]
183                         }, {
184                             width: 130,
185                             xtype: 'fieldset',
186                             title: this.app.i18n._('Status'),
187                             items: [{
188                                 xtype: 'checkbox',
189                                 hideLabel: true,
190                                 boxLabel: this.app.i18n._('non-blocking'),
191                                 name: 'transp',
192                                 requiredGrant: 'editGrant',
193                                 getValue: function() {
194                                     var bool = Ext.form.Checkbox.prototype.getValue.call(this);
195                                     return bool ? 'TRANSPARENT' : 'OPAQUE';
196                                 },
197                                 setValue: function(value) {
198                                     var bool = (value == 'TRANSPARENT' || value === true);
199                                     return Ext.form.Checkbox.prototype.setValue.call(this, bool);
200                                 }
201                             }, Ext.apply(this.perspectiveCombo.getAttendeeTranspField(), {
202                                 hideLabel: true
203                             }), {
204                                 xtype: 'checkbox',
205                                 hideLabel: true,
206                                 boxLabel: this.app.i18n._('Tentative'),
207                                 name: 'status',
208                                 requiredGrant: 'editGrant',
209                                 getValue: function() {
210                                     var bool = Ext.form.Checkbox.prototype.getValue.call(this);
211                                     return bool ? 'TENTATIVE' : 'CONFIRMED';
212                                 },
213                                 setValue: function(value) {
214                                     var bool = (value == 'TENTATIVE' || value === true);
215                                     return Ext.form.Checkbox.prototype.setValue.call(this, bool);
216                                 }
217                             }, {
218                                 xtype: 'checkbox',
219                                 hideLabel: true,
220                                 boxLabel: this.app.i18n._('Private'),
221                                 name: 'class',
222                                 requiredGrant: 'editGrant',
223                                 getValue: function() {
224                                     var bool = Ext.form.Checkbox.prototype.getValue.call(this);
225                                     return bool ? 'PRIVATE' : 'PUBLIC';
226                                 },
227                                 setValue: function(value) {
228                                     var bool = (value == 'PRIVATE' || value === true);
229                                     return Ext.form.Checkbox.prototype.setValue.call(this, bool);
230                                 }
231                             }, Ext.apply(this.perspectiveCombo.getAttendeeStatusField(), {
232                                 width: 115,
233                                 hideLabel: true
234                             })]
235                         }]
236                     }, {
237                         xtype: 'tabpanel',
238                         deferredRender: false,
239                         activeTab: 0,
240                         border: false,
241                         height: 235,
242                         form: true,
243                         items: [
244                             this.attendeeGridPanel,
245                             this.rrulePanel,
246                             this.alarmPanel
247                         ]
248                     }]
249                 }, {
250                     // activities and tags
251                     region: 'east',
252                     layout: 'accordion',
253                     animate: true,
254                     width: 200,
255                     split: true,
256                     collapsible: true,
257                     collapseMode: 'mini',
258                     header: false,
259                     margins: '0 5 0 5',
260                     border: true,
261                     items: [
262                         new Ext.Panel({
263                             // @todo generalise!
264                             title: this.app.i18n._('Description'),
265                             iconCls: 'descriptionIcon',
266                             layout: 'form',
267                             labelAlign: 'top',
268                             border: false,
269                             items: [{
270                                 style: 'margin-top: -4px; border 0px;',
271                                 labelSeparator: '',
272                                 xtype:'textarea',
273                                 name: 'description',
274                                 hideLabel: true,
275                                 grow: false,
276                                 preventScrollbars:false,
277                                 anchor:'100% 100%',
278                                 emptyText: this.app.i18n._('Enter description'),
279                                 requiredGrant: 'editGrant'                           
280                             }]
281                         }),
282                         new Tine.widgets.activities.ActivitiesPanel({
283                             app: 'Calendar',
284                             showAddNoteForm: false,
285                             border: false,
286                             bodyStyle: 'border:1px solid #B5B8C8;'
287                         }),
288                         new Tine.widgets.tags.TagPanel({
289                             app: 'Calendar',
290                             border: false,
291                             bodyStyle: 'border:1px solid #B5B8C8;'
292                         })
293                     ]
294                 }]
295             }, new Tine.widgets.activities.ActivitiesTabPanel({
296                 app: this.appName,
297                 record_id: (this.record) ? this.record.id : '',
298                 record_model: this.appName + '_Model_' + this.recordClass.getMeta('modelName')
299             })]
300         };
301     },
302     
303     initComponent: function() {
304         var organizerCombo;
305         this.attendeeGridPanel = new Tine.Calendar.AttendeeGridPanel({
306             bbar: [{
307                 xtype: 'label',
308                 html: Tine.Tinebase.appMgr.get('Calendar').i18n._('Organizer') + "&nbsp;"
309             }, organizerCombo = Tine.widgets.form.RecordPickerManager.get('Addressbook', 'Contact', {
310                 width: 300,
311                 name: 'organizer',
312                 userOnly: true,
313                 getValue: function() {
314                     var id = Tine.Addressbook.SearchCombo.prototype.getValue.apply(this, arguments),
315                         record = this.store.getById(id);
316                         
317                     return record ? record.data : id;
318                 }
319             })]
320         });
321         
322         // auto location
323         this.attendeeGridPanel.on('afteredit', function(o) {
324             if (o.field == 'user_id'
325                 && o.record.get('user_type') == 'resource'
326                 && o.record.get('user_id')
327                 && o.record.get('user_id').is_location
328             ) {
329                 this.getForm().findField('location').setValue(
330                     this.attendeeGridPanel.renderAttenderResourceName(o.record.get('user_id'))
331                 );
332             }
333         }, this);
334         
335         this.on('render', function() {this.getForm().add(organizerCombo);}, this);
336         
337         this.rrulePanel = new Tine.Calendar.RrulePanel({});
338         this.alarmPanel = new Tine.widgets.dialog.AlarmPanel({});
339         this.attendeeStore = this.attendeeGridPanel.getStore();
340         
341         // a combo with all attendee + origin/organizer
342         this.perspectiveCombo = new Tine.Calendar.PerspectiveCombo({
343             editDialog: this
344         });
345         
346         Tine.Calendar.EventEditDialog.superclass.initComponent.call(this);
347         
348         this.addAttendee();
349     },
350
351     /**
352      * if this addRelations is set, iterate and create attendee
353      */
354     addAttendee: function() {
355         var attendee = this.record.get('attendee');
356         var attendee = Ext.isArray(attendee) ? attendee : [];
357         
358         if (Ext.isArray(this.plugins)) {
359             for (var index = 0; index < this.plugins.length; index++) {
360                 if (this.plugins[index].hasOwnProperty('addRelations')) {
361
362                     var config = this.plugins[index].hasOwnProperty('relationConfig') ? this.plugins[index].relationConfig : {};
363                     
364                     for (var index2 = 0; index2 < this.plugins[index].addRelations.length; index2++) {
365                         var item = this.plugins[index].addRelations[index2];
366                         var attender = Ext.apply({
367                             user_type: 'user',
368                             role: 'REQ',
369                             quantity: 1,
370                             status: 'NEEDS-ACTION',
371                             user_id: item
372                         }, config);
373                         
374                         attendee.push(attender);
375                     }
376                 }
377             }
378         }
379         
380         this.record.set('attendee', attendee);
381     },
382     
383     /**
384      * checks if form data is valid
385      * 
386      * @return {Boolean}
387      */
388     isValid: function() {
389         var isValid = this.validateDtStart() && this.validateDtEnd();
390         
391         if (! this.rrulePanel.isValid()) {
392             isValid = false;
393             
394             this.rrulePanel.ownerCt.setActiveTab(this.rrulePanel);
395         }
396         
397         return isValid && Tine.Calendar.EventEditDialog.superclass.isValid.apply(this, arguments);
398     },
399      
400     onAllDayChange: function(checkbox, isChecked) {
401         var dtStartField = this.getForm().findField('dtstart');
402         var dtEndField = this.getForm().findField('dtend');
403         dtStartField.setDisabled(isChecked, 'time');
404         dtEndField.setDisabled(isChecked, 'time');
405         
406         if (isChecked) {
407             dtStartField.clearTime();
408             var dtend = dtEndField.getValue();
409             if (Ext.isDate(dtend) && dtend.format('H:i:s') != '23:59:59') {
410                 dtEndField.setValue(dtend.clearTime(true).add(Date.HOUR, 24).add(Date.SECOND, -1));
411             }
412             
413         } else {
414             dtStartField.undo();
415             dtEndField.undo();
416         }
417     },
418     
419     onDtEndChange: function(dtEndField, newValue, oldValue) {
420         this.validateDtEnd();
421     },
422     
423     /**
424      * on dt start change
425      * 
426      * @param {} dtStartField
427      * @param {} newValue
428      * @param {} oldValue
429      */
430     onDtStartChange: function(dtStartField, newValue, oldValue) {
431         if (Ext.isDate(newValue) && Ext.isDate(oldValue)) {
432             var dtEndField = this.getForm().findField('dtend'),
433                 dtEnd = dtEndField.getValue();
434                 
435             if (Ext.isDate(dtEnd)) {
436                 var duration = dtEnd.getTime() - oldValue.getTime(),
437                     newDtEnd = newValue.add(Date.MILLI, duration);
438                 
439                 dtEndField.setValue(newDtEnd);
440             }
441         }
442     },
443     
444     /**
445      * copy record
446      * 
447      * TODO change attender status?
448      */
449     doCopyRecord: function() {
450         Tine.Calendar.EventEditDialog.superclass.doCopyRecord.call(this);
451         
452         // remove attender ids
453         Ext.each(this.record.data.attendee, function(attender) {
454             delete attender.id;
455         }, this);
456         
457         // Calendar is the only app with record based grants -> user gets edit grant for all fields when copying
458         this.record.set('editGrant', true);
459         
460         Tine.log.debug('Tine.Calendar.EventEditDialog::doCopyRecord() -> record:');
461         Tine.log.debug(this.record);
462     },
463     
464     /**
465      * is called after all subpanels have been loaded
466      */
467     onAfterRecordLoad: function() {
468         Tine.Calendar.EventEditDialog.superclass.onAfterRecordLoad.call(this);
469         
470         this.attendeeGridPanel.onRecordLoad(this.record);
471         this.rrulePanel.onRecordLoad(this.record);
472         this.alarmPanel.onRecordLoad(this.record);
473         
474         // apply grants
475         if (! this.record.get('editGrant')) {
476             this.getForm().items.each(function(f){
477                 if(f.isFormField && f.requiredGrant !== undefined){
478                     f.setDisabled(! this.record.get(f.requiredGrant));
479                 }
480             }, this);
481         }
482         
483         this.perspectiveCombo.loadPerspective();
484         // disable container selection combo if user has no right to edit
485         this.containerSelect.setDisabled.defer(20, this.containerSelect, [(! this.record.get('editGrant'))]);
486         
487         // disable time selectors if this is a whole day event
488         if (this.record.get('is_all_day_event')) {
489             this.onAllDayChange(null, true);
490         }
491     },
492     
493     onRecordUpdate: function() {
494         Tine.Calendar.EventEditDialog.superclass.onRecordUpdate.apply(this, arguments);
495         this.attendeeGridPanel.onRecordUpdate(this.record);
496         this.rrulePanel.onRecordUpdate(this.record);
497         this.alarmPanel.onRecordUpdate(this.record);
498         this.perspectiveCombo.updatePerspective();
499     },
500
501     setTabHeight: function() {
502         var eventTab = this.items.first().items.first();
503         var centerPanel = eventTab.items.first();
504         var tabPanel = centerPanel.items.last();
505         tabPanel.setHeight(centerPanel.getEl().getBottom() - tabPanel.getEl().getTop());
506     },
507     
508     validateDtEnd: function() {
509         var dtStart = this.getForm().findField('dtstart').getValue();
510         
511         var dtEndField = this.getForm().findField('dtend');
512         var dtEnd = dtEndField.getValue();
513         
514         if (! Ext.isDate(dtEnd)) {
515             dtEndField.markInvalid(this.app.i18n._('End date is not valid'));
516             return false;
517         } else if (Ext.isDate(dtStart) && dtEnd.getTime() - dtStart.getTime() <= 0) {
518             dtEndField.markInvalid(this.app.i18n._('End date must be after start date'));
519             return false;
520         } else {
521             dtEndField.clearInvalid();
522             return true;
523         }
524     },
525     
526     validateDtStart: function() {
527         var dtStartField = this.getForm().findField('dtstart');
528         var dtStart = dtStartField.getValue();
529         
530         if (! Ext.isDate(dtStart)) {
531             dtStartField.markInvalid(this.app.i18n._('Start date is not valid'));
532             return false;
533         } else {
534             dtStartField.clearInvalid();
535             return true;
536         }
537         
538     },
539     
540     /**
541      * is called from onApplyChanges
542      * @param {Boolean} closeWindow
543      */
544     doApplyChanges: function(closeWindow) {
545         this.onRecordUpdate();
546         if (this.isValid()) {
547             this.fireEvent('update', Ext.util.JSON.encode(this.record.data));
548             this.onAfterApplyChanges(closeWindow);
549         } else {
550             this.saving = false;
551             this.loadMask.hide();
552             Ext.MessageBox.alert(_('Errors'), this.getValidationErrorMessage());
553         }
554     }
555 });
556
557 /**
558  * Opens a new event edit dialog window
559  * 
560  * @return {Ext.ux.Window}
561  */
562 Tine.Calendar.EventEditDialog.openWindow = function (config) {
563     // record is JSON encoded here...
564     var id = config.recordId ? config.recordId : 0;
565     var window = Tine.WindowFactory.getWindow({
566         width: 800,
567         height: 505,
568         name: Tine.Calendar.EventEditDialog.prototype.windowNamePrefix + id,
569         contentPanelConstructor: 'Tine.Calendar.EventEditDialog',
570         contentPanelConstructorConfig: config
571     });
572     return window;
573 };