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)
8 Ext.ns('Tine.widgets.dialog');
11 * Generic 'Edit Record' dialog
12 * Base class for all 'Edit Record' dialogs
14 * @namespace Tine.widgets.dialog
15 * @class Tine.widgets.dialog.EditDialog
16 * @extends Ext.FormPanel
17 * @author Cornelius Weiss <c.weiss@metaways.de>
19 * @param {Object} config The configuration options.
22 Tine.widgets.dialog.EditDialog = Ext.extend(Ext.FormPanel, {
24 * @cfg {Tine.Tinebase.Application} app
25 * instance of the app object (required)
30 * Set to 'local' if the EditDialog only operates on this.record (defaults to 'remote' which loads and saves using the recordProxy)
34 * @cfg {Array} tbarItems
35 * additional toolbar items (defaults to false)
39 * internal/untranslated app name (required)
41 * @cfg {String} appName
45 * the modelName (filled by application starter)
47 * @type {String} modelName
52 * record definition class (required if no modelconfig! don't declare for modelconfig!!)
54 * @cfg {Ext.data.Record} recordClass
58 * @cfg {Ext.data.DataProxy} recordProxy
62 * @cfg {Bool} showContainerSelector
63 * show container selector in bottom area
65 showContainerSelector: null,
67 * @cfg {Bool} evalGrants
68 * should grants of a grant-aware records be evaluated (defaults to true)
72 * @cfg {String} requiredSaveGrant
73 * required grant for apply/save
75 requiredSaveGrant: 'editGrant',
78 * @cfg {Ext.data.Record} record
79 * record in edit process.
84 * holds the modelConfig for the handled record (json-encoded object)
85 * will be decoded in initComponent
92 * @cfg {String} saveAndCloseButtonText
93 * text of save and close button
95 saveAndCloseButtonText: '',
97 * @cfg {String} cancelButtonText
98 * text of cancel button
100 cancelButtonText: '',
103 * @cfg {Boolean} copyRecord
109 * @cfg {Boolean} doDuplicateCheck
110 * do duplicate check when saving record (mode remote only)
112 doDuplicateCheck: true,
115 * when a record has the relations-property the relations-panel can be disabled here
116 * @cfg {Boolean} hideRelationsPanel
118 hideRelationsPanel: false,
121 * when a record has the attachments-property the attachments-panel can be disabled here
122 * @cfg {Boolean} hideAttachmentsPanel
124 hideAttachmentsPanel: false,
127 * Registry for other relationgridpanels than the generic one,
128 * handling special types of relations the generic one will not.
129 * Panels registered here must have a store with the relation records.
133 relationPanelRegistry: null,
136 * ignore relations to given php-class names in the relation grid
139 ignoreRelatedModels: null,
142 * dialog is currently saving data
148 * Disable adding cf tab even if model has support for customfields
154 * @property window {Ext.Window|Ext.ux.PopupWindow|Ext.Air.Window}
157 * @property {Number} loadRequest
158 * transaction id of loadData request
161 * @property loadMask {Ext.LoadMask}
165 * @property containerSelectCombo {Tine.widgets.container.SelectionComboBox}
167 containerSelectCombo: null,
170 * If set, these fields are readOnly (when called dependent to related record)
172 * @type {Ext.util.MixedCollection}
177 * Plain Object with additional configuration (JSON-encoded)
181 additionalConfig: null,
185 * @cfg {String} canonicalName
187 canonicalName: 'EditDialog',
190 bodyStyle:'padding:5px',
193 cls: 'tw-editdialog',
195 deferredRender: false,
202 * @type Tine.widgets.relation.GenericPickerGridPanel
204 relationsPanel: null,
206 // Array of Relation Pickers
207 relationPickers: null,
212 * @type Tine.widgets.dialog.AttachmentsGridPanel
214 attachmentsPanel: null,
218 * set this to false, if no loadMask should be shown
220 * @type {Ext.LoadMask}
225 * hook notes panel into dialog
232 initComponent: function() {
233 this.relationPanelRegistry = this.relationPanelRegistry ? this.relationPanelRegistry : [];
237 * Fired when user pressed cancel button
241 * @event saveAndClose
242 * Fired when user pressed OK button
247 * @desc Fired when the record got updated
248 * @param {Json String} data data of the entry
249 * @pram {String} this.mode
254 * Fired when user pressed apply button
259 * @param {Tine.widgets.dialog.EditDialog} this
260 * @param {Tine.data.Record} record which got loaded
261 * @param {Function} ticket function for async defer
262 * Fired when record is loaded
267 * @param {Tine.widgets.dialog.EditDialog} this
268 * @param {Tine.data.Record} record which got loaded
269 * @param {Function} ticket function for async defer
270 * Fired when remote record is saving
274 * @event updateDependent
275 * Fired when a subpanel updates the record locally
280 * Fires just before the field blurs if the field value has changed.
281 * @param {Ext.form.Field} this
282 * @param {Mixed} newValue The new value
283 * @param {Mixed} oldValue The original value
288 if (Ext.isString(this.modelConfig)) {
289 this.modelConfig = Ext.decode(this.modelConfig);
292 if (Ext.isString(this.additionalConfig)) {
293 Ext.apply(this, Ext.decode(this.additionalConfig));
296 if (Ext.isString(this.fixedFields)) {
297 var decoded = Ext.decode(this.fixedFields);
298 this.fixedFields = new Ext.util.MixedCollection();
299 this.fixedFields.addAll(decoded);
302 if (! this.recordClass && this.modelName) {
303 this.recordClass = Tine[this.appName].Model[this.modelName];
306 if (this.recordClass) {
307 this.appName = this.appName ? this.appName : this.recordClass.getMeta('appName');
308 this.modelName = this.modelName ? this.modelName : this.recordClass.getMeta('modelName');
312 this.app = Tine.Tinebase.appMgr.get(this.appName);
315 if (! this.windowNamePrefix) {
316 this.windowNamePrefix = this.modelName + 'EditWindow_';
319 Tine.log.debug('initComponent: appName: ', this.appName);
320 Tine.log.debug('initComponent: modelName: ', this.modelName);
321 Tine.log.debug('initComponent: app: ', this.app);
323 // init some translations
324 if (this.app.i18n && this.recordClass !== null) {
325 this.i18nRecordName = this.app.i18n.n_hidden(this.recordClass.getMeta('recordName'), this.recordClass.getMeta('recordsName'), 1);
326 this.i18nRecordsName = this.app.i18n._hidden(this.recordClass.getMeta('recordsName'));
330 if (! this.recordProxy && this.recordClass) {
331 Tine.log.debug('no record proxy given, creating a new one...');
332 this.recordProxy = new Tine.Tinebase.data.RecordProxy({
333 recordClass: this.recordClass
338 if (this.recordClass) {
339 var grantsField = this.recordClass.getMeta('grantsPath')
340 .replace(/^data\./, '')
341 .replace(/\.+/g, '');
343 this.evalGrants = this.evalGrants && (grantsField == 'data' || this.recordClass.hasField(grantsField));
347 this.plugins = Ext.isString(this.plugins) ? Ext.decode(this.plugins) : Ext.isArray(this.plugins) ? this.plugins.concat(Ext.decode(this.initialConfig.plugins)) : [];
349 this.plugins.push(this.tokenModePlugin = new Tine.widgets.dialog.TokenModeEditDialogPlugin({}));
350 // added possibility to disable using customfield plugin
351 if (this.disableCfs !== true) {
352 this.plugins.push(new Tine.widgets.customfields.EditDialogPlugin({}));
357 // init buttons and tbar
359 // init container selector
360 this.initContainerSelector();
363 // get items for this dialog
364 this.items = this.getFormItems();
366 // init relations panel if relations are defined
367 this.initRelationsPanel();
368 // init attachments panel
369 this.initAttachmentsPanel();
371 this.initNotesPanel();
373 Tine.widgets.dialog.EditDialog.superclass.initComponent.call(this);
375 // set fields readOnly if set
378 // firefox fix: blur each item before tab changes, so no field will be focused afterwards
380 this.items.items[0].addListener('beforetabchange', function(tabpanel, newtab, oldtab) {
384 var form = this.getForm();
386 if (form && form.hasOwnProperty('items'))
387 form.items.each(function(item, index) {
393 if (Ext.isFunction(this.window.relayEvents) && Tine.Tinebase.featureEnabled('featureRememberPopupSize')) {
394 this.window.relayEvents(this, ['resize']);
399 * returns canonical path part
402 getCanonicalPathSegment: function () {
403 if (this.recordClass) {
405 this.recordClass.getMeta('appName'),
407 this.recordClass.getMeta('modelName'),
408 ].join(Tine.Tinebase.CanonicalPath.separator);
413 * generic form layout
415 getFormItems: function() {
426 ptype : 'ux.tabpanelkeyplugin'
430 title: this.i18nRecordName,
435 defaults: { autoScroll: true },
436 items: [Ext.applyIf(this.getRecordFormItems(), {
446 })].concat(this.getEastPanel())
447 }, new Tine.widgets.activities.ActivitiesTabPanel({
449 record_id: this.record.id,
450 record_model: this.modelName
456 getEastPanel: function() {
458 if (this.recordClass.hasField('description')) {
459 items.push(new Ext.Panel({
460 title: i18n._('Description'),
461 iconCls: 'descriptionIcon',
466 style: 'margin-top: -4px; border 0px;',
472 preventScrollbars: false,
474 emptyText: i18n._('Enter description'),
475 requiredGrant: 'editGrant'
480 if (this.recordClass.hasField('tags')) {
481 items.push(new Tine.widgets.tags.TagPanel({
484 bodyStyle: 'border:1px solid #B5B8C8;'
488 return items.length ? {
495 collapseMode: 'mini',
503 getRecordFormItems: function() {
504 return new Tine.widgets.form.RecordForm({
505 recordClass: this.recordClass
510 * fix fields (used for preselecting form fields when called in dependency to another record)
513 fixFields: function() {
514 if (this.fixedFields && this.fixedFields.getCount() > 0) {
515 if (! this.rendered) {
516 this.fixFields.defer(100, this);
520 this.fixedFields.each(function(value, index) {
521 var key = this.fixedFields.keys[index];
523 var field = this.getForm().findField(key);
526 if (Ext.isFunction(this.recordClass.getField(key).type)) {
527 var foreignRecordClass = this.recordClass.getField(key).type;
528 var record = new foreignRecordClass(value);
529 field.selectedRecord = record;
530 field.setValue(value);
531 field.fireEvent('select');
533 field.setValue(value);
542 * call checkState for every field
544 checkStates: function() {
545 this.onRecordUpdate();
546 this.getForm().items.each(function (item) {
547 if (Ext.isFunction(item.checkState)) {
548 item.checkState(this, this.record);
554 * Get available model for given application
556 * @param {Mixed} application
557 * @param {Boolean} customFieldModel
559 getApplicationModels: function (application, customFieldModel) {
562 appName = Ext.isString(application) ? application : application.get('name'),
563 app = Tine.Tinebase.appMgr.get(appName),
564 trans = app && app.i18n ? app.i18n : i18n,
565 appModels = Tine[appName].Model;
568 for (var model in appModels) {
569 if (appModels.hasOwnProperty(model) && typeof appModels[model].getMeta === 'function') {
570 if (customFieldModel && appModels[model].getField('customfields')) {
571 useModel = appModels[model].getMeta('appName') + '_Model_' + appModels[model].getMeta('modelName');
573 Tine.log.info('Found model with customfields property: ' + useModel);
574 models.push([useModel, trans.n_(appModels[model].getMeta('recordName'), appModels[model].getMeta('recordsName'), 1)]);
575 } else if (! customFieldModel) {
576 useModel = 'Tine.' + appModels[model].getMeta('appName') + '.Model.' + appModels[model].getMeta('modelName');
578 Tine.log.info('Found model: ' + useModel);
579 models.push([useModel, trans.n_(appModels[model].getMeta('recordName'), appModels[model].getMeta('recordsName'), 1)]);
590 initActions: function() {
591 this.action_saveAndClose = new Ext.Action({
592 requiredGrant: this.requiredSaveGrant,
593 text: (this.saveAndCloseButtonText != '') ? this.app.i18n._(this.saveAndCloseButtonText) : i18n._('Ok'),
595 ref: '../btnSaveAndClose',
597 // TODO: remove the defer when all subpanels use the deferByTicket mechanism
598 handler: function() { this.onSaveAndClose.defer(500, this); },
599 iconCls: 'action_saveAndClose'
602 this.action_applyChanges = new Ext.Action({
603 requiredGrant: this.requiredSaveGrant,
604 text: i18n._('Apply'),
606 ref: '../btnApplyChanges',
608 handler: this.onApplyChanges,
609 iconCls: 'action_applyChanges'
612 this.action_cancel = new Ext.Action({
613 text: (this.cancelButtonText != '') ? this.app.i18n._(this.cancelButtonText) : i18n._('Cancel'),
616 handler: this.onCancel,
617 iconCls: 'action_cancel'
620 this.action_delete = new Ext.Action({
621 requiredGrant: 'deleteGrant',
622 text: i18n._('delete'),
625 handler: this.onDelete,
626 iconCls: 'action_delete',
630 this.action_export = Tine.widgets.exportAction.getExportButton(this.recordClass, {
631 getExportOptions: this.getExportOptions.createDelegate(this)
632 }, Tine.widgets.exportAction.SCOPE_SINGLE);
635 this.actionUpdater = new Tine.widgets.ActionUpdater({
636 recordClass: this.recordClass,
637 evalGrants: this.evalGrants
640 this.actionUpdater.addActions([
641 this.action_saveAndClose,
642 this.action_applyChanges,
649 * get export options/data
651 getExportOptions: function() {
652 this.onRecordUpdate();
654 recordData: this.record.data
661 * use button order from preference
663 initButtons: function () {
668 this.fbar.push(this.action_cancel, this.action_saveAndClose);
670 if (this.action_export) {
671 this.actionUpdater.addAction(this.action_export);
672 this.tbarItems = this.tbarItems || [];
673 this.tbarItems.push(this.action_export);
676 if (this.tbarItems && this.tbarItems.length) {
677 this.actionUpdater.addActions(this.tbarItems);
678 this.tbar = new Ext.Toolbar({
679 items: this.tbarItems
685 * init container selector
687 initContainerSelector: function() {
688 if (this.showContainerSelector) {
689 this.containerSelectCombo = new Tine.widgets.container.SelectionComboBox({
690 id: this.app.appName + 'EditDialogContainerSelector-' + Ext.id(),
691 fieldLabel: i18n._('Saved in'),
694 name: this.recordClass.getMeta('containerProperty'),
695 recordClass: this.recordClass,
696 containerName: this.app.i18n.n_hidden(this.recordClass.getMeta('containerName'), this.recordClass.getMeta('containersName'), 1),
697 containersName: this.app.i18n._hidden(this.recordClass.getMeta('containersName')),
698 appName: this.app.appName,
699 requiredGrant: this.evalGrants ? 'addGrant' : false,
700 disabled: this.isContainerSelectorDisabled(),
704 // enable or disable save button dependent to containers account grants
705 // on edit: check editGrant, on add: check addGrant
706 var grants = this.containerSelectCombo.selectedContainer
707 ? this.containerSelectCombo.selectedContainer.account_grants : {},
708 grantToCheck = (this.record.data.id) ? 'editGrant' : 'addGrant',
709 enabled = grants.hasOwnProperty(grantToCheck) && grants[grantToCheck]
710 || grants.hasOwnProperty('adminGrant') && grants.adminGrant ? true : false;
712 this.action_saveAndClose.setDisabled(! enabled);
716 this.on('render', function() { this.getForm().add(this.containerSelectCombo); }, this);
720 this.containerSelectCombo
727 * checks if the container selector should be disabled (dependent on account grants of the container itself)
730 isContainerSelectorDisabled: function() {
732 var cp = this.recordClass.getMeta('containerProperty'),
733 container = this.record.data[cp],
734 grants = (container && container.hasOwnProperty('account_grants')) ? container.account_grants : null,
737 // check grants if record already exists and grants should be evaluated
738 if(this.evalGrants && this.record.data.id && grants) {
739 cond = ! (grants.hasOwnProperty('editGrant') && grants.editGrant);
749 * init record to edit
751 initRecord: function() {
752 Tine.log.debug('init record with mode: ' + this.mode);
754 Tine.log.debug('creating new default data record');
755 this.record = new this.recordClass(this.recordClass.getDefaultData(), 0);
758 if (this.mode !== 'local') {
759 if (this.record && this.record.id) {
760 this.loadRemoteRecord();
762 this.onRecordLoad.defer(10, this);
765 // note: in local mode we expect a valid record
766 if (! Ext.isFunction(this.record.beginEdit)) {
767 this.record = this.recordProxy.recordReader({responseText: this.record});
769 this.onRecordLoad.defer(10, this);
774 * load record via record proxy
776 loadRemoteRecord: function() {
777 Tine.log.info('initiating record load via proxy');
778 this.loadRequest = this.recordProxy.loadRecord(this.record, {
780 success: function(record) {
781 this.record = record;
788 * copy this.record record
790 doCopyRecord: function() {
791 this.record = this.doCopyRecordToReturn(this.record);
795 * Copy record and returns "new record with same settings"
799 doCopyRecordToReturn: function(record) {
800 var omitFields = this.recordClass.getMeta('copyOmitFields') || [];
801 // always omit id + notes + attachments
802 omitFields = omitFields.concat(['id', 'notes', 'attachments', 'relations']);
804 var fieldsToCopy = this.recordClass.getFieldNames().diff(omitFields),
805 recordData = Ext.copyTo({}, record.data, fieldsToCopy);
807 var resetProperties = {
808 alarms: ['id', 'record_id', 'sent_time', 'sent_message'],
809 relations: ['id', 'own_id', 'created_by', 'creation_time', 'last_modified_by', 'last_modified_time']
812 var setProperties = {alarms: {sent_status: 'pending'}};
814 Ext.iterate(resetProperties, function(property, properties) {
815 if (recordData.hasOwnProperty(property)) {
816 var r = recordData[property];
817 for (var index = 0; index < r.length; index++) {
820 r[index][prop] = null;
827 Ext.iterate(setProperties, function(property, properties) {
828 if (recordData.hasOwnProperty(property)) {
829 var r = recordData[property];
830 for (var index = 0; index < r.length; index++) {
831 Ext.iterate(properties,
832 function(prop, value) {
833 r[index][prop] = value;
840 return new this.recordClass(recordData, 0);
845 * executed after record got updated from proxy
847 onRecordLoad: function() {
848 // interrupt process flow until dialog is rendered
849 if (! this.rendered) {
850 this.onRecordLoad.defer(250, this);
853 Tine.log.debug('Tine.widgets.dialog.EditDialog::onRecordLoad() - Loading of the following record completed:');
854 Tine.log.debug(this.record);
856 if (this.copyRecord) {
858 this.window.setTitle(String.format(i18n._('Copy {0}'), this.i18nRecordName));
860 if (! this.record.id) {
861 this.window.setTitle(String.format(i18n._('Add New {0}'), this.i18nRecordName));
863 this.window.setTitle(String.format(i18n._('Edit {0} "{1}"'), this.i18nRecordName, this.record.getTitle()));
867 var ticketFn = this.onAfterRecordLoad.deferByTickets(this),
868 wrapTicket = ticketFn();
870 this.fireEvent('load', this, this.record, ticketFn);
874 // finally load the record into the form
875 onAfterRecordLoad: function() {
876 var _ = window.lodash,
877 form = this.getForm();
880 form.loadRecord(this.record);
884 if (this.record && this.record.hasOwnProperty('data') && Ext.isObject(this.record.data[this.recordClass.getMeta('containerProperty')])) {
885 this.updateToolbars(this.record, this.recordClass.getMeta('containerProperty'));
888 // add current timestamp as id, if this is a dependent record
889 if (this.modelConfig && this.modelConfig.isDependent == true && this.record.id == 0) {
890 this.record.set('id', (new Date()).getTime());
893 // apply grants to fields with requiredGrant prop
894 if (this.evalGrants) {
895 this.getForm().items.each(function (f) {
896 if (f.isFormField && f.requiredGrant !== undefined) {
897 var hasRequiredGrant = _.get(this.record, this.recordClass.getMeta('grantsPath') + '.' + f.requiredGrant);
898 f.setDisabled(!hasRequiredGrant);
903 this.checkStates.defer(100, this);
905 if (this.loadMask && !this.saving) {
906 this.loadMask.hide();
911 * executed when record gets updated from form
913 onRecordUpdate: function(callback, scope) {
914 var form = this.getForm();
916 // merge changes from form into record
917 form.updateRecord(this.record);
919 //TODO Use Promises instead of Tickets if async is needed
920 this.fireEvent('recordUpdate', this, this.record);
926 onRender : function(ct, position){
927 Tine.widgets.dialog.EditDialog.superclass.onRender.call(this, ct, position);
929 // generalized keybord map for edit dlgs
930 new Ext.KeyMap(this.el, [
932 key: [10,13], // ctrl + return
936 if (this.getForm().hasOwnProperty('items')) {
937 // force set last selected field
938 this.getForm().items.each(function(item) {
940 item.setValue(item.getRawValue());
944 this.action_saveAndClose.execute();
949 if (this.loadMask !== false && this.i18nRecordName) {
950 this.loadMask = new Ext.LoadMask(ct, {msg: String.format(i18n._('Transferring {0}...'), this.i18nRecordName)});
951 this.loadMask.show();
955 var form = this.getForm().items.each(function(item) {
956 this.relayEvents(item, ['change', 'select']);
958 this.on('change', this.checkStates, this, {buffer: 100});
959 this.on('select', this.checkStates, this, {buffer: 100});
963 * update (action updateer) top and bottom toolbars
965 updateToolbars: function(record, containerField) {
966 if (! this.evalGrants) {
971 this.action_saveAndClose,
972 this.action_applyChanges,
976 this.actionUpdater.updateActions(record);
982 getToolbar: function() {
983 return this.getTopToolbar();
991 isValid: function() {
993 return new Promise(function (fulfill, reject) {
994 if (me.getForm().isValid()) {
997 reject(me.getValidationErrorMessage())
1003 * vaidates on multiple edit
1007 isMultipleValid: function() {
1014 onCancel : function(){
1015 this.fireEvent('cancel');
1016 this.purgeListeners();
1017 this.window.close();
1023 onSaveAndClose: function() {
1024 this.fireEvent('saveAndClose');
1025 this.onApplyChanges(true);
1029 * generic apply changes handler
1030 * @param {Boolean} closeWindow
1032 onApplyChanges: function(closeWindow) {
1038 this.loadMask.show();
1040 var ticketFn = this.doApplyChanges.deferByTickets(this, [closeWindow]),
1041 wrapTicket = ticketFn();
1043 this.fireEvent('save', this, this.record, ticketFn);
1048 * is called from onApplyChanges
1049 * @param {Boolean} closeWindow
1051 doApplyChanges: function(closeWindow) {
1052 // we need to sync record before validating to let (sub) panels have
1053 // current data of other panels
1054 this.onRecordUpdate();
1057 this.copyRecord = false;
1059 var isValid = this.isValid(),
1063 if (Ext.isDefined(isValid) && ! Ext.isFunction(isValid.then)) {
1064 // convert legacy isValid into promise
1065 isValid = new Promise(function (fulfill, reject) {;
1066 return vBool ? fulfill(true) : reject(me.getValidationErrorMessage());
1070 isValid.then(function () {
1071 if (me.mode !== 'local') {
1072 me.recordProxy.saveRecord(me.record, {
1074 success: function (record) {
1075 // override record with returned data
1077 if (!Ext.isFunction(me.window.cascade)) {
1078 // update form with this new data
1079 // NOTE: We update the form also when window should be closed,
1080 // cause sometimes security restrictions might prevent
1081 // closing of native windows
1084 var ticketFn = me.onAfterApplyChanges.deferByTickets(me, [closeWindow]),
1085 wrapTicket = ticketFn();
1087 me.fireEvent('update', Ext.util.JSON.encode(me.record.data), me.mode, me, ticketFn);
1090 failure: me.onRequestFailed,
1091 timeout: 300000 // 5 minutes
1092 }, me.getAdditionalSaveParams(me));
1095 var ticketFn = me.onAfterApplyChanges.deferByTickets(me, [closeWindow]),
1096 wrapTicket = ticketFn();
1098 me.fireEvent('update', Ext.util.JSON.encode(me.record.data), me.mode, me, ticketFn);
1101 }, function (message) {
1104 Ext.MessageBox.alert(i18n._('Errors'), message);
1109 * returns additional save params
1111 * @param {EditDialog} me
1112 * @returns {{duplicateCheck: boolean}}
1114 getAdditionalSaveParams: function(me) {
1116 duplicateCheck: me.doDuplicateCheck
1120 onAfterApplyChanges: function(closeWindow) {
1121 this.window.rename(this.windowNamePrefix + this.record.id);
1122 this.saving = false;
1125 this.window.fireEvent('saveAndClose');
1126 this.purgeListeners();
1127 this.window.close();
1129 this.loadMask.hide();
1134 * get validation error message
1138 getValidationErrorMessage: function() {
1139 return i18n._('Please fix the errors noted.');
1143 * generic delete handler
1145 onDelete: function(btn, e) {
1146 Ext.MessageBox.confirm(i18n._('Confirm'), String.format(i18n._('Do you really want to delete this {0}?'), this.i18nRecordName), function(_button) {
1148 var deleteMask = new Ext.LoadMask(this.getEl(), {msg: String.format(i18n._('Deleting {0}'), this.i18nRecordName)});
1151 this.recordProxy.deleteRecords(this.record, {
1153 success: function() {
1154 this.purgeListeners();
1155 this.window.close();
1157 failure: function () {
1158 Ext.MessageBox.alert(i18n._('Failed'), String.format(i18n._('Could not delete {0}.'), this.i18nRecordName));
1159 Ext.MessageBox.hide();
1167 * duplicate(s) found exception handler
1169 * @param {Object} exception
1171 onDuplicateException: function(exception) {
1172 var resolveGridPanel = new Tine.widgets.dialog.DuplicateResolveGridPanel({
1174 store: new Tine.widgets.dialog.DuplicateResolveStore({
1176 recordClass: this.recordClass,
1177 recordProxy: this.recordProxy,
1179 clientRecord: exception.clientRecord,
1180 duplicates: exception.duplicates
1186 this.action_saveAndClose
1190 // intercept save handler
1191 resolveGridPanel.btnSaveAndClose.setHandler(function(btn, e) {
1192 var resolveStrategy = resolveGridPanel.store.resolveStrategy;
1194 // action discard -> close window
1195 if (resolveStrategy == 'discard') {
1196 return this.onCancel();
1199 this.record = resolveGridPanel.store.getResolvedRecord();
1201 // quit copy mode before populating form with resolved data
1202 this.copyRecord = false;
1203 this.onRecordLoad();
1205 mainCardPanel.layout.setActiveItem(this.id);
1206 resolveGridPanel.doLayout();
1208 this.doDuplicateCheck = false;
1209 this.onSaveAndClose();
1212 // place in viewport
1213 this.window.setTitle(String.format(i18n._('Resolve Duplicate {0} Suspicion'), this.i18nRecordName));
1214 var mainCardPanel = this.findParentBy(function(p) {return p.isWindowMainCardPanel });
1215 mainCardPanel.add(resolveGridPanel);
1216 mainCardPanel.layout.setActiveItem(resolveGridPanel.id);
1217 resolveGridPanel.doLayout();
1221 * generic request exception handler
1223 * @param {Object} exception
1225 onRequestFailed: function(exception) {
1226 this.saving = false;
1228 if (this.exceptionHandlingMap && this.exceptionHandlingMap[exception.code] && typeof this.exceptionHandlingMap[exception.code] === 'function') {
1229 this.exceptionHandlingMap[exception.code](exception);
1231 } else if (exception.code == 629) {
1232 this.onDuplicateException.apply(this, arguments);
1235 Tine.Tinebase.ExceptionHandler.handleRequestException(exception);
1238 this.loadMask.hide();
1242 * creates the relations panel, if relations are defined
1244 initRelationsPanel: function() {
1245 if (! this.hideRelationsPanel && this.recordClass && this.recordClass.hasField('relations')) {
1246 // init relations panel before onRecordLoad
1247 if (! this.relationsPanel) {
1248 this.relationsPanel = new Tine.widgets.relation.GenericPickerGridPanel({ anchor: '100% 100%', editDialog: this });
1250 // interrupt process flow until dialog is rendered
1251 if (! this.rendered) {
1252 this.initRelationsPanel.defer(250, this);
1255 // add relations panel if this is rendered
1256 if (this.items.items[0]) {
1257 this.items.items[0].add(this.relationsPanel);
1260 Tine.log.debug('Tine.widgets.dialog.EditDialog::initRelationsPanel() - Initialized relations panel and added to dialog tab items.');
1265 * create notes panel
1267 initNotesPanel: function() {
1268 // This dialog is pretty generic but for some cases it's used in a differend way
1269 if(this.displayNotes == true) {
1270 this.items.items.push(new Tine.widgets.activities.ActivitiesGridPanel({
1271 anchor: '100% 100%',
1278 * creates attachments panel
1280 initAttachmentsPanel: function() {
1281 if (! this.attachmentsPanel && ! this.hideAttachmentsPanel && this.recordClass && this.recordClass.hasField('attachments') && Tine.Tinebase.registry.get('filesystemAvailable')) {
1282 this.attachmentsPanel = new Tine.widgets.dialog.AttachmentsGridPanel({ anchor: '100% 100%', editDialog: this });
1283 this.items.items.push(this.attachmentsPanel);