Allow to override module name of a specific application either through the model...
[tine20] / tine20 / Tinebase / js / widgets / ContentTypeTreePanel.js
1 /*
2  * Tine 2.0
3  * 
4  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
5  * @author      Alexander Stintzing <a.stintzing@metaways.de>
6  * @copyright   Copyright (c) 2012-2013 Metaways Infosystems GmbH (http://www.metaways.de)
7  */
8 Ext.ns('Tine.widgets');
9
10 /**
11  * @namespace   Tine.widgets
12  * @class       Tine.widgets.ContentTypeTreePanel
13  * @extends     Ext.tree.TreePanel
14  * @author      Alexander Stintzing <a.stintzing@metaways.de>
15  * @param       {Object} config Configuration options
16  * @description
17  * <p>Utility class for generating content type trees as used in the apps westpanel</p>
18  *<p>Example usage:</p>
19 <pre><code>
20 var modulePanel =  new Tine.widgets.ContentTypeTreePanel({
21     app: Tine.Tinebase.appMgr.get('Timetracker'),
22     contentTypes: [{modelName: 'Timesheet', requiredRight: null}, {modelName: 'Timeaccount', requiredRight: 'manage'}],
23     contentType: 'Timeaccount'
24 });
25 </code></pre>
26  */
27 Tine.widgets.ContentTypeTreePanel = function(config) {
28     Ext.apply(this, config);
29         
30     Tine.widgets.ContentTypeTreePanel.superclass.constructor.call(this);
31 };
32
33 Ext.extend(Tine.widgets.ContentTypeTreePanel, Ext.tree.TreePanel, {
34     rootVisible : false,
35     border : false,
36
37     root: null,
38     
39     title: 'Modules', // i18n._('Modules')
40
41     collapsible: true,
42     baseCls: 'ux-arrowcollapse',
43     animCollapse: true,
44     titleCollapse:true,
45     draggable : true,
46     autoScroll: false,
47     autoHeight: true,
48     canonicalName: 'ModulPicker',
49     
50     collapsed: false,
51     renderHidden: true,
52     
53     recordClass: null,
54     /**
55      * @cfg {Tine.Tinebase.Application} app
56      */
57     app: null,
58     
59     /**
60      * @cfg {Array} contentTypes
61      */
62     contentTypes: null,
63     
64     /**
65      * @cfg {String} contentType 
66      */
67     contentType: null,
68     
69     /**
70      * Enable state
71      * 
72      * @type {Boolean}
73      */
74     stateful: true,
75     
76     /**
77      * if a previous state has been applied, this is set to true.
78      * if restoring the last state fails, this is set to false
79      * 
80      * @type Boolean
81      */
82     stateApplied: null,
83     
84     /**
85      * define state events
86      * 
87      * @type {Array}
88      */
89     stateEvents: ['collapse', 'expand', 'collapsenode', 'expandnode'],
90     
91     /**
92      * @private {Ext.tree.treeNode} the latest clicked node
93      */
94     lastClickedNode: null,
95     
96     /**
97      * init
98      */  
99     initComponent: function() {
100         this.stateId = this.app.name + (this.contentType ? this.contentType : '') + '-moduletree';
101         Tine.widgets.ContentTypeTreePanel.superclass.initComponent.call(this);
102         
103         this.setTitle(i18n._(this.title));
104         
105         var treeRoot = new Ext.tree.TreeNode({
106             expanded: true,
107             text : '',
108             allowDrag : false,
109             allowDrop : false,
110             icon : false
111         });
112         var groupNodes = {};
113         this.setRootNode(treeRoot);
114         var treeRoot = this.getRootNode();
115         
116         this.recordClass = Tine[this.app.appName].Model[this.contentType];
117         
118         this.on('click', this.saveClickedNodeState, this);
119         
120         Ext.each (this.contentTypes, function(ct) {
121             var modelName = ct.hasOwnProperty('meta') 
122                 ? ct.meta.modelName 
123                 : (
124                     ct.hasOwnProperty('model')
125                         ? ct.model
126                         : ct.modelName
127                 ); 
128             
129             var modelApp = ct.appName ? Tine.Tinebase.appMgr.get(ct.appName) : this.app;
130             
131             var recordClass = Tine[modelApp.appName].Model[modelName];
132             
133             if (! recordClass) {
134                 // module is disabled (feature switch) or otherwise non-functional
135                 return;
136             }
137             
138             var group = recordClass.getMeta('group');
139             
140             if (group) {
141                 if(! groupNodes[group]) {
142                     groupNodes[group] = new Ext.tree.TreeNode({
143                         id : 'modulenode-' + group,
144                         iconCls: modelApp.appName + modelName,
145                         text: modelApp.i18n._hidden(group),
146                         leaf : false,
147                         expanded: false
148                     });
149                     treeRoot.appendChild(groupNodes[group]);
150                 }
151                 var parentNode = groupNodes[group];
152             } else {
153                 var parentNode = treeRoot;
154             }
155             
156             // check requiredRight if any
157             // add check for model name also
158             if (
159                 ct.requiredRight && 
160                 ! Tine.Tinebase.common.hasRight(ct.requiredRight, this.app.appName, recordClass.getMeta('recordsName').toLowerCase()) &&
161                 ! Tine.Tinebase.common.hasRight(ct.requiredRight, this.app.appName, recordClass.getMeta('modelName').toLowerCase()) &&
162                 ! Tine.Tinebase.common.hasRight(ct.requiredRight, this.app.appName, recordClass.getMeta('modelName').toLowerCase() + 's')
163             ) return true;
164             
165             var c = {
166                 id : 'treenode-' + recordClass.getMeta('modelName'),
167                 contentType: recordClass.getMeta('modelName'),
168                 iconCls: modelApp.appName + modelName,
169                 text: recordClass.getModuleName(),
170                 leaf : true
171             };
172             
173             if (ct.genericCtxActions) {
174                 c.container = modelApp.getRegistry().get('default' + recordClass.getMeta('modelName') + 'Container');
175             }
176             
177             var child = new Ext.tree.TreeNode(c);
178             
179             child.on('click', function() {
180                 this.app.getMainScreen().setActiveContentType(modelName);
181             }, this);
182
183             // append generic ctx-items (Tine.widgets.tree.ContextMenu)
184             if (ct.genericCtxActions) {
185                 this['contextMenu' + modelName] = Tine.widgets.tree.ContextMenu.getMenu({
186                     nodeName: modelApp.i18n.ngettext(recordClass.getMeta('recordName'), recordClass.getMeta('recordsName'), 12),
187                     actions: ct.genericCtxActions,
188                     scope: this,
189                     backend: 'Tinebase_Container',
190                     backendModel: 'Container'
191                 });
192           
193                 child.on('contextmenu', function(node, event) {
194                     event.stopEvent();
195                     if(node.leaf) {
196                         this.ctxNode = node;
197                         this['contextMenu' + modelName].showAt(event.getXY());
198                     }
199                 }, this);
200             }
201             
202             parentNode.appendChild(child);
203         }, this);
204     },
205     
206     /**
207      * saves the last clicked node as state
208      * 
209      * @param {Ext.tree.treeNode} node
210      * @param {Ext.EventObjectImpl} event
211      */
212     saveClickedNodeState: function(node, event) {
213         this.lastClickedNode = node;
214         this.saveState();
215     },
216     
217     /**
218      * @see Ext.Component
219      */
220     getState: function() {
221         var root = this.getRootNode();
222         
223         var state = {
224             expanded: [],
225             selected: this.lastClickedNode ? this.lastClickedNode.id : null
226         };
227         Ext.each(root.childNodes, function(node) {
228             state.expanded.push(!! node.expanded);
229         }, this);
230         
231         return state;
232     },
233     
234     /**
235      * applies state to cmp
236      * 
237      * @param {Object} state
238      */
239     applyState: function(state) {
240         var root = this.getRootNode();
241         Ext.each(state.expanded, function(isExpanded, index) {
242             // check if node exists, as user might have lost permissions for modules
243             if (root.childNodes[index]) {
244                 root.childNodes[index].expanded = isExpanded;
245             }
246         }, this);
247
248         (function() {
249             var node = this.getNodeById(state.selected);
250             if (node) {
251                 node.select();
252                 this.stateApplied = true;
253                 var contentType = node.id.split('-')[1];
254                 this.app.getMainScreen().setActiveContentType(contentType ? contentType : '');
255             } else {
256                 this.stateApplied = false;
257             }
258             
259         }).defer(10, this);
260     },
261     
262     /**
263      * is called after render, calls the superclass and this.afterRenderSelectNode
264      */
265     afterRender: function () {
266         Tine.widgets.ContentTypeTreePanel.superclass.afterRender.call(this);
267         
268         this.afterRenderSelectNode();
269     },
270     
271     /**
272      * is called after render and will be deferred, if state restoring is still running
273      */
274     afterRenderSelectNode: function() {
275         // wait if we don't know already if the state could be applied
276         if (this.stateful === true && this.stateApplied === null) {
277             this.afterRenderSelectNode.defer(20, this)
278             return;
279         }
280         
281         // don't do anything, if a state has been applied
282         if (this.stateful === true && this.stateApplied === true) {
283             return;
284         }
285         
286         // find treenode to select
287         var treeNode = this.getRootNode().findChild('id', 'treenode-' + this.contentType);
288         
289         // if no treenode was found, try to find the module node
290         if (! treeNode) {
291             // get group by current contentType
292             for (var index = 0; index < this.contentTypes.length; index++) {
293                 if (this.contentTypes[index].modelName == this.contentType) {
294                     var group = this.contentTypes[index].group;
295                 }
296             }
297             
298             // if a group was found, try to expand the node
299             if (group) {
300                 var moduleNode = this.getRootNode().findChild('id', 'modulenode-' + group);
301                 if (moduleNode) {
302                     moduleNode.expand();
303                 }
304                 // try to find node a bit later after expanding the parent, so we can be sure the node is rendered already
305                 (function() {
306                     var treeNode = moduleNode.findChild('id', 'treenode-' + this.contentType);
307                     if (treeNode) {
308                         this.getSelectionModel().select(treeNode);
309                     }
310                 }).defer(50, this);
311             }
312         } else {
313             //  select the node if it has been found
314             if (treeNode) {
315                 this.getSelectionModel().select(treeNode);
316             }
317         }
318     },
319     
320     /**
321      * initializes the state and is called before afterRender
322      * 
323      * @see Ext.Component
324      */
325     initState: function() {
326         Tine.widgets.ContentTypeTreePanel.superclass.initState.call(this);
327         
328         (function() {
329             if (this.stateApplied === null) {
330                 this.stateApplied = false;
331             }
332         }).defer(50, this);
333     }
334 });