48ae47b7bb00f98b2581c68b1472fa9c94a870bb
[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         Tine.log.info('ApplicationStarter::init');
54         
55         if (! this.userApplications || this.userApplications.length == 0) {
56             this.userApplications = Tine.Tinebase.registry.get('userApplications');
57             this.createStructure(true);
58         }
59     },
60     
61     /**
62      * returns the field
63      * 
64      * @param {Object} fieldDefinition
65      * @return {Object}
66      */
67     getField: function(fieldDefinition, key) {
68         // default type is auto
69         var field = {name: key};
70         
71         if (fieldDefinition.type) {
72             // add pre defined type
73             field.type = this.types[fieldDefinition.type];
74             switch (fieldDefinition.type) {
75                 case 'datetime':
76                     field.dateFormat = Date.patterns.ISO8601Long;
77                     break;
78                 case 'date':
79                     field.dateFormat = Date.patterns.ISO8601Long;
80                     break;
81                 case 'time':
82                     field.dateFormat = Date.patterns.ISO8601Time;
83                     break;
84                 case 'record':
85                 case 'records':
86                     fieldDefinition.config.modelName = fieldDefinition.config.modelName.replace(/_/, '');
87                     field.type = fieldDefinition.config.appName + '.' + fieldDefinition.config.modelName;
88                     break;
89             }
90             // allow overwriting date pattern in model
91             if (fieldDefinition.hasOwnProperty('dateFormat')) {
92                 field.dateFormat = fieldDefinition.dateFormat;
93             }
94             
95             if (fieldDefinition.hasOwnProperty('label')) {
96                 field.label = fieldDefinition.label;
97             }
98         }
99         
100         // TODO: create field registry, add fields here
101         return field;
102     },
103     /**
104      * returns the grid renderer
105      * @param {Object} config
106      * @param {String} field
107      * @return {Function}
108      */
109     getGridRenderer: function(config, field, appName, modelName) {
110         var gridRenderer = null;
111         if (config && field) {
112             switch (config.type) {
113                 case 'record':
114                     if (Tine.Tinebase.common.hasRight('view', config.config.appName, config.config.modelName.toLowerCase())) {
115                         if (config.config.appName == appName && config.config.modelName == modelName) {
116                             // pointing to same model
117                             gridRenderer = function (value, row, record) {
118                                 var title = value && config.config.titleProperty ? value[config.config.titleProperty] : '';
119                                 return Ext.util.Format.htmlEncode(title);
120                             };
121                         } else {
122                             var foreignRecordClass = Tine[config.config.appName].Model[config.config.modelName];
123                             if (foreignRecordClass) {
124                                 gridRenderer = function (value, row, record) {
125                                     var titleProperty = foreignRecordClass.getMeta('titleProperty');
126                                     return record && record.get(field) ? Ext.util.Format.htmlEncode(record.get(field)[titleProperty]) : '';
127                                 };
128                             } else {
129                                 gridRenderer = null;
130                             }
131                         }
132                     } else {
133                         gridRenderer = null;
134                     }
135                     break;
136                 case 'integer':
137                 case 'float':
138                     if (config.hasOwnProperty('specialType')) {
139                         switch (config.specialType) {
140                             case 'bytes1000':
141                                 gridRenderer = function(value, cell, record) {
142                                     return Tine.Tinebase.common.byteRenderer(value, cell, record, 2, true);
143                                 };
144                                 break;
145                             case 'bytes':
146                                 gridRenderer = function(value, cell, record) {
147                                     return Tine.Tinebase.common.byteRenderer(value, cell, record, 2, false);
148                                 };
149                                 break;
150                             case 'minutes':
151                                 gridRenderer = Tine.Tinebase.common.minutesRenderer;
152                                 break;
153                             case 'seconds':
154                                 gridRenderer = Tine.Tinebase.common.secondsRenderer;
155                                 break;
156                             case 'percent':
157                                 gridRenderer = function(value, cell, record) {
158                                     return Tine.Tinebase.common.percentRenderer(value, config.type);
159                                 };
160                                 break;
161                             default:
162                                 gridRenderer = Ext.util.Format.htmlEncode;
163                         }
164                     }
165                     break;
166                 case 'user':
167                     gridRenderer = Tine.Tinebase.common.usernameRenderer;
168                     break;
169                 case 'keyfield': 
170                     gridRenderer = Tine.Tinebase.widgets.keyfield.Renderer.get(appName, config.name);
171                     break;
172                 case 'date':
173                     gridRenderer = Tine.Tinebase.common.dateRenderer;
174                     break;
175                 case 'datetime':
176                     gridRenderer = Tine.Tinebase.common.dateTimeRenderer;
177                     break;
178                 case 'time':
179                     gridRenderer = Tine.Tinebase.common.timeRenderer;
180                     break;
181                 case 'tag':
182                     gridRenderer = Tine.Tinebase.common.tagsRenderer;
183                     break;
184                 case 'container':
185                     gridRenderer = Tine.Tinebase.common.containerRenderer;
186                     break;
187                 case 'boolean':
188                     gridRenderer = Tine.Tinebase.common.booleanRenderer;
189                     break;
190                 case 'money':
191                     gridRenderer = Ext.util.Format.money;
192                     break;
193                 case 'relation':
194                     var cc = config.config;
195                     gridRenderer = new Tine.widgets.relation.GridRenderer({
196                         appName: appName,
197                         type: cc.type,
198                         foreignApp: cc.appName,
199                         foreignModel: cc.modelName
200                         });
201                     break;
202                 default:
203                     gridRenderer = Ext.util.Format.htmlEncode;
204             }
205         }
206         return gridRenderer;
207     },
208
209     /**
210      * used in getFilter for mapping types to filter
211      * 
212      * @type 
213      */
214     filterMap: function(type, fieldconfig, filter, filterconfig, appName, modelName, modelConfig) {
215         switch (type) {
216             case 'string':
217             case 'text':
218                 break;
219             case 'user':
220                 filter.valueType = 'user';
221                 break;
222             case 'boolean': 
223                 filter.valueType = 'bool';
224                 filter.defaultValue = false;
225                 break;
226             case 'record':
227                 filterconfig.options.modelName = filterconfig.options.modelName.replace(/_/, '');
228                 var foreignApp = filterconfig.options.appName;
229                 var foreignModel = filterconfig.options.modelName;
230                 
231                 // create generic foreign id filter
232                 var filterclass = Ext.extend(Tine.widgets.grid.ForeignRecordFilter, {
233                     foreignRecordClass: foreignApp + '.' + foreignModel,
234                     linkType: 'foreignId',
235                     ownField: fieldconfig.key,
236                     label: filter.label
237                 });
238                 // register foreign id field as appName.modelName.fieldKey
239                 var fc = appName + '.' + modelName + '.' + fieldconfig.key;
240                 Tine.widgets.grid.FilterToolbar.FILTERS[fc] = filterclass;
241                 filter = {filtertype: fc};
242                 break;
243             case 'tag': 
244                 filter = {filtertype: 'tinebase.tag', app: appName};
245                 break;
246             case 'container':
247                 var applicationName = filterconfig.appName ? filterconfig.appName : appName;
248                 var modelName = filterconfig.modelName ? filterconfig.modelName : modelName;
249                 filter = {
250                     filtertype: 'tine.widget.container.filtermodel', 
251                     app: applicationName, 
252                     recordClass: applicationName + '.' + modelName,
253                     field: fieldconfig.key,
254                     label: fieldconfig.label,
255                     callingApp: appName
256                 };
257                 break;
258             case 'keyfield':
259                 filter.filtertype = 'tine.widget.keyfield.filter';
260                 filter.app = {name: appName};
261                 filter.keyfieldName = fieldconfig.name;
262                 break;
263             case 'date':
264                 filter.valueType = 'date';
265                 break;
266             case 'datetime':
267                 filter.valueType = 'date';
268                 break;
269             case 'float':
270             case 'integer':
271                 filter.valueType = 'number';
272         }
273         return filter;
274     },
275     
276     /**
277      * returns filter
278      * 
279      * @param {String} fieldKey
280      * @param {Object} filterconfig
281      * @param {Object} fieldconfig
282      * @return {Object}
283      */
284     getFilter: function(fieldKey, filterconfig, modelConfig) {
285         // take field label if no filterlabel is defined
286         // TODO Refactor: tag and tags see ticket 0008944
287         // TODO Remove this ugly hack!
288         if (fieldKey == 'tag') {
289             fieldKey = 'tags';
290         }
291         var fieldconfig = modelConfig.fields[fieldKey];
292         var appName = modelConfig.appName;
293         var modelName = modelConfig.modelName;
294         
295         var app = Tine.Tinebase.appMgr.get(appName);
296         if (! app) {
297             Tine.log.error('Application ' + appName + ' not found!');
298             return null;
299         }
300         
301         // check right on foreign app
302         if (fieldconfig && (fieldconfig.type == 'record' || fieldconfig.type == 'records')) {
303             var opt = fieldconfig.config;
304             
305             if (opt && (! opt.doNotCheckModuleRight) && (! Tine.Tinebase.common.hasRight('view', opt.appName, opt.modelName.toLowerCase()))) {
306                 return null;
307             }
308         }
309         
310         var fieldTypeKey = (fieldconfig && fieldconfig.type) ? fieldconfig.type : (filterconfig && filterconfig.type) ? filterconfig.type : 'default',
311             label = (filterconfig && filterconfig.hasOwnProperty('label')) ? filterconfig.label : (fieldconfig && fieldconfig.hasOwnProperty('label')) ? fieldconfig.label : null,
312             globalI18n = ((filterconfig && filterconfig.hasOwnProperty('useGlobalTranslation')) || (fieldconfig && fieldconfig.hasOwnProperty('useGlobalTranslation')));
313         
314         if (! label) {
315             return null;
316         }
317         // prepare filter
318         var filter = {
319             label: globalI18n ? i18n._(label) : app.i18n._(label),
320             field: fieldKey
321         };
322         
323         if (filterconfig) {
324             if (filterconfig.hasOwnProperty('options') && (filterconfig.options.hasOwnProperty('jsFilterType') || filterconfig.options.hasOwnProperty('jsFilterValueType'))) {
325                 Tine.log.error('jsFilterType and jsFilterValueType are deprecated. Use jsConfig.<property> instead.');
326             }
327             // if js filter is defined in filterconfig.options, take this and return
328             if (filterconfig.hasOwnProperty('jsConfig')) {
329                 Ext.apply(filter, filterconfig.jsConfig);
330                 return filter;
331             } 
332             
333             try {
334                 filter = this.filterMap(fieldTypeKey, fieldconfig, filter, filterconfig, appName, modelName, modelConfig);
335             } catch (e) {
336                 var keys = filterconfig.filter.split('_'),
337                     filterkey = keys[0].toLowerCase() + '.' + keys[2].toLowerCase();
338                     filterkey = filterkey.replace(/filter/g, '');
339     
340                 if (Tine.widgets.grid.FilterToolbar.FILTERS[filterkey]) {
341                     filter = {filtertype: filterkey};
342                 } else { // set to null if no filter could be found
343                     filter = null;
344                 }
345             }
346         }
347
348         return filter;
349     },
350     
351     /**
352      * if application starter should be used, here the js contents are (pre-)created
353      */
354     createStructure: function(initial) {
355         var start = new Date();
356         Ext.each(this.userApplications, function(app) {
357             
358             var appName = app.name;
359             Tine.log.info('ApplicationStarter::createStructure for app ' + appName);
360             Ext.namespace('Tine.' + appName);
361             
362             var models = Tine[appName].registry ? Tine[appName].registry.get('models') : null;
363             
364             if (models) {
365                 
366                 Tine[appName].isAuto = true;
367                 var contentTypes = [];
368                 
369                 // create translation
370                 Tine[appName].i18n = new Locale.Gettext();
371                 Tine[appName].i18n.textdomain(appName);
372                 
373                 // iterate models of this app
374                 Ext.iterate(models, function(modelName, modelConfig) {
375                     // create main screen
376                     if(! Tine[appName].hasOwnProperty('MainScreen')) {
377                         Tine[appName].MainScreen = Ext.extend(Tine.widgets.MainScreen, {
378                             app: appName,
379                             contentTypes: contentTypes,
380                             activeContentType: modelName
381                         });
382                     }
383
384                     var containerProperty = modelConfig.hasOwnProperty('containerProperty') ? modelConfig.containerProperty : null;
385                     
386                     modelName = modelName.replace(/_/, '');
387                     
388                     Ext.namespace('Tine.' + appName, 'Tine.' + appName + '.Model');
389                     
390                     var modelArrayName = modelName + 'Array',
391                         modelArray = [];
392                     
393                     Tine.log.info('ApplicationStarter::createStructure for model ' + modelName);
394                     
395                     if (modelConfig.createModule) {
396                         contentTypes.push(modelConfig);
397                     }
398                     
399                     // iterate record fields
400                     Ext.each(modelConfig.fieldKeys, function(key) {
401                         // add field to model array
402                         modelArray.push(this.getField(modelConfig.fields[key], key));
403                         
404                         if (modelConfig.fields[key].label) {
405                             // register grid renderer
406                             if (initial) {
407                                 try {
408                                     var renderer = this.getGridRenderer(modelConfig.fields[key], key, appName, modelName);
409                                 } catch (e) {
410                                     Tine.log.err(e);
411                                     var renderer = null;
412                                 }
413                                 
414                                 if (Ext.isFunction(renderer)) {
415                                     if (! Tine.widgets.grid.RendererManager.has(appName, modelName, key)) {
416                                         Tine.widgets.grid.RendererManager.register(appName, modelName, key, renderer);
417                                     }
418                                 } else if (Ext.isObject(renderer)) {
419                                     if (! Tine.widgets.grid.RendererManager.has(appName, modelName, key)) {
420                                         Tine.widgets.grid.RendererManager.register(appName, modelName, key, renderer.render, null, renderer);
421                                     }
422                                 }
423                             }
424                         }
425                         
426                     }, this);
427                     
428                     // iterate virtual record fields
429                     if (modelConfig.virtualFields && modelConfig.virtualFields.length) {
430                         Ext.each(modelConfig.virtualFields, function(field) {
431                             modelArray.push(this.getField(field, field.key));
432                         }, this);
433                     }
434                     
435                     // collect the filterModel
436                     var filterModel = [];
437                     Ext.iterate(modelConfig.filterModel, function(key, filter) {
438                         var f = this.getFilter(key, filter, modelConfig);
439                         
440                         if (f) {
441                             Tine.widgets.grid.FilterRegistry.register(appName, modelName, f);
442                         }
443                     }, this);
444                     
445                     // TODO: registry loses info if gridpanel resides in an editDialog
446                     // delete filterModel as all filters are in the filter registry now
447                     // delete modelConfig.filterModel;
448                     
449                     Tine[appName].Model[modelArrayName] = modelArray;
450                     
451                     // create model
452                     if (! Tine[appName].Model.hasOwnProperty(modelName)) {
453                         Tine[appName].Model[modelName] = Tine.Tinebase.data.Record.create(Tine[appName].Model[modelArrayName], 
454                             Ext.copyTo({modelConfiguration: modelConfig}, modelConfig,
455                                'idProperty,defaultFilter,appName,modelName,recordName,recordsName,titleProperty,containerProperty,containerName,containersName,group,copyOmitFields')
456                         );
457                         Tine[appName].Model[modelName].getFilterModel = function() {
458                             return filterModel;
459                         }
460                     }
461                     
462                     Ext.namespace('Tine.' + appName);
463                     
464                     // create recordProxy
465                     var recordProxyName = modelName.toLowerCase() + 'Backend';
466                     if (! Tine[appName].hasOwnProperty(recordProxyName)) {
467                         Tine[appName][recordProxyName] = new Tine.Tinebase.data.RecordProxy({
468                             appName: appName,
469                             modelName: modelName,
470                             recordClass: Tine[appName].Model[modelName]
471                         });
472                     }
473                     // if default data is empty, it will be resolved to an array
474                     if (Ext.isArray(modelConfig.defaultData)) {
475                         modelConfig.defaultData = {};
476                     }
477                     
478                     // overwrite function
479                     Tine[appName].Model[modelName].getDefaultData = function() {
480                         if (! dd) {
481                             var dd = Ext.decode(Ext.encode(modelConfig.defaultData));
482                         }
483                         
484                         // find container by selection or use defaultContainer by registry
485                         if (modelConfig.containerProperty) {
486                             if (! dd.hasOwnProperty(modelConfig.containerProperty)) {
487                                 var app = Tine.Tinebase.appMgr.get(appName),
488                                     registry = app.getRegistry(),
489                                     ctp = app.getMainScreen().getWestPanel().getContainerTreePanel();
490                                     
491                                 var container = (ctp ? ctp.getDefaultContainer() : null) || (registry ? registry.get("default" + modelName + "Container") : null);
492                                 
493                                 if (container) {
494                                     dd[modelConfig.containerProperty] = container;
495                                 }
496                             }
497                         }
498                         return dd;
499                     };
500
501                     // create filter panel
502                     var filterPanelName = modelName + 'FilterPanel';
503                     if (! Tine[appName].hasOwnProperty(filterPanelName)) {
504                         Tine[appName][filterPanelName] = function(c) {
505                             Ext.apply(this, c);
506                             Tine[appName][filterPanelName].superclass.constructor.call(this);
507                         };
508                         Ext.extend(Tine[appName][filterPanelName], Tine.widgets.persistentfilter.PickerPanel);
509                     }
510                     // create container tree panel, if needed
511                     if (containerProperty) {
512                         var containerTreePanelName = modelName + 'TreePanel';
513                         if (! Tine[appName].hasOwnProperty(containerTreePanelName)) {
514                             Tine[appName][containerTreePanelName] = Ext.extend(Tine.widgets.container.TreePanel, {
515                                 filterMode: 'filterToolbar',
516                                 recordClass: Tine[appName].Model[modelName]
517                             });
518                         }
519                     }
520                     
521                     // create editDialog openWindow function only if edit dialog exists
522                     var editDialogName = modelName + 'EditDialog';
523                     if (! Tine[appName].hasOwnProperty(editDialogName)) {
524                         Tine[appName][editDialogName] = Ext.extend(Tine.widgets.dialog.EditDialog, {
525                             displayNotes: Tine[appName].Model[modelName].hasField('notes')
526                         });
527                     }
528
529                     
530                     if (Tine[appName].hasOwnProperty(editDialogName)) {
531                         var edp = Tine[appName][editDialogName].prototype;
532                         if (containerProperty) {
533                             edp.showContainerSelector = true;
534                         }
535                         Ext.apply(edp, {
536                             modelConfig:      Ext.encode(modelConfig),
537                             modelName:        modelName,
538                             recordClass:      Tine[appName].Model[modelName],
539                             recordProxy:      Tine[appName][recordProxyName],
540                             appName:          appName,
541                             windowNamePrefix: modelName + 'EditWindow_'
542                         });
543                         if (! Ext.isFunction(Tine[appName][editDialogName].openWindow)) {
544                             Tine[appName][editDialogName].openWindow  = function (cfg) {
545                                 var id = cfg.recordId ? cfg.recordId : ( (cfg.record && cfg.record.id) ? cfg.record.id : 0 );
546                                 var window = Tine.WindowFactory.getWindow({
547                                     width: edp.windowWidth ? edp.windowWidth : 600,
548                                     height: edp.windowHeight ? edp.windowHeight : 230,
549                                     name: edp.windowNamePrefix + id,
550                                     contentPanelConstructor: 'Tine.' + appName + '.' + editDialogName,
551                                     contentPanelConstructorConfig: cfg
552                                 });
553                                 return window;
554                             };
555                         }
556                     }
557                     // create Gridpanel
558                     var gridPanelName = modelName + 'GridPanel', 
559                         gpConfig = {
560                             modelConfig: modelConfig,
561                             app: Tine.Tinebase.appMgr.get(appName),
562                             recordProxy: Tine[appName][recordProxyName],
563                             recordClass: Tine[appName].Model[modelName]
564                         };
565                         
566                     if (! Tine[appName].hasOwnProperty(gridPanelName)) {
567                         Tine[appName][gridPanelName] = Ext.extend(Tine.widgets.grid.GridPanel, gpConfig);
568                     } else {
569                         Ext.apply(Tine[appName][gridPanelName].prototype, gpConfig);
570                     }
571
572                     if (! Tine[appName][gridPanelName].prototype.detailsPanel) {
573                         Tine[appName][gridPanelName].prototype.detailsPanel = {
574                             xtype: 'widget-detailspanel',
575                             recordClass: Tine[appName].Model[modelName]
576                         }
577                     }
578                     // add model to global add splitbutton if set
579                     if (modelConfig.hasOwnProperty('splitButton') && modelConfig.splitButton == true) {
580                         var iconCls = appName + modelName;
581                         if (! Ext.util.CSS.getRule('.' + iconCls)) {
582                             iconCls = 'ApplicationIconCls';
583                         }
584                         Ext.ux.ItemRegistry.registerItem('Tine.widgets.grid.GridPanel.addButton', {
585                             text: Tine[appName].i18n._('New ' + modelName), 
586                             iconCls: iconCls,
587                             scope: Tine.Tinebase.appMgr.get(appName),
588                             handler: (function() {
589                                 var ms = this.getMainScreen(),
590                                     cp = ms.getCenterPanel(modelName);
591                                     
592                                 cp.onEditInNewWindow.call(cp, {});
593                             }).createDelegate(Tine.Tinebase.appMgr.get(appName))
594                         });
595                     }
596                     
597                 }, this);
598             }
599         }, this);
600         
601         var stop = new Date();
602     }
603 }