Merge branch 'pu/2013.03/modelconfig-hr'
[tine20] / tine20 / Tinebase / js / ApplicationStarter.js
1 /*
2  * Tine 2.0
3  * 
4  * @package     Tinebase
5  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
6  * @author      Alexander Stintzing <a.stintzing@metaways.de>
7  * @copyright   Copyright (c) 2012-2013 Metaways Infosystems GmbH (http://www.metaways.de)
8  *
9  */
10 Ext.namespace('Tine.Tinebase');
11
12 /**
13  * Tinebase Application Starter
14  * 
15  * @namespace   Tine.Tinebase
16  * @function    Tine.MailAccounting.MailAggregateGridPanel
17  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
18  * @author      Alexander Stintzing <a.stintzing@metaways.de>
19  */
20 Tine.Tinebase.ApplicationStarter = {
21     
22     /**
23      * the applictions the user has access to
24      * @type 
25      */
26     userApplications: null,
27     
28     /**
29      * type mapping
30      * @type {Object}
31      */
32     types: {
33         'date':     'date',
34         'datetime': 'date',
35         'time':     'date',
36         'string':   'string',
37         'text':     'string',
38         'boolean':  'bool',
39         'integer':  'int',
40         'float':    'float'
41     },
42     
43     /**
44      * initializes the starter
45      */
46     init: function() {
47         // Wait until appmgr is initialized
48         if (! Tine.Tinebase.hasOwnProperty('appMgr')) {
49             this.init.defer(100, this);
50             return;
51         }
52         
53         if (! this.userApplications || this.userApplications.length == 0) {
54             this.userApplications = Tine.Tinebase.registry.get('userApplications');
55             this.createStructure(true);
56         }
57     },
58     
59     /**
60      * returns the field
61      * 
62      * @param {Object} fieldDefinition
63      * @return {Object}
64      */
65     getField: function(fieldDefinition, key) {
66         // default type is auto
67         var field = {name: key};
68         
69         if (fieldDefinition.type) {
70             // add pre defined type
71             field.type = this.types[fieldDefinition.type];
72             switch (fieldDefinition.type) {
73                 case 'datetime':
74                     field.dateFormat = Date.patterns.ISO8601Long;
75                     break;
76                 case 'date':
77                     field.dateFormat = Date.patterns.ISO8601Long;
78                     break;
79                 case 'time':
80                     field.dateFormat = Date.patterns.ISO8601Time;
81                     break;
82                 case 'record':
83                 case 'records':
84                     fieldDefinition.config.modelName = fieldDefinition.config.modelName.replace(/_/, '');
85                     field.type = fieldDefinition.config.appName + '.' + fieldDefinition.config.modelName;
86                     break;
87                 
88             }
89             // allow overwriting date pattern in model
90             if (fieldDefinition.hasOwnProperty('dateFormat')) {
91                 field.dateFormat = fieldDefinition.dateFormat;
92             }
93         }
94         
95         // TODO: create field registry, add fields here
96         return field;
97     },
98     /**
99      * returns the grid renderer
100      * @param {Object} config
101      * @param {String} field
102      * @return {Function}
103      */
104     getGridRenderer: function(config, field, appName, modelName) {
105         var gridRenderer = null;
106         if (config && field) {
107             switch (config.type) {
108                 case 'record':
109                     gridRenderer = function(value, row, record) {
110                         var foreignRecordClass = Tine[config.config.appName].Model[config.config.modelName];
111                         var titleProperty = foreignRecordClass.getMeta('titleProperty');
112                         return record.get(field) ? Ext.util.Format.htmlEncode(record.get(field)[titleProperty]) : '';
113                     };
114                     break;
115                 case 'integer':
116                     if (config.hasOwnProperty('specialType')) {
117                         switch (config.specialType) {
118                             case 'bytes1000':
119                                 gridRenderer = function(a,b,c) {
120                                     return Tine.Tinebase.common.byteRenderer(a, b, c, 2, true);
121                                 };
122                                 break;
123                             case 'bytes':
124                                 gridRenderer = function(a,b,c) {
125                                     return Tine.Tinebase.common.byteRenderer(a, b, c, 2, false);
126                                 };
127                                 break;
128                             case 'minutes':
129                                 gridRenderer = Tine.Tinebase.common.minutesRenderer;
130                                 break;
131                             case 'seconds':
132                                 gridRenderer = Tine.Tinebase.common.secondsRenderer;
133                                 break;
134                             case 'usMoney':
135                                 gridRenderer = Ext.util.Format.usMoney;
136                                 break;
137                             case 'euMoney':
138                                 gridRenderer = Ext.util.Format.euMoney;
139                                 break;
140                             default:
141                                 gridRenderer = Ext.util.Format.htmlEncode;
142                         }
143                     }
144                     break;
145                 case 'user':
146                     gridRenderer = Tine.Tinebase.common.usernameRenderer;
147                     break;
148                 case 'keyfield': 
149                     gridRenderer = Tine.Tinebase.widgets.keyfield.Renderer.get(appName, config.name);
150                     break;
151                 case 'date':
152                     gridRenderer = Tine.Tinebase.common.dateRenderer;
153                     break;
154                 case 'datetime':
155                     gridRenderer = Tine.Tinebase.common.dateTimeRenderer;
156                     break;
157                 case 'time':
158                     gridRenderer = Tine.Tinebase.common.timeRenderer;
159                     break;
160                 case 'tag':
161                     gridRenderer = Tine.Tinebase.common.tagsRenderer;
162                     break;
163                 case 'container':
164                     gridRenderer = Tine.Tinebase.common.containerRenderer;
165                     break;
166                 case 'boolean':
167                     gridRenderer = Tine.Tinebase.common.booleanRenderer;
168                     break;
169                 case 'relation':
170                     var cc = config.config;
171                     gridRenderer = new Tine.widgets.relation.GridRenderer({
172                         appName: appName,
173                         type: cc.type,
174                         foreignApp: cc.appName,
175                         foreignModel: cc.modelName
176                         });
177                     break;
178                 default:
179                     gridRenderer = Ext.util.Format.htmlEncode;
180             }
181         }
182         return gridRenderer;
183     },
184
185     /**
186      * used in getFilter for mapping types to filter
187      * 
188      * @type 
189      */
190     filterMap: function(type, fieldconfig, filter, filterconfig, appName, modelName, modelConfig) {
191         switch (type) {
192             case 'string':
193             case 'text':
194             case 'user':
195                 break;
196             case 'boolean': 
197                 filter.valueType = 'bool'
198                 filter.defaultValue = false;
199                 break;
200             case 'record':
201                 filterconfig.options.modelName = filterconfig.options.modelName.replace(/_/, '');
202                 var foreignApp = filterconfig.options.appName;
203                 var foreignModel = filterconfig.options.modelName;
204                 
205                 // create generic foreign id filter
206                 var filterclass = Ext.extend(Tine.widgets.grid.ForeignRecordFilter, {
207                     foreignRecordClass: foreignApp + '.' + foreignModel,
208                     linkType: 'foreignId',
209                     ownField: fieldconfig.key,
210                     label: filter.label
211                 });
212                 // register foreign id field as appName.modelName.fieldKey
213                 var fc = appName + '.' + modelName + '.' + fieldconfig.key;
214                 Tine.widgets.grid.FilterToolbar.FILTERS[fc] = filterclass;
215                 filter = {filtertype: fc};
216                 break;
217             case 'tag': 
218                 filter = {filtertype: 'tinebase.tag', app: appName};
219                 break;
220             case 'container':
221                 var applicationName = filterconfig.appName ? filterconfig.appName : appName;
222                 var modelName = filterconfig.modelName ? filterconfig.modelName : modelName;
223                 filter = {
224                     filtertype: 'tine.widget.container.filtermodel', 
225                     app: applicationName, 
226                     recordClass: applicationName + '.' + modelName,
227                     field: fieldconfig.key,
228                     label: fieldconfig.label,
229                     callingApp: appName
230                 };
231                 break;
232             case 'keyfield':
233                 filter.filtertype = 'tine.widget.keyfield.filter';
234                 filter.app = {name: appName};
235                 filter.keyfieldName = fieldconfig.name;
236                 break;
237             case 'date':
238                 filter.valueType = 'date';
239                 break;
240             case 'datetime':
241                 filter.valueType = 'date';
242                 break;
243             case 'integer':
244                 filter.valueType = 'number';
245         }
246         return filter;
247     },
248     
249     /**
250      * returns filter
251      * 
252      * @param {String} fieldKey
253      * @param {Object} filterconfig
254      * @param {Object} fieldconfig
255      * @return {Object}
256      */
257     getFilter: function(fieldKey, filterconfig, modelConfig) {
258         // take field label if no filterlabel is defined
259         var fieldconfig = modelConfig.fields[fieldKey];
260         var appName = modelConfig.appName;
261         var modelName = modelConfig.modelName;
262         
263         var app = Tine.Tinebase.appMgr.get(appName),
264             fieldTypeKey = (fieldconfig && fieldconfig.type) ? fieldconfig.type : (filterconfig && filterconfig.type) ? filterconfig.type : 'default',
265             label = (filterconfig && filterconfig.hasOwnProperty('label')) ? filterconfig.label : (fieldconfig && fieldconfig.hasOwnProperty('label')) ? fieldconfig.label : null,
266             globalI18n = ((filterconfig && filterconfig.hasOwnProperty('useGlobalTranslation')) || (fieldconfig && fieldconfig.hasOwnProperty('useGlobalTranslation')));
267         
268         if (! label) {
269             return null;
270         }
271         // prepare filter
272         var filter = {
273             label: globalI18n ? _(label) : app.i18n._(label),
274             field: fieldKey
275         };
276         
277         if (filterconfig) {
278             if (filterconfig.hasOwnProperty('options') && (filterconfig.options.hasOwnProperty('jsFilterType') || filterconfig.options.hasOwnProperty('jsFilterValueType'))) {
279                 Tine.log.err('jsFilterType and jsFilterValueType are deprecated. Use jsConfig.<property> instead.');
280             }
281             // if js filter is defined in filterconfig.options, take this and return
282             if (filterconfig.hasOwnProperty('jsConfig')) {
283                 Ext.apply(filter, filterconfig.jsConfig);
284                 return filter;
285             } 
286             
287             try {
288                 filter = this.filterMap(fieldTypeKey, fieldconfig, filter, filterconfig, appName, modelName, modelConfig);
289             } catch (e) {
290                 var keys = filterconfig.filter.split('_'),
291                     filterkey = keys[0].toLowerCase() + '.' + keys[2].toLowerCase();
292                     filterkey = filterkey.replace(/filter/g, '');
293     
294                 if (Tine.widgets.grid.FilterToolbar.FILTERS[filterkey]) {
295                     filter = {filtertype: filterkey};
296                 } else { // set to null if no filter could be found
297                     filter = null;
298                 }
299             }
300         }
301         return filter;
302     },
303     
304     /**
305      * if application starter should be used, here the js contents are (pre-)created
306      */
307     createStructure: function(initial) {
308         var start = new Date();
309         Ext.each(this.userApplications, function(app) {
310             var appName = app.name;
311             Ext.namespace('Tine.' + appName);
312             
313             var models = Tine[appName].registry ? Tine[appName].registry.get('models') : null;
314             
315             if (models) {
316                 
317                 Tine[appName].isAuto = true;
318                 var contentTypes = [];
319                 
320                 // create translation
321                 Tine[appName].i18n = new Locale.Gettext();
322                 Tine[appName].i18n.textdomain(appName);
323                 
324                 // iterate models of this app
325                 Ext.iterate(models, function(modelName, modelConfig) {
326                     var containerProperty = modelConfig.hasOwnProperty('containerProperty') ? modelConfig.containerProperty : null;
327                     
328                     modelName = modelName.replace(/_/, '');
329                     
330                     Ext.namespace('Tine.' + appName, 'Tine.' + appName + '.Model');
331                     
332                     var modelArrayName = modelName + 'Array',
333                         modelArray = [];
334
335                     if (modelConfig.createModule) {
336                         contentTypes.push(modelConfig);
337                     }
338                     
339                     // iterate record fields
340                     Ext.each(modelConfig.fieldKeys, function(key) {
341                         // add field to model array
342                         modelArray.push(this.getField(modelConfig.fields[key], key));
343                         
344                         if (modelConfig.fields[key].label) {
345                             // register grid renderer
346                             if (initial) {
347                                 var renderer = this.getGridRenderer(modelConfig.fields[key], key, appName, modelName);
348                                 
349                                 if (Ext.isFunction(renderer)) {
350                                     if (! Tine.widgets.grid.RendererManager.has(appName, modelName, key)) {
351                                         Tine.widgets.grid.RendererManager.register(appName, modelName, key, renderer);
352                                     }
353                                 } else if (Ext.isObject(renderer)) {
354                                     if (! Tine.widgets.grid.RendererManager.has(appName, modelName, key)) {
355                                         Tine.widgets.grid.RendererManager.register(appName, modelName, key, renderer.render, null, renderer);
356                                     }
357                                 }
358                             }
359                         }
360                         
361                     }, this);
362                     
363                     // iterate virtual record fields
364                     if (modelConfig.virtualFields && modelConfig.virtualFields.length) {
365                         Ext.each(modelConfig.virtualFields, function(field) {
366                             modelArray.push(this.getField(field, field.key));
367                         }, this);
368                     }
369                     
370                     // collect the filterModel
371                     var filterModel = [];
372                     Ext.iterate(modelConfig.filterModel, function(key, filter) {
373                         var f = this.getFilter(key, filter, modelConfig);
374                         
375                         if (f) {
376                             Tine.widgets.grid.FilterRegistry.register(appName, modelName, f);
377                             filterModel.push(f);
378                         }
379                     }, this);
380                     
381                     // TODO: registry looses info if gridpanel resides in an editDialog
382                     // delete filterModel as all filters are in the filter registry now
383                     // delete modelConfig.filterModel;
384                     
385                     Tine[appName].Model[modelArrayName] = modelArray;
386                     
387                     // create model
388                     if (! Tine[appName].Model.hasOwnProperty(modelName)) {
389                         Tine[appName].Model[modelName] = Tine.Tinebase.data.Record.create(Tine[appName].Model[modelArrayName], 
390                             Ext.copyTo({}, modelConfig, 
391                                'defaultFilter,appName,modelName,recordName,recordsName,titleProperty,containerProperty,containerName,containersName,group')
392                         );
393                         Tine[appName].Model[modelName].getFilterModel = function() {
394                             return filterModel;
395                         }
396                     }
397                     
398                     Ext.namespace('Tine.' + appName);
399                     
400                     // create recordProxy
401                     var recordProxyName = modelName.toLowerCase() + 'Backend';
402                     if (! Tine[appName].hasOwnProperty(recordProxyName)) {
403                         Tine[appName][recordProxyName] = new Tine.Tinebase.data.RecordProxy({
404                             appName: appName,
405                             modelName: modelName,
406                             recordClass: Tine[appName].Model[modelName]
407                         });
408                     }
409                     
410                     // overwrite function
411                     Tine[appName].Model[modelName].getDefaultData = function() {
412                         if (! dd) {
413                             var dd = Ext.decode(Ext.encode(modelConfig.defaultData));
414                         }
415                         
416                         // find container by selection or use defaultContainer by registry
417                         if (modelConfig.containerProperty) {
418                             if (! dd.hasOwnProperty(modelConfig.containerProperty)) {
419                                 var app = Tine.Tinebase.appMgr.get(appName),
420                                     registry = app.getRegistry(),
421                                     ctp = app.getMainScreen().getWestPanel().getContainerTreePanel();
422                                     
423                                 var container = (ctp ? ctp.getDefaultContainer() : null) || (registry ? registry.get("default" + modelName + "Container") : null);
424                                 
425                                 if (container) {
426                                     dd[modelConfig.containerProperty] = container;
427                                 }
428                             }
429                         }
430                         return dd;
431                     };
432                     
433                     // create filter panel
434                     var filterPanelName = modelName + 'FilterPanel';
435                     if (! Tine[appName].hasOwnProperty(filterPanelName)) {
436                         Tine[appName][filterPanelName] = function(c) {
437                             Ext.apply(this, c);
438                             Tine[appName][filterPanelName].superclass.constructor.call(this);
439                         };
440                         Ext.extend(Tine[appName][filterPanelName], Tine.widgets.persistentfilter.PickerPanel);
441                     }
442                     // create container tree panel, if needed
443                     if (containerProperty) {
444                         var containerTreePanelName = modelName + 'TreePanel';
445                         if (! Tine[appName].hasOwnProperty(containerTreePanelName)) {
446                             Tine[appName][containerTreePanelName] = Ext.extend(Tine.widgets.container.TreePanel, {
447                                 filterMode: 'filterToolbar',
448                                 recordClass: Tine[appName].Model[modelName]
449                             });
450                         }
451                     }
452                     
453                     // create main screen
454                     if(! Tine[appName].hasOwnProperty('MainScreen')) {
455                         Tine[appName].MainScreen = Ext.extend(Tine.widgets.MainScreen, {
456                             app: appName,
457                             contentTypes: contentTypes,
458                             activeContentType: modelName
459                         });
460                     }
461                     
462                     // create editDialog openWindow function only if edit dialog exists
463                     var editDialogName = modelName + 'EditDialog';
464                     
465                     if (Tine[appName].hasOwnProperty(editDialogName)) {
466                         var edp = Tine[appName][editDialogName].prototype;
467                         if (containerProperty) {
468                             edp.showContainerSelector = true;
469                         }
470                         Ext.apply(edp, {
471                             modelConfig:      Ext.encode(modelConfig),
472                             modelName:        modelName,
473                             recordClass:      Tine[appName].Model[modelName],
474                             recordProxy:      Tine[appName][recordProxyName],
475                             appName:          appName,
476                             windowNamePrefix: modelName + 'EditWindow_'
477                         });
478                         if (! Ext.isFunction(Tine[appName][editDialogName].openWindow)) {
479                             Tine[appName][editDialogName].openWindow  = function (cfg) {
480                                 var id = (cfg.record && cfg.record.id) ? cfg.record.id : 0;
481                                 var window = Tine.WindowFactory.getWindow({
482                                     width: edp.windowWidth ? edp.windowWidth : 600,
483                                     height: edp.windowHeight ? edp.windowHeight : 230,
484                                     name: edp.windowNamePrefix + id,
485                                     contentPanelConstructor: 'Tine.' + appName + '.' + editDialogName,
486                                     contentPanelConstructorConfig: cfg
487                                 });
488                                 return window;
489                             };
490                         }
491                     }
492                     // create Gridpanel
493                     var gridPanelName = modelName + 'GridPanel', 
494                         gpConfig = {
495                             modelConfig: modelConfig,
496                             app: Tine[appName], 
497                             recordProxy: Tine[appName][recordProxyName],
498                             recordClass: Tine[appName].Model[modelName]
499                         };
500                         
501                     if (! Tine[appName].hasOwnProperty(gridPanelName)) {
502                         Tine[appName][gridPanelName] = Ext.extend(Tine.widgets.grid.GridPanel, gpConfig);
503                     } else {
504                         Ext.apply(Tine[appName][gridPanelName].prototype, gpConfig);
505                     }
506                 }, this);
507             }
508         }, this);
509         
510         var stop = new Date();
511     }
512 }