37aa9f38683102926ca3f3355fbac4f82a41936d
[Packages/TYPO3.CMS.git] / typo3 / js / modulemenu.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2010-2011 Steffen Kamper <steffen@typo3.org>
5 * All rights reserved
6 *
7 * This script is part of the TYPO3 project. The TYPO3 project is
8 * free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * The GNU General Public License can be found at
14 * http://www.gnu.org/copyleft/gpl.html.
15 * A copy is found in the textfile GPL.txt and important notices to the license
16 * from the author is found in LICENSE.txt distributed with these scripts.
17 *
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26
27
28 /**
29 * Class to render the module menu and handle the BE navigation
30 *
31 * @author Steffen Kamper
32 */
33
34
35 Ext.ns('TYPO3', 'ModuleMenu');
36
37 TYPO3.ModuleMenu = {};
38
39 Ext.define('TYPO3.model.ModuleMenu', {
40 extend: 'Ext.data.Model',
41 idProperty: 'index',
42 fields: [{
43 name: 'index',
44 type: 'int',
45 },{
46 name: 'key',
47 type: 'string'
48 },{
49 name: 'name',
50 type: 'string'
51 },{
52 name: 'label',
53 type: 'string'
54 },{
55 name: 'description',
56 type: 'string'
57 },{
58 name: 'icon',
59 type: 'string'
60 },{
61 name: 'menuState',
62 type: 'int'
63 },{
64 name: 'navigationComponentId',
65 type: 'string'
66 },{
67 name: 'navigationFrameScript',
68 type: 'string'
69 },{
70 name: 'navframe',
71 type: 'string'
72 },{
73 name: 'navigationFrameScriptParam',
74 type: 'string'
75 },{
76 name: 'link',
77 type: 'string'
78 },{
79 name: 'originalLink',
80 type: 'string'
81 },{
82 name: 'subitems',
83 type: 'int'
84 }],
85 associations: [{
86 name: 'sub',
87 type: 'hasMany',
88 model: 'TYPO3.model.ModuleMenu'
89 }]
90 });
91 TYPO3.ModuleMenu.Store = Ext.create('Ext.data.Store', {
92 storeId: 'moduleMenuStore',
93 model: 'TYPO3.model.ModuleMenu',
94 proxy: {
95 type: 'ajax',
96 url: 'ajax.php?ajaxID=ModuleMenu::getData',
97 extraParams: {
98 'action': 'getModules'
99 },
100 reader: {
101 type: 'json',
102 root: 'root'
103 }
104 },
105 listeners: {
106 beforeload: function(store) {
107 this.loaded = false;
108 },
109 load: function(store) {
110 this.loaded = true;
111 }
112 },
113 // Custom indicator for loaded store:
114 loaded: false,
115 isLoaded: function() {
116 return this.loaded;
117 }
118 });
119
120 TYPO3.ModuleMenu.Template = Ext.create('Ext.XTemplate',
121 '<ul id="typo3-menu">',
122 '<tpl for=".">',
123 ' <li class="menuSection" id="{key}">',
124 ' <div class="modgroup {[this.getStateClass(values)]}">{label}</div>',
125 ' <ul {[this.getStateStyle(values)]}>',
126 ' <tpl for="sub">',
127 ' <li id="{name}" class="submodule mod-{name}">',
128 ' <a title="{description}" href="#" class="modlink">',
129 ' <span class="submodule-icon">',
130 ' <img width="16" height="16" alt="{label}" title="{label}" src="{icon}" />',
131 ' </span>',
132 ' <span>{label}</span>',
133 ' </a>',
134 ' </li>',
135 ' </tpl>',
136 ' </ul>',
137 ' </li>',
138 '</tpl>',
139 '</ul>',
140 {
141 getStateClass: function(value) {
142 return value.menuState ? 'collapsed' : 'expanded';
143 },
144 getStateStyle: function(value) {
145 return value.menuState ? 'style="display:none"' : '';
146 }
147 }
148 );
149
150 TYPO3.ModuleMenu.App = {
151 loadedModule: null,
152 loadedNavigationComponentId: '',
153 availableNavigationComponents: {},
154
155 init: function() {
156 TYPO3.ModuleMenu.Store.load({
157 scope: this,
158 callback: function(records, operation, success) {
159 this.renderMenu(records);
160 }
161 });
162 },
163
164 renderMenu: function(records) {
165 TYPO3.Backend.ModuleMenuContainer.removeAll();
166 TYPO3.Backend.ModuleMenuContainer.addDocked({
167 cls: 'typo3-module-panel-toolbar',
168 height: 22,
169 html: '<div id="typo3-docheader">' +
170 ' <div id="typo3-docheader-row1">' +
171 ' <div class="buttonsleft"></div>' +
172 ' <div class="buttonsright"></div>' +
173 ' </div>' +
174 '</div>',
175 xtype: 'component'
176 });
177 TYPO3.Backend.ModuleMenuContainer.add({
178 xtype: 'dataview',
179 store: TYPO3.ModuleMenu.Store,
180 tpl: TYPO3.ModuleMenu.Template,
181 singleSelect: true,
182 itemSelector: 'li.submodule',
183 overItemCls: 'x-view-over',
184 trackOver: true,
185 selectedItemCls: 'highlighted',
186 itemId: 'modDataView',
187 // ExtJS is not really ready to handle nested template...
188 updateIndexes: function(startIndex, endIndex) {
189 var ns = this.all.elements,
190 records = this.store.getRange(),
191 index = 0, i, j, m, n, record, items;
192 startIndex = startIndex || 0;
193 endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
194 for (i = 0, n = records.length -1; i < n; i++) {
195 items = records[i].getAssociatedData().sub;
196 if (items.length) {
197 for (j = 0, m = items.length - 1; j < m; j++) {
198 if (startIndex <= index && index <= endIndex) {
199 ns[index].viewIndex = index;
200 ns[index].viewRecordId = records[i].internalId;
201 if (!ns[index].boundView) {
202 ns[index].boundView = this.id;
203 }
204 }
205 index++;
206 }
207 }
208 }
209 },
210 listeners: {
211 viewready: {
212 fn: function () {
213 if (top.startInModule) {
214 this.showModule(top.startInModule[0], top.startInModule[1]);
215 } else {
216 this.loadFirstAvailableModule();
217 }
218 },
219 scope: this
220 },
221 // The selection of this view is on the main module. We don't need this
222 beforeselect: {
223 fn: function (view) {
224 return false;
225 }
226 },
227 itemclick: {
228 fn: function(view, record, node, index, event) {
229 var moduleName = node.getAttribute('id');
230 if (moduleName) {
231 TYPO3.ModuleMenu.App.showModule(moduleName);
232 }
233 }
234 },
235 containerclick: {
236 fn: function(view, event) {
237 var item = event.getTarget('li.menuSection', view.getEl());
238 if (item) {
239 var el = Ext.get(item);
240 var id = el.getAttribute('id');
241 var section = el.first('div'), state;
242 if (section.hasCls('expanded')) {
243 state = true;
244 section.removeCls('expanded').addCls('collapsed');
245 el.first('ul').slideOut('t', {
246 easing: 'easeOut',
247 duration: .2,
248 remove: false,
249 useDisplay: true
250 });
251 } else {
252 state = false;
253 section.removeCls('collapsed').addCls('expanded');
254 el.first('ul').slideIn('t', {
255 easing: 'easeIn',
256 duration: .2,
257 remove: false,
258 useDisplay: true
259 });
260 }
261 // save menu state
262 Ext.Ajax.request({
263 url: 'ajax.php?ajaxID=ModuleMenu::saveMenuState',
264 params: {
265 'menuid': 'modmenu_' + id,
266 'state': state
267 }
268 });
269 }
270 return false;
271 }
272 }
273 }
274 });
275 },
276 getRecordFromIndex: function(index) {
277 var i, record, items;
278 for (i = 0; i < TYPO3.ModuleMenu.Store.getCount(); i++) {
279 record = TYPO3.ModuleMenu.Store.getAt(i);
280 items = record.getAssociatedData().sub;
281 if (index < record.get('subitems')) {
282 return items[index];
283 }
284 index -= record.get('subitems');
285 }
286 },
287
288 getRecordFromName: function(name) {
289 var i, j, recordsCount, itemsCount, record, items;
290 for (i = 0, recordsCount = TYPO3.ModuleMenu.Store.getCount(); i < recordsCount; i++) {
291 record = TYPO3.ModuleMenu.Store.getAt(i);
292 items = record.getAssociatedData().sub;
293 for (j = 0, itemsCount = record.get('subitems'); j < itemsCount; j++) {
294 if (items[j].name === name) {
295 return items[j];
296 }
297 }
298 }
299 },
300
301 showModule: function(mod, params) {
302 params = params || '';
303 this.selectedModule = mod;
304
305 params = this.includeId(mod, params);
306 var record = this.getRecordFromName(mod);
307 if (record) {
308 this.loadModuleComponents(record, params);
309 } else {
310 //defined startup module is not present, use the first available instead
311 this.loadFirstAvailableModule(params);
312 }
313 },
314
315 loadFirstAvailableModule: function(params) {
316 params = params || '';
317 if (TYPO3.ModuleMenu.Store.isLoaded() === false) {
318 new Ext.util.DelayedTask(
319 this.loadFirstAvailableModule,
320 this,
321 [params]
322 ).delay(250);
323 } else if (TYPO3.ModuleMenu.Store.getCount() === 0) {
324 // Store is empty, something went wrong
325 TYPO3.Flashmessage.display(TYPO3.Severity.error, 'Module loader', 'No module found. If this is a temporary error, please reload the Backend!', 50000);
326 } else {
327 mod = TYPO3.ModuleMenu.Store.getAt(0).getAssociatedData().sub[0];
328 this.loadModuleComponents(mod, params);
329 }
330 },
331
332 loadModuleComponents: function(record, params) {
333 var mod = record.name;
334 if (record.navigationComponentId) {
335 this.loadNavigationComponent(record.navigationComponentId);
336 TYPO3.Backend.NavigationDummy.hide();
337 TYPO3.Backend.NavigationIframe.getEl().parent().setStyle('overflow', 'auto');
338 } else if (record.navframe || record.navigationFrameScript) {
339 TYPO3.Backend.NavigationDummy.hide();
340 TYPO3.Backend.NavigationContainer.show();
341 this.loadNavigationComponent('typo3-navigationIframe');
342 this.openInNavFrame(record.navigationFrameScript || record.navframe, record.navigationFrameScriptParam);
343 TYPO3.Backend.NavigationIframe.getEl().parent().setStyle('overflow', 'hidden');
344 } else {
345 TYPO3.Backend.NavigationContainer.hide();
346 TYPO3.Backend.NavigationDummy.show();
347 }
348 // Set internal state
349 this.loadedModule = mod;
350 this.highlightModuleMenuItem(mod);
351 this.openInContentFrame(record.originalLink, params);
352
353 // compatibility
354 top.currentSubScript = record.originalLink;
355 top.currentModuleLoaded = mod;
356
357 TYPO3.Backend.doLayout();
358 },
359
360 includeId: function(mod, params) {
361 //get id
362 var section = mod.split('_')[0];
363 if (top.fsMod.recentIds[section]) {
364 params = 'id=' + top.fsMod.recentIds[section] + '&' + params;
365 }
366
367 return params;
368 },
369
370 loadNavigationComponent: function(navigationComponentId) {
371 if (navigationComponentId === this.loadedNavigationComponentId) {
372 if (TYPO3.Backend.NavigationContainer.hidden) {
373 TYPO3.Backend.NavigationContainer.show();
374 }
375
376 return;
377 }
378
379 if (this.loadedNavigationComponentId !== '') {
380 Ext.getCmp(this.loadedNavigationComponentId).hide();
381 }
382
383 var component = Ext.getCmp(navigationComponentId);
384 if (typeof component !== 'object') {
385 if (typeof this.availableNavigationComponents[navigationComponentId] !== 'function') {
386 throw 'The navigation component "' + navigationComponentId + '" is not available ' +
387 'or has no valid callback function';
388 }
389
390 component = this.availableNavigationComponents[navigationComponentId]();
391 TYPO3.Backend.NavigationContainer.add(component);
392 }
393
394 component.show()
395
396 // backwards compatibility
397 top.nav = component;
398
399 TYPO3.Backend.NavigationContainer.show();
400 this.loadedNavigationComponentId = navigationComponentId;
401 },
402
403 registerNavigationComponent: function(componentId, initCallback) {
404 this.availableNavigationComponents[componentId] = initCallback;
405 },
406
407 openInNavFrame: function(url, params) {
408 var navUrl = url + (params ? (url.indexOf('?') !== -1 ? '&' : '?') + params : '');
409 var currentUrl = this.relativeUrl(TYPO3.Backend.NavigationIframe.getUrl());
410 if (currentUrl !== navUrl) {
411 TYPO3.Backend.NavigationIframe.setUrl(navUrl);
412 }
413 },
414
415 openInContentFrame: function(url, params) {
416 if (top.nextLoadModuleUrl) {
417 TYPO3.Backend.ContentContainer.setUrl(top.nextLoadModuleUrl);
418 top.nextLoadModuleUrl = '';
419 } else {
420 TYPO3.Backend.ContentContainer.setUrl(url + (params ? (url.indexOf('?') !== -1 ? '&' : '?') + params : ''));
421 }
422 },
423
424 highlightModuleMenuItem: function(module, mainModule) {
425 var highlighted = Ext.fly('typo3-menu').query('li.highlighted');
426 Ext.Array.each(highlighted, function(el) {
427 Ext.fly(el).removeCls('highlighted');
428 });
429 Ext.fly(module).addCls('highlighted');
430 },
431
432 relativeUrl: function(url) {
433 return url.replace(TYPO3.configuration.siteUrl + 'typo3/', '');
434 },
435
436 refreshMenu: function() {
437 TYPO3.ModuleMenu.Store.load({
438 scope: this,
439 callback: function(records, operation, success) {
440 this.renderMenu(records);
441 if (this.loadedModule) {
442 this.highlightModuleMenuItem(this.loadedModule);
443 }
444 }
445 });
446 },
447
448 reloadFrames: function() {
449 TYPO3.Backend.NavigationIframe.refresh();
450 TYPO3.Backend.ContentContainer.refresh();
451 }
452
453 };
454
455
456
457 Ext.onReady(function() {
458 TYPO3.ModuleMenu.App.init();
459
460 // keep backward compatibility
461 top.list = TYPO3.Backend.ContentContainer;
462 top.list_frame = top.list.getIframe();
463 top.nav_frame = TYPO3.Backend.NavigationContainer.PageTree;
464
465 top.TYPO3ModuleMenu = TYPO3.ModuleMenu.App;
466 top.content = {
467 nav_frame: TYPO3.Backend.NavigationContainer.PageTree,
468 list_frame: TYPO3.Backend.ContentContainer.getIframe(),
469 location: TYPO3.Backend.ContentContainer.getIframe().location,
470 document: TYPO3.Backend.ContentContainer.getIframe()
471 }
472 });
473
474
475 /*******************************************************************************
476 *
477 * Backwards compatability handling down here
478 *
479 ******************************************************************************/
480
481 /**
482 * Highlight module:
483 */
484 var currentlyHighLightedId = '';
485 var currentlyHighLighted_restoreValue = '';
486 var currentlyHighLightedMain = '';
487 function highlightModuleMenuItem(trId, mainModule) {
488 TYPO3.ModuleMenu.App.highlightModule(trId, mainModule);
489 }