0011966: create grid and dialogs from model config
authorCornelius Weiß <c.weiss@metaways.de>
Fri, 1 Jul 2016 08:47:55 +0000 (10:47 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Mon, 4 Jul 2016 11:07:23 +0000 (13:07 +0200)
* autocreate edit dialogs from modelconfig

https://forge.tine20.org/view.php?id=11966

Change-Id: Ib1cf9e94fbc43d07e2715f25ac82b9476989f282
Reviewed-on: http://gerrit.tine20.com/customers/3294
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
tine20/Tinebase/Tinebase.jsb2
tine20/Tinebase/js/ApplicationStarter.js
tine20/Tinebase/js/data/Record.js
tine20/Tinebase/js/ux/form/BooleanCombo.js [new file with mode: 0644]
tine20/Tinebase/js/widgets/dialog/EditDialog.js
tine20/Tinebase/js/widgets/form/FieldManager.js [new file with mode: 0644]

index 913ffdc..eefb8ac 100644 (file)
           "path": "js/widgets/form/"
         },
         {
+          "text": "FieldManager.js",
+          "path": "js/widgets/form/"
+        },
+        {
           "text": "AddToRecordPanel.js",
           "path": "js/widgets/dialog/"
         },
           "path": "js/ux/form/"
         },
         {
+          "text": "BooleanCombo.js",
+          "path": "js/ux/form/"
+        },
+        {
           "text": "ClearableDateField.js",
           "path": "js/ux/form/"
         },
