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