Skip to content
This repository has been archived by the owner on Dec 27, 2023. It is now read-only.

Commit

Permalink
feature(Tinebase): auto form for relations fields
Browse files Browse the repository at this point in the history
Change-Id: Iee7db528bf7a5a46f6310ea6dd419c006942913f
Reviewed-on: http://gerrit.tine20.com/customers/18717
Tested-by: Jenkins CI (http://ci.tine20.com/) <tine20-jenkins@metaways.de>
Reviewed-by: Christian Feitl <c.feitl@metaways.de>
  • Loading branch information
corneliusweiss authored and Christian Feitl committed Dec 16, 2020
1 parent d66e0b8 commit e7fbea4
Show file tree
Hide file tree
Showing 7 changed files with 373 additions and 111 deletions.
4 changes: 4 additions & 0 deletions tine20/Tinebase/Tinebase.jsb2
Expand Up @@ -753,6 +753,10 @@
"text": "GenericPickerGridPanel.js",
"path": "js/widgets/relation/"
},
{
"text": "PickerGridPanel.js",
"path": "js/widgets/relation/"
},
{
"text": "RecordRenderer.js",
"path": "js/widgets/printer/"
Expand Down
16 changes: 15 additions & 1 deletion tine20/Tinebase/js/widgets/form/FieldManager.js
Expand Up @@ -180,6 +180,7 @@ Tine.widgets.form.FieldManager = function() {
field.useAccountRecord = true;
break;
case 'relation':
case 'relations':
if (fieldDefinition.config && fieldDefinition.config.appName && fieldDefinition.config.modelName) {
field.xtype = 'tinerelationpickercombo';
field.recordClass = Tine[fieldDefinition.config.appName].Model[fieldDefinition.config.modelName];
Expand All @@ -191,6 +192,16 @@ Tine.widgets.form.FieldManager = function() {
}
// TODO pass degree and other options in config?
field.relationDegree = 'sibling';

if (fieldType === 'relations') {
_.assign(field, {
xtype: 'tinerelationpickergridpanel',
isFormField: true,
fieldName: fieldDefinition.fieldName,
hideHeaders: true,
height: 80 /* 4 records */ + 2 * 26 /* 2 toolbars */
});
}
}
break;
case 'record':
Expand All @@ -212,7 +223,10 @@ Tine.widgets.form.FieldManager = function() {
field.isFormField = true;
field.fieldName = fieldDefinition.fieldName;
field.hideHeaders = true;
field.height = 170; // 5 records
field.height = 80 /* 4 records */ + field.enableTbar * 26 + 26 /* 2 toolbars */
if (_.get(fieldDefinition, 'config.dependentRecords', false)) {
_.set(field, 'editDialogConfig.mode', 'local');
}
} else {
var picker = Tine.widgets.form.RecordsPickerManager.get(
fieldDefinition.config.appName,
Expand Down
25 changes: 18 additions & 7 deletions tine20/Tinebase/js/widgets/grid/PickerGridPanel.js
Expand Up @@ -30,6 +30,17 @@ Ext.ns('Tine.widgets.grid');
* Create a new Tine.widgets.grid.PickerGridPanel
*/
Tine.widgets.grid.PickerGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
/**
* @cfg {Class} editDialogClass (optional)
* editDialogClass having a static openWindow method
*/
editDialogClass: null,

/**
* @cfg {Object} editDialogConfig (optional)
*/
editDialogConfig: null,

/**
* @cfg {bool}
* enable bottom toolbar
Expand Down Expand Up @@ -154,7 +165,7 @@ Tine.widgets.grid.PickerGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
this.initActionsAndToolbars();

this.on('afterrender', this.onAfterRender, this);
this.on('rowdblclick', this.onRowDblClick, this);
this.on('rowdblclick', this.onRowDblClick, this);

Tine.widgets.grid.PickerGridPanel.superclass.initComponent.call(this);
},
Expand Down Expand Up @@ -469,8 +480,7 @@ Tine.widgets.grid.PickerGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
const record = Tine.Tinebase.data.Record.setFromJson(Ext.apply(this.recordClass.getDefaultData(), this.recordDefaults || {}), this.recordClass);
const editDialogClass = this.editDialogClass || Tine.widgets.dialog.EditDialog.getConstructor(this.recordClass);

editDialogClass.openWindow({
mode: 'local',
editDialogClass.openWindow(_.assign({
record: Ext.encode(record.data),
recordId: record.getId(),
listeners: {
Expand All @@ -479,7 +489,7 @@ Tine.widgets.grid.PickerGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
this.store.add(record);
}
}
});
}, this.editDialogConfig || {}));
},

/**
Expand Down Expand Up @@ -575,13 +585,14 @@ Tine.widgets.grid.PickerGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
validate: function() { return true; },

// NOTE: picker picks independed records - so lets support to open them w.o. restirctions
// NO: it might also pick / create /edit depended records - how to detect this?
onRowDblClick: function(grid, row, col) {
var me = this,
editDialogClass = Tine.widgets.dialog.EditDialog.getConstructor(me.recordClass),
editDialogClass = this.editDialogClass || Tine.widgets.dialog.EditDialog.getConstructor(me.recordClass),
record = me.store.getAt(row);

if (editDialogClass) {
editDialogClass.openWindow({
editDialogClass.openWindow(_.assign({
record: record,
recordId: record.getId(),
listeners: {
Expand All @@ -603,7 +614,7 @@ Tine.widgets.grid.PickerGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
me.fireEvent('update', this, updatedRecord);
}
}
});
}, this.editDialogConfig || {}));
}
}
});
Expand Down
178 changes: 90 additions & 88 deletions tine20/Tinebase/js/widgets/relation/GenericPickerGridPanel.js
Expand Up @@ -304,7 +304,9 @@ Tine.widgets.relation.GenericPickerGridPanel = Ext.extend(Tine.widgets.grid.Pick
*/
onEditInNewWindow: async function() {
var _ = window.lodash,
record = this.getRelatedRecord(this.getSelectionModel().getSelected()),
relation = this.getSelectionModel().getSelected(),
record = this.getRelatedRecord(relation),
recordClass = Tine.Tinebase.data.RecordMgr.get(record.appName, record.modelName),
openMethod = record ? _.get(Tine, record.appName + '.' + record.modelName + 'EditDialog.openWindow') : null;

if (! record) {
Expand All @@ -327,10 +329,19 @@ Tine.widgets.relation.GenericPickerGridPanel = Ext.extend(Tine.widgets.grid.Pick

loadMask.hide();
loadMask.destroy();
}else if (openMethod) {
} else if (openMethod) {
openMethod({
record: record,
mode: 'remote'
mode: 'remote',
listeners: {
update: (recordData) => {
const record = Tine.Tinebase.data.Record.setFromJson(recordData, recordClass);
record.data.relations = null;
delete record.data.relations;
relation.set('related_record', record.data);
relation.commit();
}
}
});
} else {
Ext.MessageBox.show({
Expand Down Expand Up @@ -806,10 +817,10 @@ Tine.widgets.relation.GenericPickerGridPanel = Ext.extend(Tine.widgets.grid.Pick
* is called when selecting a record in the searchCombo (relationpickercombo)
*/
onAddRecordFromCombo: async function(node) {
var record = null;
let record = null;
const pickerCombo = this.getActiveSearchCombo();

if (this.getActiveSearchCombo().hasOwnProperty('store')) {
if (pickerCombo.hasOwnProperty('store')) {
record = pickerCombo.store.getById(pickerCombo.getValue())
} else {
if(node.leaf === false) {
Expand All @@ -823,12 +834,9 @@ Tine.widgets.relation.GenericPickerGridPanel = Ext.extend(Tine.widgets.grid.Pick
return;
}

if (Ext.isArray(this.constraintsConfig[this.activeModel])) {
var relconf = {type: this.constraintsConfig[this.activeModel][0]['type']};
} else {
var relconf = {};
}

const relconf = Ext.isArray(this.constraintsConfig[this.activeModel]) ?
{type: this.constraintsConfig[this.activeModel][0]['type']} : {};

this.onAddRecord(record, relconf);

if (Ext.isFunction(pickerCombo.collapse)) {
Expand All @@ -850,7 +858,7 @@ Tine.widgets.relation.GenericPickerGridPanel = Ext.extend(Tine.widgets.grid.Pick
* @param {Tine.Tinebase.data.Record} record
* @param {Object} relconf
*/
onAddRecord: function(record, relconf) {
onAddRecord: async function(record, relconf) {
if (record) {
if (! relconf) {
relconf = {};
Expand All @@ -859,46 +867,31 @@ Tine.widgets.relation.GenericPickerGridPanel = Ext.extend(Tine.widgets.grid.Pick
record.data.relations = null;
delete record.data.relations;
}
var rc = this.getActiveSearchCombo().recordClass;
var relatedPhpModel = rc.getPhpClassName();

var app = rc.getMeta('appName'), model = rc.getMeta('modelName'), f = app + model;
var type = '';
const rc = Tine.Tinebase.data.RecordMgr.get(relconf.related_model) || this.getActiveSearchCombo().recordClass;
const appName = rc.getMeta('appName');
const model = rc.getMeta('modelName');
const f = appName + model;
const type = relconf.type
|| _.get(this.constraintsConfig, f + '[0].type', ''); // per default the first defined type is used

if (this.constraintsConfig[f] && this.constraintsConfig[f].length) {
// per default the first defined type is used
var type = this.constraintsConfig[f][0].type;
}

var rc = this.getActiveSearchCombo().recordClass,
relatedPhpModel = rc.getPhpClassName(),
appName = rc.getMeta('appName'),
model = rc.getMeta('modelName'),
f = appName + model,
type = '';

var relationRecord = new Tine.Tinebase.Model.Relation(Ext.apply(this.getRelationDefaults(), Ext.apply({
const relationRecord = new Tine.Tinebase.Model.Relation(Ext.apply(this.getRelationDefaults(), Ext.apply({
related_record: record.data || record,
related_id: record.id,
related_model: relatedPhpModel,
related_model: rc.getPhpClassName(),
type: type,
related_degree: 'sibling'
}, relconf)), Ext.id());

var mySideValid = true;

let mySideValid = true;
if (this.constraintsConfig[f]) {
if (this.constraintsConfig[f].length) {
// per default the first defined type is used
var type = this.constraintsConfig[f][0].type;
}
// validate constrains config from own side
mySideValid = this.checkLocalConstraints(appName, model, relationRecord, type);
}

// if my side is not valid, it's ok to skip related constraints validation, the relation is marked invalid already
if (mySideValid) {
this.validateRelatedConstrainsConfig(record, relationRecord);
await this.validateRelatedConstrainsConfig(record, relationRecord);
} else {
this.onAddNewRelationToStore(relationRecord, record);
}
Expand All @@ -912,62 +905,71 @@ Tine.widgets.relation.GenericPickerGridPanel = Ext.extend(Tine.widgets.grid.Pick
* @param {Tine.Tinebase.Model.Relation} relationRecord
*/
validateRelatedConstrainsConfig: function(record, relationRecord) {
var rc = relationRecord.get('related_model').split(/_Model_/);
var rc = Tine[rc[0]].Model[rc[1]];

var appName = rc.getMeta('appName');
var model = rc.getMeta('modelName');
var relatedApp = Tine.Tinebase.appMgr.get(appName);
var relatedConstrainsConfig = relatedApp.getRegistry().get('relatableModels');
var ownRecordClassName = this.editDialog.recordClass.getMeta('modelName');
var relatedRecordProxy = this.getActiveSearchCombo().recordProxy
|| Tine[appName][(model.toLowerCase() + 'Backend')]
|| new Tine.Tinebase.data.RecordProxy({
appName: appName,
modelName: model,
recordClass: rc
});

if (! Ext.isFunction(record.get)) {
record = relatedRecordProxy.recordReader({responseText: Ext.encode(record)});
}

if (relatedConstrainsConfig) {
for (var index = 0; index < relatedConstrainsConfig.length; index++) {
var rcc = relatedConstrainsConfig[index];

if ((rcc.relatedApp == this.app.name) && (rcc.relatedModel == ownRecordClassName)) {
var myRelatedConstrainsConfig = rcc;
break;
return new Promise((fulfill, reject) => {
const rc = Tine.Tinebase.data.RecordMgr.get(relationRecord.get('related_model'))
|| this.getActiveSearchCombo().recordClass;

var appName = rc.getMeta('appName');
var model = rc.getMeta('modelName');
var relatedApp = Tine.Tinebase.appMgr.get(appName);
var relatedConstrainsConfig = relatedApp.getRegistry().get('relatableModels');
var ownRecordClassName = this.editDialog.recordClass.getMeta('modelName');

var relatedRecordProxy = _.get(this.searchCombos, appName+model + '.recordProxy')
|| Tine[appName][(model.toLowerCase() + 'Backend')]
|| new Tine.Tinebase.data.RecordProxy({
appName: appName,
modelName: model,
recordClass: rc
});

if (! Ext.isFunction(record.get)) {
record = relatedRecordProxy.recordReader({responseText: Ext.encode(record)});
}

if (relatedConstrainsConfig) {
for (var index = 0; index < relatedConstrainsConfig.length; index++) {
var rcc = relatedConstrainsConfig[index];

if ((rcc.relatedApp == this.app.name) && (rcc.relatedModel == ownRecordClassName)) {
var myRelatedConstrainsConfig = rcc;
break;
}
}
}
}

// validate constrains config from other side if a config exists
if (myRelatedConstrainsConfig) {
// if relations hasn't been fetched already, fetch them now
if (! Ext.isArray(record.data.relations) || record.data.relations.length === 0) {
relatedRecordProxy.loadRecord(record, {
success: function(record) {
// if record has relations, validate each relation
if (Ext.isArray(record.get('relations')) && record.get('relations').length > 0) {
this.onValidateRelatedConstrainsConfig(myRelatedConstrainsConfig.config, relationRecord, appName, model, record);
} else {
// if there aren't any relations, no validation is needed
this.onAddNewRelationToStore(relationRecord, record);

// validate constrains config from other side if a config exists
if (myRelatedConstrainsConfig) {
// if relations hasn't been fetched already, fetch them now
if (! Ext.isArray(record.data.relations) || record.data.relations.length === 0) {
relatedRecordProxy.loadRecord(record, {
scope: this,
success: function(record) {
// if record has relations, validate each relation
if (Ext.isArray(record.get('relations')) && record.get('relations').length > 0) {
this.onValidateRelatedConstrainsConfig(myRelatedConstrainsConfig.config, relationRecord, appName, model, record);
} else {
// if there aren't any relations, no validation is needed
this.onAddNewRelationToStore(relationRecord, record);
}
fulfill();
},
// don't break on failure, use given record instead
failure: function() {
this.onAddNewRelationToStore.createDelegate(this, [relationRecord, record])
fulfill();
}
},
// don't break on failure, use given record instead
failure: this.onAddNewRelationToStore.createDelegate(this, [relationRecord, record]),
scope: this
});

});

} else {
this.onValidateRelatedConstrainsConfig(myRelatedConstrainsConfig.config, relationRecord, appName, model, record);
fulfill();
}
} else {
this.onValidateRelatedConstrainsConfig(myRelatedConstrainsConfig.config, relationRecord, appName, model, record);
this.onAddNewRelationToStore(relationRecord, record);
fulfill();
}
} else {
this.onAddNewRelationToStore(relationRecord, record);
}
});
},

/**
Expand Down

0 comments on commit e7fbea4

Please sign in to comment.