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)
11 Ext.ns('Tine.Calendar');
14 * @namespace Tine.Calendar
15 * @class Tine.Calendar.EventEditDialog
16 * @extends Tine.widgets.dialog.EditDialog
17 * Calendar Edit Dialog <br>
19 * @author Cornelius Weiss <c.weiss@metaways.de>
21 Tine.Calendar.EventEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
23 * @cfg {Number} containerId initial container id
28 windowNamePrefix: 'EventEditWindow_',
30 recordClass: Tine.Calendar.Model.Event,
31 recordProxy: Tine.Calendar.backend,
32 showContainerSelector: false,
33 tbarItems: [{xtype: 'widget-activitiesaddbutton'}],
37 // note: we need up use new action updater here or generally in the widget!
40 onResize: function() {
41 Tine.Calendar.EventEditDialog.superclass.onResize.apply(this, arguments);
42 this.setTabHeight.defer(100, this);
48 * NOTE: when this method gets called, all initalisation is done.
49 * @return {Object} components this.itmes definition
51 getFormItems: function() {
56 ptype : 'ux.tabpanelkeyplugin'
65 title: this.app.i18n.n_('Event', 'Events', 1),
79 text: this.app.i18n._('Summary')
84 listeners: {render: function(field){field.focus(false, 250);}},
86 requiredGrant: 'editGrant',
95 text: this.app.i18n._('View')
96 }, Ext.apply(this.perspectiveCombo, {
111 title: this.app.i18n._('Details'),
124 fieldLabel: this.app.i18n._('Location'),
126 requiredGrant: 'editGrant',
129 xtype: 'datetimefield',
130 fieldLabel: this.app.i18n._('Start Time'),
131 listeners: {scope: this, change: this.onDtStartChange},
133 requiredGrant: 'editGrant'
138 boxLabel: this.app.i18n._('whole day'),
139 listeners: {scope: this, check: this.onAllDayChange},
140 name: 'is_all_day_event',
141 requiredGrant: 'editGrant'
143 xtype: 'datetimefield',
144 fieldLabel: this.app.i18n._('End Time'),
145 listeners: {scope: this, change: this.onDtEndChange},
147 requiredGrant: 'editGrant'
155 name: 'originator_tz',
156 requiredGrant: 'editGrant'
157 }], [ this.containerSelectCombo = new Tine.widgets.container.selectionComboBox({
159 id: this.app.appName + 'EditDialogContainerSelector' + Ext.id(),
160 fieldLabel: _('Saved in'),
161 ref: '../../../../../../../../containerSelect',
164 name: this.recordClass.getMeta('containerProperty'),
165 recordClass: this.recordClass,
166 containerName: this.app.i18n.n_hidden(this.recordClass.getMeta('containerName'), this.recordClass.getMeta('containersName'), 1),
167 containersName: this.app.i18n._hidden(this.recordClass.getMeta('containersName')),
168 appName: this.app.appName,
169 requiredGrant: this.record.data.id ? ['editGrant'] : ['addGrant'],
171 }), Ext.apply(this.perspectiveCombo.getAttendeeContainerField(), {
178 title: this.app.i18n._('Status'),
182 boxLabel: this.app.i18n._('non-blocking'),
184 requiredGrant: 'editGrant',
185 getValue: function() {
186 var bool = Ext.form.Checkbox.prototype.getValue.call(this);
187 return bool ? 'TRANSPARENT' : 'OPAQUE';
189 setValue: function(value) {
190 var bool = (value == 'TRANSPARENT' || value === true);
191 return Ext.form.Checkbox.prototype.setValue.call(this, bool);
193 }, Ext.apply(this.perspectiveCombo.getAttendeeTranspField(), {
198 boxLabel: this.app.i18n._('Tentative'),
200 requiredGrant: 'editGrant',
201 getValue: function() {
202 var bool = Ext.form.Checkbox.prototype.getValue.call(this);
203 return bool ? 'TENTATIVE' : 'CONFIRMED';
205 setValue: function(value) {
206 var bool = (value == 'TENTATIVE' || value === true);
207 return Ext.form.Checkbox.prototype.setValue.call(this, bool);
212 boxLabel: this.app.i18n._('Private'),
214 requiredGrant: 'editGrant',
215 getValue: function() {
216 var bool = Ext.form.Checkbox.prototype.getValue.call(this);
217 return bool ? 'PRIVATE' : 'PUBLIC';
219 setValue: function(value) {
220 var bool = (value == 'PRIVATE' || value === true);
221 return Ext.form.Checkbox.prototype.setValue.call(this, bool);
223 }, Ext.apply(this.perspectiveCombo.getAttendeeStatusField(), {
230 deferredRender: false,
236 this.attendeeGridPanel,
242 // activities and tags
249 collapseMode: 'mini',
256 title: this.app.i18n._('Description'),
257 iconCls: 'descriptionIcon',
262 style: 'margin-top: -4px; border 0px;',
268 preventScrollbars:false,
270 emptyText: this.app.i18n._('Enter description'),
271 requiredGrant: 'editGrant'
274 new Tine.widgets.activities.ActivitiesPanel({
276 showAddNoteForm: false,
278 bodyStyle: 'border:1px solid #B5B8C8;'
280 new Tine.widgets.tags.TagPanel({
283 bodyStyle: 'border:1px solid #B5B8C8;'
287 }, new Tine.widgets.activities.ActivitiesTabPanel({
289 record_id: (this.record) ? this.record.id : '',
290 record_model: this.appName + '_Model_' + this.recordClass.getMeta('modelName')
301 onMuteAlertOnce: function (button, e) {
302 this.record.set('mute', button.pressed);
305 initComponent: function() {
306 this.tbarItems.push(new Ext.Button(new Ext.Action({
307 text: Tine.Tinebase.appMgr.get('Calendar').i18n._('Mute Alert'),
308 handler: this.onMuteAlertOnce,
309 iconCls: 'notes_noteIcon',
316 this.attendeeGridPanel = new Tine.Calendar.AttendeeGridPanel({
319 html: Tine.Tinebase.appMgr.get('Calendar').i18n._('Organizer') + " "
320 }, organizerCombo = Tine.widgets.form.RecordPickerManager.get('Addressbook', 'Contact', {
324 getValue: function() {
325 var id = Tine.Addressbook.SearchCombo.prototype.getValue.apply(this, arguments),
326 record = this.store.getById(id);
328 return record ? record.data : id;
334 this.attendeeGridPanel.on('afteredit', function(o) {
335 if (o.field == 'user_id'
336 && o.record.get('user_type') == 'resource'
337 && o.record.get('user_id')
338 && o.record.get('user_id').is_location
340 this.getForm().findField('location').setValue(
341 this.attendeeGridPanel.renderAttenderResourceName(o.record.get('user_id'))
346 this.on('render', function() {this.getForm().add(organizerCombo);}, this);
348 this.rrulePanel = new Tine.Calendar.RrulePanel({});
349 this.alarmPanel = new Tine.widgets.dialog.AlarmPanel({});
350 this.attendeeStore = this.attendeeGridPanel.getStore();
352 // a combo with all attendee + origin/organizer
353 this.perspectiveCombo = new Tine.Calendar.PerspectiveCombo({
357 Tine.Calendar.EventEditDialog.superclass.initComponent.call(this);
363 * if this addRelations is set, iterate and create attendee
365 addAttendee: function() {
366 var attendee = this.record.get('attendee');
367 var attendee = Ext.isArray(attendee) ? attendee : [];
369 if (Ext.isArray(this.plugins)) {
370 for (var index = 0; index < this.plugins.length; index++) {
371 if (this.plugins[index].hasOwnProperty('addRelations')) {
373 var config = this.plugins[index].hasOwnProperty('relationConfig') ? this.plugins[index].relationConfig : {};
375 for (var index2 = 0; index2 < this.plugins[index].addRelations.length; index2++) {
376 var item = this.plugins[index].addRelations[index2];
377 var attender = Ext.apply({
381 status: 'NEEDS-ACTION',
385 attendee.push(attender);
391 this.record.set('attendee', attendee);
395 * checks if form data is valid
399 isValid: function() {
400 var isValid = this.validateDtStart() && this.validateDtEnd();
402 if (! this.rrulePanel.isValid()) {
405 this.rrulePanel.ownerCt.setActiveTab(this.rrulePanel);
408 return isValid && Tine.Calendar.EventEditDialog.superclass.isValid.apply(this, arguments);
411 onAllDayChange: function(checkbox, isChecked) {
412 var dtStartField = this.getForm().findField('dtstart');
413 var dtEndField = this.getForm().findField('dtend');
414 dtStartField.setDisabled(isChecked, 'time');
415 dtEndField.setDisabled(isChecked, 'time');
418 dtStartField.clearTime();
419 var dtend = dtEndField.getValue();
420 if (Ext.isDate(dtend) && dtend.format('H:i:s') != '23:59:59') {
421 dtEndField.setValue(dtend.clearTime(true).add(Date.HOUR, 24).add(Date.SECOND, -1));
430 onDtEndChange: function(dtEndField, newValue, oldValue) {
431 this.validateDtEnd();
437 * @param {} dtStartField
441 onDtStartChange: function(dtStartField, newValue, oldValue) {
442 if (this.validateDtStart() == false) {
446 if (Ext.isDate(newValue) && Ext.isDate(oldValue)) {
447 var dtEndField = this.getForm().findField('dtend'),
448 dtEnd = dtEndField.getValue();
450 if (Ext.isDate(dtEnd)) {
451 var duration = dtEnd.getTime() - oldValue.getTime(),
452 newDtEnd = newValue.add(Date.MILLI, duration);
453 dtEndField.setValue(newDtEnd);
454 this.validateDtEnd();
462 * TODO change attender status?
464 doCopyRecord: function() {
465 Tine.Calendar.EventEditDialog.superclass.doCopyRecord.call(this);
467 // remove attender ids
468 Ext.each(this.record.data.attendee, function(attender) {
472 // Calendar is the only app with record based grants -> user gets edit grant for all fields when copying
473 this.record.set('editGrant', true);
475 Tine.log.debug('Tine.Calendar.EventEditDialog::doCopyRecord() -> record:');
476 Tine.log.debug(this.record);
480 * is called after all subpanels have been loaded
482 onAfterRecordLoad: function() {
483 Tine.Calendar.EventEditDialog.superclass.onAfterRecordLoad.call(this);
485 // disable relations panel for non persistent exceptions till we have the baseEventId
486 if (this.record.isRecurInstance()) {
487 this.relationsPanel.setDisabled(true);
489 this.attendeeGridPanel.onRecordLoad(this.record);
490 this.rrulePanel.onRecordLoad(this.record);
491 this.alarmPanel.onRecordLoad(this.record);
494 if (! this.record.get('editGrant')) {
495 this.getForm().items.each(function(f){
496 if(f.isFormField && f.requiredGrant !== undefined){
497 f.setDisabled(! this.record.get(f.requiredGrant));
502 this.perspectiveCombo.loadPerspective();
503 // disable container selection combo if user has no right to edit
504 this.containerSelect.setDisabled.defer(20, this.containerSelect, [(! this.record.get('editGrant'))]);
506 // disable time selectors if this is a whole day event
507 if (this.record.get('is_all_day_event')) {
508 this.onAllDayChange(null, true);
512 onRecordUpdate: function() {
513 Tine.Calendar.EventEditDialog.superclass.onRecordUpdate.apply(this, arguments);
514 this.attendeeGridPanel.onRecordUpdate(this.record);
515 this.rrulePanel.onRecordUpdate(this.record);
516 this.alarmPanel.onRecordUpdate(this.record);
517 this.perspectiveCombo.updatePerspective();
520 setTabHeight: function() {
521 var eventTab = this.items.first().items.first();
522 var centerPanel = eventTab.items.first();
523 var tabPanel = centerPanel.items.last();
524 tabPanel.setHeight(centerPanel.getEl().getBottom() - tabPanel.getEl().getTop());
527 validateDtEnd: function() {
528 var dtStart = this.getForm().findField('dtstart').getValue();
530 var dtEndField = this.getForm().findField('dtend');
531 var dtEnd = dtEndField.getValue();
533 var prefs = this.app.getRegistry().get('preferences'),
534 endTime = Date.parseDate(prefs.get('daysviewendtime'), 'H:i');
536 if (endTime.format('H:i') == '00:00') {
537 endTime = endTime.add(Date.MINUTE, -1);
540 // Update to the selected day
541 endTime.setDate(dtEnd.getDate());
542 endTime.setMonth(dtEnd.getMonth());
543 endTime.setYear(dtEnd.getYear() + 1900);
545 if (! Ext.isDate(dtEnd)) {
546 dtEndField.markInvalid(this.app.i18n._('End date is not valid'));
548 } else if (Ext.isDate(dtStart) && dtEnd.getTime() - dtStart.getTime() <= 0) {
549 dtEndField.markInvalid(this.app.i18n._('End date must be after start date'));
551 } else if (! Tine.Tinebase.configManager.get('daysviewallowallevents', 'Calendar') && this.getForm().findField('is_all_day_event').checked === false && !! Tine.Tinebase.configManager.get('daysviewcroptime', 'Calendar') && dtEnd > endTime) {
552 dtEndField.markInvalid(this.app.i18n._('End date is not allowed to be be higher than the configured time range.'));
555 dtEndField.clearInvalid();
560 validateDtStart: function() {
561 var dtStartField = this.getForm().findField('dtstart');
562 var dtStart = dtStartField.getValue();
564 var prefs = this.app.getRegistry().get('preferences'),
565 startTime = Date.parseDate(prefs.get('daysviewstarttime'), 'H:i');
567 // Update to the selected day
568 startTime.setDate(dtStart.getDate());
569 startTime.setMonth(dtStart.getMonth());
570 startTime.setYear(dtStart.getYear() + 1900);
572 if (! Ext.isDate(dtStart)) {
573 dtStartField.markInvalid(this.app.i18n._('Start date is not valid'));
575 } else if (! Tine.Tinebase.configManager.get('daysviewallowallevents', 'Calendar') && this.getForm().findField('is_all_day_event').checked === false && !! Tine.Tinebase.configManager.get('daysviewcroptime', 'Calendar') && dtStart < startTime) {
576 dtStartField.markInvalid(this.app.i18n._('End date is not allowed to be be lower than the configured time range.'));
579 dtStartField.clearInvalid();
585 * is called from onApplyChanges
586 * @param {Boolean} closeWindow
588 doApplyChanges: function(closeWindow) {
589 this.onRecordUpdate();
590 if (this.isValid()) {
591 this.fireEvent('update', Ext.util.JSON.encode(this.record.data));
592 this.onAfterApplyChanges(closeWindow);
595 this.loadMask.hide();
596 Ext.MessageBox.alert(_('Errors'), this.getValidationErrorMessage());
602 * Opens a new event edit dialog window
604 * @return {Ext.ux.Window}
606 Tine.Calendar.EventEditDialog.openWindow = function (config) {
607 // record is JSON encoded here...
608 var id = config.recordId ? config.recordId : 0;
609 var window = Tine.WindowFactory.getWindow({
612 name: Tine.Calendar.EventEditDialog.prototype.windowNamePrefix + id,
613 contentPanelConstructor: 'Tine.Calendar.EventEditDialog',
614 contentPanelConstructorConfig: config