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