/* * Tine 2.0 * * @package Calendar * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3 * @author Cornelius Weiss * @copyright Copyright (c) 2009-2013 Metaways Infosystems GmbH (http://www.metaways.de) * */ Ext.ns('Tine.Calendar'); require('./AttendeePickerCombo'); require('./ResourcePickerCombo'); /** * @namespace Tine.Calendar * @class Tine.Calendar.AttendeeGridPanel * @extends Ext.grid.EditorGridPanel * @author Cornelius Weiss */ Tine.Calendar.AttendeeGridPanel = Ext.extend(Ext.grid.EditorGridPanel, { autoExpandColumn: 'user_id', clicksToEdit: 1, enableHdMenu: false, canonicalName: 'AttendeeGrid', /** * @cfg defaut text for new attendee combo * i18n._('Click here to invite another attender...') */ addNewAttendeeText: 'Click here to invite another attender...', /** * @cfg {Boolean} showGroupMemberType * show user_type groupmember in type selection */ showMemberOfType: false, /** * @cfg {Boolean} showNamesOnly * true to only show types and names in the list */ showNamesOnly: false, /** * @cfg {Boolean} showAttendeeRole * true to show roles in the list */ showAttendeeRole: false, /** * @cfg {String} defaultAttendeeRole * attendee role for new attendee row */ defaultAttendeeRole: 'REQ', /** * @cfg {Bool} requireFreeBusyGrantOnly * freebusy grant is sufficient to find ressource (instead of inviteGrant) */ requireFreeBusyGrantOnly: null, /** * The record currently being edited * * @type Tine.Calendar.Model.Event * @property record */ record: null, /** * id of current account * * @type Number * @property currentAccountId */ currentAccountId: null, /** * ctx menu * * @type Ext.menu.Menu * @property ctxMenu */ ctxMenu: null, /** * store to hold all attendee * * @type Ext.data.Store * @property attendeeStore */ attendeeStore: null, attendeeTypeCombo: null, /** * grid panel phone hook for calling attendee * * @type Tine.Phone.AddressbookGridPanelHook */ phoneHook: null, stateful: true, stateId: 'cal-attendeegridpanel', initComponent: function() { this.app = this.app ? this.app : Tine.Tinebase.appMgr.get('Calendar'); this.currentAccountId = Tine.Tinebase.registry.get('currentAccount').accountId; this.title = this.hasOwnProperty('title') ? this.title : this.app.i18n._('Attendee'); Ext.ux.pluginRegistry.addRegisteredPlugins(this, 'Calendar.AttendeeGridPanel'); if (! this.showNamesOnly) { this.plugins.push(new Ext.ux.grid.GridViewMenuPlugin({})); } this.store = new Ext.data.SimpleStore({ fields: Tine.Calendar.Model.Attender.getFieldDefinitions().concat('sort'), sortInfo: {field: 'user_id', direction: 'ASC'}, sortData : function(f, direction){ direction = direction || 'ASC'; var st = this.fields.get(f).sortType; var fn = function(r1, r2){ // make sure new-attendee line is on the bottom if (!r1.data.user_id) return direction == 'ASC'; if (!r2.data.user_id) return direction != 'ASC'; var v1 = st(r1.data[f]), v2 = st(r2.data[f]); return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0); }; this.data.sort(direction, fn); if(this.snapshot && this.snapshot != this.data){ this.snapshot.sort(direction, fn); } } }); this.on('beforeedit', this.onBeforeAttenderEdit, this); this.on('afteredit', this.onAfterAttenderEdit, this); this.on('rowcontextmenu', this.onRowContextMenu, this); this.addEvents('beforenewattendee'); this.initColumns(); this.viewConfig = _.assign(this.viewConfig || {},{ getRowClass: this.getRowClass }); Tine.Calendar.AttendeeGridPanel.superclass.initComponent.call(this); this.initPhoneGridPanelHook(); }, initColumns: function() { var attendeeTypes = new Ext.data.ArrayStore({ fields: ['name', 'value'], idIndex: 0 // id for each record will be the first element }); attendeeTypes.loadData([ ['any', '...' ], ['user', this.app.i18n._('User') ], ['group', this.app.i18n._('Group') ], ['resource', this.app.i18n._('Resource')] ].concat(this.showMemberOfType ? [['memberOf', this.app.i18n._('Member of group') ]] : [])); this.attendeeTypeCombo = new Ext.form.ComboBox({ blurOnSelect : true, expandOnFocus : true, listWidth : 100, mode : 'local', valueField: 'name', displayField: 'value', store : attendeeTypes }); this.columns = [{ id: 'role', width: 70, sortable: true, hidden: !this.showAttendeeRole || this.showNamesOnly, header: this.app.i18n._('Role'), renderer: this.renderAttenderRole.createDelegate(this), editor: { xtype: 'widget-keyfieldcombo', app: 'Calendar', keyFieldName: 'attendeeRoles' } }, { id: 'displaycontainer_id', width: 200, sortable: false, hidden: this.showNamesOnly || true, header: i18n._hidden('Saved in'), tooltip: this.app.i18n._('This is the calendar where the attendee saved the event.'), renderer: this.renderAttenderDispContainer.createDelegate(this), // disable for the moment, as updating calendarSelectWidget is not working in both directions editor2: new Tine.widgets.container.SelectionComboBox({ blurOnSelect: true, selectOnFocus: true, appName: 'Calendar', getValue: function() { if (this.selectedContainer) { // NOTE: the store checks if data changed. If we don't overwrite to string, // the check only sees [Object Object] wich of course never changes... var container_id = this.selectedContainer.id; this.selectedContainer.toString = function() {return container_id;}; } return this.selectedContainer; }, listeners: { scope: this, select: function(field, newValue) { // the field is already blured, due to the extra chooser window. We need to change the value per hand var selection = this.getSelectionModel().getSelectedCell(); if (selection) { var row = selection[0]; this.store.getAt(row).set('displaycontainer_id', newValue); } } } }) }, { id: 'user_type', width: 50, sortable: true, header: this.app.i18n._('Type'), tooltip: this.app.i18n._('Click icon to change'), renderer: this.renderAttenderType.createDelegate(this), editor: this.attendeeTypeCombo }, { id: 'user_id', width: 300, sortable: true, header: this.app.i18n._('Name'), renderer: this.renderAttenderName.createDelegate(this), editor: true }, { id: 'fbInfo', width: 20, hidden: this.showNamesOnly, header: ' ', tooltip: this.app.i18n._('Availability of Attendee'), fixed: true, sortable: false, renderer: function(v, m, r) { // NOTE: already encoded return r.get('user_id') ? v : ''; } }, { id: 'status', width: 100, sortable: true, header: this.app.i18n._('Status'), hidden: this.showNamesOnly, renderer: this.renderAttenderStatus.createDelegate(this), editor: { xtype: 'widget-keyfieldcombo', app: 'Calendar', keyFieldName: 'attendeeStatus' } }]; }, onEditComplete: function(ed, value, startValue) { var _ = window.lodash, attendeeData = _.get(ed, 'field.selectedRecord.data.user_id', null), type = _.get(ed, 'field.selectedRecord.data.user_type'), fbInfo = _.get(ed, 'field.selectedRecord.data.fbInfo'); // attendeePickerCombo if ((attendeeData || type === 'email') && type) { if (this.showMemberOfType && 'group' == type) { var selectedRecord = _.get(ed, 'field.selectedRecord'); Tine.widgets.dialog.MultiOptionsDialog.openWindow({ title: this.app.i18n._('Whole group or each group member'), questionText: this.app.i18n._('Choose "Group" to filter by the group as a whole. Choose "Member of Group" to filter by each group member.'), height: 170, scope: this, options: [ {text: this.app.i18n._('Group'), name: 'sel_group'}, {text: this.app.i18n._('Member of Group'), name: 'sel_memberOf'} ], handler: function(option) { // NOTE: don't modify the record of the picker store (its list can't render 'sel_*' types) const groupRecord = selectedRecord.copy(); groupRecord.set('user_type', option); groupRecord.groupType = option; // NOTE: complete with the existing editor - starting a new edit would create (and // immediately destroy) another attendee picker combo ed.field.selectedRecord = groupRecord; this.onEditComplete(ed, null, startValue); } }); // abort normal flow value = startValue; } else { value = attendeeData; ed.record.set('user_type', type.replace(/^sel_/, '')); ed.record.set('user_email', _.get(ed, 'field.selectedRecord.data.user_email')); ed.record.set('user_displayname', _.get(ed, 'field.selectedRecord.data.user_displayname')); ed.record.set('fbInfo', fbInfo); ed.record.commit(); } } Tine.Calendar.AttendeeGridPanel.superclass.onEditComplete.call(this, ed, value, startValue); }, onAfterAttenderEdit: function(o) { switch (o.field) { case 'user_id' : const type = o.record.get('user_type'); // detect duplicate entry var isDuplicate = false; this.store.each(function(attender) { if (o.record.getUserId() === attender.getUserId() && o.record.get('user_type') === attender.get('user_type') && o.record != attender) { attender.set('checked', true); var row = this.getView().getRow(this.store.indexOf(attender)); Ext.fly(row).highlight(); isDuplicate = true; return false; } }, this); if (isDuplicate) { o.record.set('user_id', undefined); o.record.set('user_type', undefined); o.record.set('user_email', undefined); o.record.set('user_displayname', undefined); o.record.set('fbInfo', undefined); o.record.commit(); o.record.reject(); this.startEditing(o.row, o.column); } else if (o.value || type === 'email') { o.record.setId(String(o.record.id).replace(/new-/, '')); // set status authkey for contacts and recources so user can edit status directly // NOTE: we can't compute if the user has editgrant to the displaycontainer of an account here! // WELL we could add the info to search attendee somehow if ( (['user', 'email'].indexOf(type) >= 0 && ! o.value?.account_id && !o.record.get('status_authkey'))) { o.record.set('status_authkey', Tine.Tinebase.data.Record.generateUID()); } else if (type === 'resource') { const grants = lodash.get(o.record, 'data.user_id.container_id.account_grants', {}); if (grants.resourceStatusGrant && !o.record.get('status_authkey')) { o.record.set('status_authkey', Tine.Tinebase.data.Record.generateUID()); } } o.record.explicitlyAdded = true; o.record.set('checked', true); // Set status if the resource has a specific default status if (type === 'resource' && o.record.get('user_id') && o.record.get('user_id').status) { const grants = lodash.get(o.record, 'data.user_id.container_id.account_grants', {}); if (grants.resourceStatusGrant && o.record.get('user_id').status_with_grant) { o.record.set('status', o.record.get('user_id').status_with_grant); } else { o.record.set('status', o.record.get('user_id').status); } } // resolve group members if (type === 'group' && !this.showMemberOfType) { this.resolveListMembers(o.record.get('user_id')); } else { this.addNewAttendeeRow(); this.startEditing(o.row + 1, o.column); } } break; case 'user_type': this.startEditing(o.row, this.getColumnModel().getIndexById('user_id')); break; case 'role': if (o.row == this.store.getCount()-1) { this.setDefaultAttendeeRole(o.record.get('role')); this.startEditing(o.row, this.getColumnModel().getIndexById('user_id')); } break; case 'container_id': // check if displaycontainer of owner got changed if (o.record == this.eventOriginator) { this.record.set('container_id', ''); this.record.set('container_id', o.record.get('displaycontainer_id')); } break; } }, resolveListMembers: function() { if (this.showMemberOfType) return; var _ = window.lodash, members = Tine.Calendar.Model.Attender.getAttendeeStore.getData(this.store), fbInfoUpdate = []; // NOTE: grid might not be rendered yet (e.g. onRecordLoad before window is shown) let isLoading = true, mask = null; this.afterIsRendered().then(() => { if (isLoading && !this.isDestroyed) { mask = new Ext.LoadMask(this.getEl(), {msg: this.app.i18n._("Loading Group Members...")}); mask.show(); } }); Tine.Calendar.resolveGroupMembers(members, function(attendeesData) { isLoading = false; if (this.isDestroyed) return; var attendees = Tine.Calendar.Model.Attender.getAttendeeStore(attendeesData); // remove not longer existing attendee this.store.each(function(attendee) { if (! Tine.Calendar.Model.Attender.getAttendeeStore.getAttenderRecord(attendees, attendee)) { this.store.remove(attendee); } }); // add new attendee attendees.each(function(attendee) { if (! Tine.Calendar.Model.Attender.getAttendeeStore.getAttenderRecord(this.store, attendee)) { attendee.set('role', this.defaultAttendeeRole); this.fireEvent('beforenewattendee', this, attendee, this.record); this.store.add([attendee]); fbInfoUpdate.push(attendee.id); } }, this); this.updateFreeBusyInfo(fbInfoUpdate); mask?.hide(); this.addNewAttendeeRow(); }, this); }, onBeforeAttenderEdit: function(o) { if (o.field == 'status') { // allow status setting if status authkey is present o.cancel = ! o.record.get('status_authkey'); return; } if (o.field == 'displaycontainer_id') { if (! o.value || ! o.value.account_grants || ! o.value.account_grants.deleteGrant) { o.cancel = true; } return; } // for all other fields user need editGrant if (! this.record.get('editGrant')) { o.cancel = true; return; } // don't allow to set anything besides quantity and role for already set attendee if (o.record.get('user_id') || o.record.get('user_type') === 'email') { o.cancel = true; if (o.field == 'quantity' && o.record.get('user_type') == 'resource') { o.cancel = false; } if (o.field == 'role') { o.cancel = false; } return; } if (o.field == 'user_id') { // switch editor var colModel = o.grid.getColumnModel(), type = o.record.get('user_type'); type = type == 'memberOf' ? 'group' : type; var attendeePickerCombo = new Tine.Calendar.AttendeePickerCombo({ minListWidth: 370, blurOnSelect: true, eventRecord: this.record, requireFreeBusyGrantOnly: this.requireFreeBusyGrantOnly, additionalFilters: type != 'any' ? [{field: 'type', operator: 'oneof', value: [type]}] : null }); this.fireEvent('beforenewattendeepickercombo', this, attendeePickerCombo, o); colModel.config[o.column].setEditor(attendeePickerCombo); colModel.config[o.column].editor.selectedRecord = null; } }, /** * give new attendee an extra cls */ getRowClass: function(record, index) { if (! record.get('user_id')) { return 'x-cal-add-attendee-row'; } }, onBeforeRemoveAttendee: function() { return true; }, onRowContextMenu: function(grid, row, e) { e.stopEvent(); var me = this, attender = this.store.getAt(row), type = attender.get('user_type'); if (attender && ! this.disabled) { // don't delete 'add' row var attender = this.store.getAt(row); if (! attender.get('user_id') && type !== 'email') { return; } // select name cell if (Ext.isFunction(this.getSelectionModel().select)) { this.getSelectionModel().select(row, 3); } else { // west panel attendee filter grid this.getSelectionModel().selectRow(row); } Tine.log.debug('onContextMenu - attender:'); Tine.log.debug(attender); var items = [{ text: this.app.i18n._('Remove Attendee'), iconCls: 'action_delete', scope: this, disabled: ! this.record.get('editGrant') || type == 'groupmember', handler: function() { if(this.onBeforeRemoveAttendee(attender, type) === false) { return false; } this.store.remove(attender); this.fireEvent('removeentry', this, attender) if (type == 'group' && !this.showMemberOfType) { this.resolveListMembers() } } },{ text: this.app.i18n._('Use as event location'), iconCls: 'action_export', scope: this, disabled: ! this.record.get('editGrant'), handler: async function () { const locationField = this.editDialog.getForm().findField('location'); const locationRecordField = this.editDialog.getForm().findField('location_record'); if (attender.get('user_type') == 'resource') { this.editDialog.setLocationRecord(attender, true); } else { const attendeeName = await this.renderAttenderName(attender?.data?.user_id, {noIcon: true}, attender).asString(); locationField.setValue(attendeeName); locationRecordField.setValue(attender?.data?.user_id); } } }, '-']; //TODO: use grid panel hook ? var felamimailApp = Tine.Tinebase.appMgr.get('Felamimail'); if (felamimailApp && attender.get('user_type') == 'user') { Tine.log.debug('Adding email compose hook for attender'); items = items.concat(new Ext.Action({ text: felamimailApp.i18n._('Compose email'), iconCls: felamimailApp.getIconCls(), disabled: false, scope: this, handler: async() => { const record = new Tine.Felamimail.Model.Message({ subject: _.get(this.record, 'data.poll.name', _.get(this.record, 'data.summary', ''), ''), body: this.record.hasPoll() ? String.format(this.app.i18n._('Poll URL: {0}'), this.record.getPollUrl()) : '', massMailingFlag: this.record.hasPoll(), to: [attender.data.user_id.email] }, 0); var popupWindow = Tine.Felamimail.MessageEditDialog.openWindow({ // @TODO fixme, invent better handling to skip gdpr client plugin massMailingPlugins: ['poll'], record: record }); } })); } if (attender.get('user_type') == 'resource') { Tine.log.debug('Adding resource hook for attender'); var resourceId = attender.get('user_id').id, resource = new Tine.Calendar.Model.Resource(attender.get('user_id'), resourceId), type = Tine.Tinebase.widgets.keyfield.StoreMgr.get('Calendar', 'resourceTypes').getById(lodash.get(resource, 'data.type', {})), grants = lodash.get(resource, 'data.container_id.account_grants', {}); items = items.concat(new Ext.Action({ text: grants.resourceEditGrant || Tine.Tinebase.common.hasRight('manage', 'Calendar', 'resources') ? this.app.i18n._('Edit Resource') : this.app.i18n._('View Resource'), iconCls: 'cal-resource', scope: this, handler: function() { Tine.Calendar.ResourceEditDialog.openWindow({record: resource}); } })); var exportAction = Tine.widgets.exportAction.getExportButton( Tine.Calendar.Model.Resource, { getExportOptions: function() { var options = { recordData: attender.get('user_id') }; // do we have a 'real' event? if (me.record.data.dtstart) { options.additionalRecords = { Event: { model: 'Calendar_Model_Event', recordData: me.record.data } }; } return options; } }, Tine.widgets.exportAction.SCOPE_SINGLE ); items = items.concat(exportAction); } var plugins = [{ ptype: 'ux.itemregistry', key: 'Attendee-GridPanel-ContextMenu' }, { ptype: 'ux.itemregistry', key: 'Tinebase-MainContextMenu' }]; // add phone call action via item registry if (this.phoneHook && attender.get('user_type') == 'user') { var contact = new Tine.Addressbook.Model.Contact(attender.get('user_id')); this.phoneHook.setContactAndUpdateAction(contact); } items = _.compact(items); Tine.log.debug(items); this.ctxMenu = new Ext.menu.Menu({ items: items, plugins: plugins, listeners: { scope: this, hide: function() { this.getSelectionModel().clearSelections(); } } }); var actionUpdater = new Tine.widgets.ActionUpdater({ evalGrants: false }); actionUpdater.addAction(exportAction); actionUpdater.updateActions([resource]); if (this.fireEvent('beforeshowctxmenu', this, row, this.ctxMenu, e) !== false) { this.ctxMenu.showAt(e.getXY()); } } }, /** * init phone grid panel hook if Phone app is available */ initPhoneGridPanelHook: function() { var phoneApp = Tine.Tinebase.appMgr.get('Phone'); if (phoneApp && _.get(Tine, 'Phone.AddressbookGridPanelHook')) { Tine.log.debug('Adding Phone call hook'); this.phoneHook = new Tine.Phone.AddressbookGridPanelHook({ app: phoneApp, keyPrefix: 'Attendee', useActionUpdater: false }); } }, /** * loads this panel with data from given record * called by edit dialog when record is loaded * * @param {Tine.Calendar.Model.Event} record * @param Array addAttendee */ onRecordLoad: function(record) { this.record = record; this.store.removeAll(); var attendee = record.get('attendee'), resolveListMembers = false; Ext.each(attendee, function(attender) { var record = new Tine.Calendar.Model.Attender(attender, attender.id); this.store.addSorted(record); if (attender.displaycontainer_id && this.record.get('container_id') && attender.displaycontainer_id.id == this.record.get('container_id').id) { this.eventOriginator = record; } if (String(record.get('user_type')).match(/^group/)) { resolveListMembers = true; } }, this); this.updateFreeBusyInfo(); if (resolveListMembers) { this.resolveListMembers(); } else if (record.get('editGrant')) { this.addNewAttendeeRow(); } }, updateFreeBusyInfo: function(force) { if (this.showMemberOfType) return; var _ = window.lodash, schedulingInfo = this.record.getSchedulingData(), encodedSchedulingInfo = Ext.encode(schedulingInfo); if (encodedSchedulingInfo === this.encodedSchedulingInfo && !force) { return; } if (! schedulingInfo.dtend || ! schedulingInfo.dtstart) { // dtend & dtstart are required return; } // @TODO have load spinner? this.encodedSchedulingInfo = encodedSchedulingInfo; // clear state this.store.each(function(attendee) { if (Ext.isArray(force) && force.indexOf(attendee.id) < 0) { return; } attendee.set('fbInfo', '...'); attendee.commit(); }, this); Tine.Calendar.getFreeBusyInfo( _.map(Tine.Calendar.Model.Attender.getAttendeeStore.getData(this.store),(attendee) => { return Object.assign({... attendee}, {user_id: _.get(attendee, 'user_id.id'), id: null}) }), [schedulingInfo], [this.record.get('uid')], function(freeBusyData) { // outdated data if (encodedSchedulingInfo != this.encodedSchedulingInfo) return; var fbInfo = new Tine.Calendar.FreeBusyInfo(_.values(freeBusyData)[0]); this.store.each(function(attendee) { attendee.set('fbInfo', fbInfo.getStateOfAttendee(attendee, this.record)); attendee.commit(); }, this); }, this); }, // Add new attendee addNewAttendeeRow: function() { this.newAttendee = new Tine.Calendar.Model.Attender(Tine.Calendar.Model.Attender.getDefaultData(), 'new-' + Ext.id()); this.newAttendee.set('role', this.defaultAttendeeRole); this.fireEvent('beforenewattendee', this, this.newAttendee, this.record); this.store.add([this.newAttendee]); }, setDefaultAttendeeRole:function(role) { this.defaultAttendeeRole = role; if (this.newAttendee) { this.newAttendee.set('role', role); } }, /** * Updates given record with data from this panel * called by edit dialog to get data * * @param {Tine.Calendar.Model.Event} record */ onRecordUpdate: function(record) { this.stopEditing(false); this.updateFreeBusyInfo(); // remove fake user_id from external email this.store.each(function(attendee) { if (attendee.get('user_type') === 'email') { attendee.set('user_id', null); attendee.commit(); } }, this); Tine.Calendar.Model.Attender.getAttendeeStore.getData(this.store, record); }, onKeyDown: function(e) { switch(e.getKey()) { case e.DELETE: if (this.record.get('editGrant')) { var selection = this.getSelectionModel().getSelectedCell(); if (selection) { var row = selection[0]; // don't delete 'add' row var attender = this.store.getAt(row); if (! attender.get('user_id')) { return; } this.store.removeAt(row); } } break; } }, renderAttenderName: function(name, metaData, record) { this.app = this.app ? this.app : Tine.Tinebase.appMgr.get('Calendar'); const type = _.get(record, 'data.user_type', 'user'); if (name || type === 'email') { const fn = this['renderAttender' + Ext.util.Format.capitalize(type) + 'Name']; return fn ? fn.apply(this, arguments): ''; } // add new user: if (arguments[1]) { arguments[1].css = 'x-form-empty-field'; return this.app.i18n._(this.addNewAttendeeText); } }, renderAttenderAnyName: function(name) { return Tine.Tinebase.appMgr.get('Tinebase').i18n._('Anyone'); }, renderAttenderEmailName: function(name, metadata, record) { return this.renderAttenderUserName({ "email": record.get('user_email'), "n_fileas": record.get('user_displayname') || record.get('user_email'), "n_fn": record.get('user_displayname') }); }, /** * render attender user name * * @param name * @returns {*} */ renderAttenderUserName: function(name) { name = name || ""; var result = "", email = ""; if (typeof name.getTitle == 'function') { result = name.getTitle(); } else if (name.n_fileas) { result = Tine.Tinebase.data.Record.setFromJson(name, Tine.Addressbook.Model.Contact).getTitle(); } else if (name.accountDisplayName) { result = name.accountDisplayName; } else if (Ext.isString(name) && ! name.match('^[0-9a-f-]{40,}$') && ! parseInt(name, 10)) { // how to detect hash/string ids result = name; } if (result === '') { result = Tine.Tinebase.appMgr.get('Calendar').i18n._('No Information') } else { result = Ext.util.Format.htmlEncode(result) } // NOTE: this fn gets also called from other scopes return result; }, renderAttenderGroupmemberName: function(name) { return Ext.util.Format.htmlEncode(name.n_fileas + ' ' + Tine.Tinebase.appMgr.get('Calendar').i18n._('(as a group member)')); }, renderAttenderGroupName: function(name) { if (typeof name.getTitle == 'function') { return Ext.util.Format.htmlEncode(name.getTitle()); } if (name.name) { return Ext.util.Format.htmlEncode(name.name); } if (Ext.isString(name)) { return Ext.util.Format.htmlEncode(name); } return Tine.Tinebase.appMgr.get('Calendar').i18n._('No Information'); }, renderAttenderMemberofName: function(name) { return Tine.Calendar.AttendeeGridPanel.prototype.renderAttenderGroupName.apply(this, arguments); }, renderAttenderResourceName: function (name, metadata) { var icon = metadata && metadata.noIcon ? '' : this.renderAttenderResourceIcon(name), displayName = Ext.isString(name) ? name : Ext.isFunction(name.getTitle) ? name.getTitle() : name.name ? name.name : Tine.Tinebase.appMgr.get('Calendar').i18n._('No Information'); return icon + Ext.util.Format.htmlEncode(displayName); }, renderAttenderResourceIcon: function (name) { try { var ResourceId = name.type, icon = Tine.Tinebase.widgets.keyfield.StoreMgr.get('Calendar', 'resourceTypes') .getById(ResourceId).get('icon'); return ''; } catch (e) { } return ''; }, renderAttenderDispContainer: function(displaycontainer_id, metadata, attender) { metadata.attr = 'style = "overflow: none;"'; if (displaycontainer_id) { if (displaycontainer_id.name) { return Ext.util.Format.htmlEncode(displaycontainer_id.name).replace(/ /g," "); } else { metadata.css = 'x-form-empty-field'; return this.app.i18n._('No Information'); } } }, renderAttenderQuantity: function(quantity, metadata, attender) { return quantity > 1 ? quantity : ''; }, renderAttenderRole: function(role, metadata, attender) { var i18n = Tine.Tinebase.appMgr.get('Calendar').i18n, renderer = Tine.widgets.grid.RendererManager.get('Calendar', 'Attender', 'role', Tine.widgets.grid.RendererManager.CATEGORY_GRIDPANEL); if (this.record && this.record.get('editGrant')) { metadata.attr = 'style = "cursor:pointer;"'; } else { metadata.css = 'x-form-empty-field'; } return renderer(role); }, renderAttenderStatus: function(status, metadata, attender) { var i18n = Tine.Tinebase.appMgr.get('Calendar').i18n, renderer = Tine.widgets.grid.RendererManager.get('Calendar', 'Attender', 'status', Tine.widgets.grid.RendererManager.CATEGORY_GRIDPANEL); if (! attender.get('user_id') && attender.get('user_type') !== 'email') { return ''; } if (attender.get('status_authkey')) { metadata.attr = 'style = "cursor:pointer;"'; } else { metadata.css = 'x-form-empty-field'; } return renderer(status); }, renderAttenderType: function(type, metadata, attender) { metadata.css = 'tine-grid-cell-no-dirty'; var cssClass = 'tine-grid-row-action-icon ', qtipText = '', userId = attender.get('user_id'), hasAccount = userId && ((userId.get && userId.get('account_id')) || userId.account_id); switch (type) { case 'user': cssClass += hasAccount || ! userId ? 'renderer_typeAccountIcon' : 'renderer_typeContactIcon'; qtipText = hasAccount || ! userId ? '' : Tine.Tinebase.appMgr.get('Calendar').i18n._('External Attendee'); break; case 'group': cssClass += 'renderer_accountGroupIcon'; break; case 'email': cssClass += 'renderer_typeEmailIcon'; qtipText = Tine.Tinebase.appMgr.get('Calendar').i18n._('External Attendee'); break; default: cssClass += 'cal-attendee-type-' + type; break; } var qtip = qtipText ? 'ext:qtip="' + Tine.Tinebase.common.doubleEncode(qtipText) + '" ': ''; var result = '
 
'; if (! attender.get('user_id') && type !== 'email') { result = Tine.Tinebase.common.cellEditorHintRenderer(result); } return result; }, /** * disable contents not panel */ setDisabled: function(v) { if (v) { // remove "add new attender" row this.store.filterBy(function(r) {return ! (r.id && String(r.id).match(/^new-/))}); } else { this.store.clearFilter(); } } });