index 8d1bc77..7b11407 100644 (file)
@@ -433,7 +433,7 @@ Tine.Tinebase.ApplicationStarter = {
                     // create model
                     if (! Tine[appName].Model.hasOwnProperty(modelName)) {
                         Tine[appName].Model[modelName] = Tine.Tinebase.data.Record.create(Tine[appName].Model[modelArrayName], 
-                            Ext.copyTo({}, modelConfig, 
+                            Ext.copyTo({modelConfiguration: modelConfig}, modelConfig,
                                'idProperty,defaultFilter,appName,modelName,recordName,recordsName,titleProperty,containerProperty,containerName,containersName,group')
                         );
                         Tine[appName].Model[modelName].getFilterModel = function() {
@@ -511,6 +511,12 @@ Tine.Tinebase.ApplicationStarter = {
                     
                     // create editDialog openWindow function only if edit dialog exists
                     var editDialogName = modelName + 'EditDialog';
+                    if (! Tine[appName].hasOwnProperty(editDialogName)) {
+                        Tine[appName][editDialogName] = Ext.extend(Tine.widgets.dialog.EditDialog, {
+                            displayNotes: Tine[appName].Model[modelName].hasField('notes')
+                        });
+                    }
+
                     
                     if (Tine[appName].hasOwnProperty(editDialogName)) {
                         var edp = Tine[appName][editDialogName].prototype;
index 5441911..73deebd 100644 (file)
@@ -306,6 +306,9 @@ Tine.Tinebase.data.Record.create = function(o, meta) {
         var appName = (Ext.isObject(app) && app.hasOwnProperty('name')) ? app.name : app;
         return appName + '_Model_' + model;
     };
+    f.getModelConfiguration = function() {
+        return p.modelConfiguration;
+    };
     
     // sanitize containerProperty label
     var containerProperty = f.getMeta('containerProperty');
diff --git a/tine20/Tinebase/js/ux/form/BooleanCombo.js b/tine20/Tinebase/js/ux/form/BooleanCombo.js
new file mode 100644 (file)
index 0000000..76f3f30
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Tine 2.0
+ * 
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Cornelius Weiss <c.weiss@metaways.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+
+/*global Ext*/
+
+Ext.ns('Ext.ux', 'Ext.ux.form');
+
+/**
+ * a yes/no combo box
+ *
+ * @namespace   Ext.ux.form
+ * @class       Ext.ux.form.BooleanCombo
+ * @extends     Ext.form.ComboBox
+ */
+Ext.ux.form.BooleanCombo = Ext.extend(Ext.form.ComboBox, {
+    mode: 'local',
+    forceSelection: true,
+    allowEmpty: false,
+    triggerAction: 'all',
+    editable: false,
+
+    initComponent: function () {
+        this.store = [[true, i18n._('Yes')], [false, i18n._('No')]];
+        this.supr().initComponent.call(this);
+    }
+});
+Ext.reg('booleancombo', Ext.ux.form.BooleanCombo);
index f84ca2e..0d57682 100644 (file)
@@ -365,6 +365,123 @@ Tine.widgets.dialog.EditDialog = Ext.extend(Ext.FormPanel, {
     },
 
     /**
+     * generic form layout
+     */
+    getFormItems: function() {
+        return {
+            xtype: 'tabpanel',
+            border: false,
+            plain:true,
+            activeTab: 0,
+            border: false,
+            defaults: {
+                hideMode: 'offsets'
+            },
+            plugins: [{
+                ptype : 'ux.tabpanelkeyplugin'
+            }],
+            items:[
+                {
+                    title: this.i18nRecordName,
+                    autoScroll: true,
+                    border: false,
+                    frame: true,
+                    layout: 'border',
+                    items: [{
+                        region: 'center',
+                        xtype: 'columnform',
+                        labelAlign: 'top',
+                        formDefaults: {
+                            xtype:'textfield',
+                            anchor: '100%',
+                            labelSeparator: '',
+                            columnWidth: 1/2
+                        },
+                        items: this.getRecordFormItems()
+                    }].concat(this.getEastPanel())
+                }, new Tine.widgets.activities.ActivitiesTabPanel({
+                    app: this.appName,
+                    record_id: this.record.id,
+                    record_model: this.modelName
+                })
+            ]
+        };
+    },
+
+    getEastPanel: function() {
+        var items = [];
+        if (this.recordClass.hasField('description')) {
+            items.push(new Ext.Panel({
+                title: i18n._('Description'),
+                iconCls: 'descriptionIcon',
+                layout: 'form',
+                labelAlign: 'top',
+                border: false,
+                items: [{
+                    style: 'margin-top: -4px; border 0px;',
+                    labelSeparator: '',
+                    xtype: 'textarea',
+                    name: 'description',
+                    hideLabel: true,
+                    grow: false,
+                    preventScrollbars: false,
+                    anchor: '100% 100%',
+                    emptyText: i18n._('Enter description'),
+                    requiredGrant: 'editGrant'
+                }]
+            }));
+        }
+
+        if (this.recordClass.hasField('tags')) {
+            items.push(new Tine.widgets.tags.TagPanel({
+                app: this.appName,
+                border: false,
+                bodyStyle: 'border:1px solid #B5B8C8;'
+            }));
+        }
+
+        return items.length ? {
+            layout: 'accordion',
+            animate: true,
+            region: 'east',
+            width: 210,
+            split: true,
+            collapsible: true,
+            collapseMode: 'mini',
+            header: false,
+            margins: '0 5 0 5',
+            border: true,
+            items: items
+        } : [];
+    },
+
+    getRecordFormItems: function() {
+        // @TODO move to Tine.widgets.form.RecordForm to cope with group and sort
+        var items = [],
+            fieldNames = this.recordClass.getFieldNames(),
+            modelConfig = this.recordClass.getModelConfiguration(),
+            fieldsToExclude = ['description', 'tags', 'notes', 'attachments', 'relations', 'customfields'];
+
+        Ext.each(Tine.Tinebase.Model.genericFields, function(field) {fieldsToExclude.push(field.name)});
+        fieldsToExclude.push(this.recordClass.getMeta('idProperty'));
+
+        Ext.each(fieldNames, function(fieldName) {
+            var fieldDefinition = modelConfig.fields[fieldName];
+            // exclude: genericFields, idProperty, wellKnown(description, tags, customfields, relations, attachments, notes)
+            if (fieldsToExclude.indexOf(fieldDefinition.fieldName) < 0 && ! fieldDefinition.shy) {
+                var field = Tine.widgets.form.FieldManager.get(this.app, this.recordClass, fieldDefinition.fieldName);
+                if (field) {
+                    // apply basic layout
+                    field.columnWidth = 1;
+                    items.push([field]);
+                }
+            }
+        }, this);
+
+        return items;
+    },
+
+    /**
      * fix fields (used for preselecting form fields when called in dependency to another record)
      * @return {Boolean}
      */
diff --git a/tine20/Tinebase/js/widgets/form/FieldManager.js b/tine20/Tinebase/js/widgets/form/FieldManager.js
new file mode 100644 (file)
index 0000000..6b01ae5
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * Tine 2.0
+ *
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Cornelius Weiss <c.weiss@metaways.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+Ext.ns('Tine.widgets.grid');
+
+/**
+ * central form field manager
+ * - get form field for a given field
+ * - register form field for a given field
+ *
+ * @namespace   Tine.widgets.form
+ * @class       Tine.widgets.form.FieldManager
+ * @author      Cornelius Weiss <c.weiss@metaways.de>
+ * @singleton
+ */
+Tine.widgets.form.FieldManager = function() {
+    var fields = {};
+
+    /**
+     * modelConfigType => xtype
+     */
+    var typeMap = {
+        'date':     'datefield',
+        'time':     'timefield',
+        'datetime': 'datetimefield',
+        'string':   'textfield',
+        'text':     'textarea',
+        //'bool':     'checkbox',
+        //'boolean':  'checkbox',
+    };
+
+    return {
+        /**
+         * const for category editDialog
+         */
+        CATEGORY_EDITDIALOG: 'editDialog',
+
+        /**
+         * const for category propertyGrid
+         */
+        CATEGORY_PROPERTYGRID: 'propertyGrid',
+
+        /**
+         * get form field of well known field names
+         *
+         * @param {String} fieldName
+         * @return {Object}
+         */
+        getByFieldname: function(fieldName) {
+            var field = null;
+
+            return field;
+        },
+
+        /**
+         * get form field by data type
+         *
+         * @param {String} appName
+         * @param {Record/String} modelName
+         * @param {String} fieldName
+         * @param {String} category {editDialog|propertyGrid} optional.
+         * @return {Object}
+         */
+        getByModelConfig: function(appName, modelName, fieldName, category) {
+            var field = {},
+                recordClass = Tine.Tinebase.data.RecordMgr.get(appName, modelName),
+                modelConfig = recordClass ? recordClass.getModelConfiguration() : null,
+                fieldDefinition = modelConfig && modelConfig.fields ? modelConfig.fields[fieldName] : {},
+                fieldType = fieldDefinition.type || 'textfield',
+                app = Tine.Tinebase.appMgr.get(appName),
+                i18n = fieldDefinition.useGlobalTranslation ? i18n : app.i18n;
+
+            field.fieldLabel = i18n._hidden(fieldDefinition.label || fieldDefinition.fieldName);
+            field.name = fieldName;
+            field.disabled = !! (fieldDefinition.readOnly || fieldDefinition.disabled);
+            field.allowBlank = !! (fieldDefinition.validators && fieldDefinition.validators.allowEmpty);
+
+            if (fieldDefinition.default) {
+                field.default = i18n._hidden(fieldDefinition.default);
+            }
+
+            switch(fieldType) {
+                case 'date':
+                    field.xtype = 'datefield';
+                    if (fieldDefinition.dateFormat) {
+                        field.dateFormat = fieldDefinition.dateFormat;
+                    }
+                    break;
+                case 'time':
+                    field.xtype = 'timefield';
+                    break;
+                case 'datetime':
+                    field.xtype = 'datetimefield'; // form ux.datetimefield
+                    break;
+                case 'bool':
+                case 'boolean':
+                    field.xtype = category == 'editDialg' ? 'checkbox' : 'booleancombo';
+                    field.boxLabel = field.fieldLabel;
+                    break;
+                case 'integer':
+                    field.xtype = 'numberfield';
+                    field.allowDecimals = false;
+                    // min max ???
+                    break;
+                case 'float':
+                    field.xtype = 'numberfield';
+                    field.decimalPrecision = 2; //???
+                    // min max ???
+                    break;
+                case 'user':
+                    field.xtype = 'addressbookcontactpicker';
+                    field.userOnly = true;
+                    break;
+                case 'keyField':
+                    field.xtype = 'widget-keyfieldcombo';
+                    var keyFieldName = fieldDefinition.keyFieldConfigName;
+                    break;
+                default:
+                    field.xtype = 'textfield';
+                    break;
+            }
+
+            return field;
+        },
+
+        /**
+         * returns form field for given field
+         *
+         * @param {String/Tine.Tinebase.Application} appName
+         * @param {Record/String} modelName
+         * @param {String} fieldName
+         * @param {String} category {editDialog|propertyGrid} optional.
+         * @return {Object}
+         */
+        get: function(appName, modelName, fieldName, category) {
+            var appName = this.getAppName(appName),
+                modelName = this.getModelName(modelName),
+                categoryKey = this.getKey([appName, modelName, fieldName, category]),
+                genericKey = this.getKey([appName, modelName, fieldName]);
+
+            // check for registered renderer
+            var field = fields[categoryKey] ? fields[categoryKey] : fields[genericKey];
+
+            // check for common names
+            if (! field) {
+                field = this.getByFieldname(fieldName);
+            }
+
+            // check for known datatypes
+            if (! field) {
+                field = this.getByModelConfig(appName, modelName, fieldName, category);
+            }
+
+            return field;
+        },
+
+        /**
+         * register renderer for given field
+         *
+         * @param {String/Tine.Tinebase.Application} appName
+         * @param {Record/String} modelName
+         * @param {String} fieldName
+         * @param {Object} field
+         * @param {String} category {editDialog|propertyGrid} optional.
+         */
+        register: function(appName, modelName, fieldName, field, category) {
+            var appName = this.getAppName(appName),
+                modelName = this.getModelName(modelName),
+                categoryKey = this.getKey([appName, modelName, fieldName, category]),
+                genericKey = this.getKey([appName, modelName, fieldName]);
+
+            fields[category ? categoryKey : genericKey] = field;
+        },
+
+        /**
+         * check if a field is explicitly registered
+         *
+         * @param {String/Tine.Tinebase.Application} appName
+         * @param {Record/String} modelName
+         * @param {String} fieldName
+         * @param {String} category {editDialog|propertyGrid} optional.
+         * @return {Boolean}
+         */
+        has: function(appName, modelName, fieldName, category) {
+            var appName = this.getAppName(appName),
+                modelName = this.getModelName(modelName),
+                categoryKey = this.getKey([appName, modelName, fieldName, category]),
+                genericKey = this.getKey([appName, modelName, fieldName]);
+
+            // check for registered renderer
+            return (fields[categoryKey] ? fields[categoryKey] : fields[genericKey]) ? true : false;
+        },
+
+        /**
+         * returns the modelName by modelName or record
+         *
+         * @param {Record/String} modelName
+         * @return {String}
+         */
+        getModelName: function(modelName) {
+            return Ext.isFunction(modelName) ? modelName.getMeta('modelName') : modelName;
+        },
+
+        /**
+         * returns the modelName by appName or application instance
+         *
+         * @param {String/Tine.Tinebase.Application} appName
+         * @return {String}
+         */
+        getAppName: function(appName) {
+            return Ext.isString(appName) ? appName : appName.appName;
+        },
+
+        /**
+         * returns a key by joining the array values
+         *
+         * @param {Array} params
+         * @return {String}
+         */
+        getKey: function(params) {
+            return params.join('_');
+        }
+    };
+}();
\ No newline at end of file