9622d2457980941bd7058258e95c28b7539bd22c
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / ModuleMenu.js
1 /*
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13
14
15 /**
16 * Class to render the module menu and handle the BE navigation
17 */
18 require(
19 [
20 'jquery',
21 'TYPO3/CMS/Backend/Storage',
22 'TYPO3/CMS/Backend/Icons',
23 'TYPO3/CMS/Backend/Viewport',
24 'TYPO3/CMS/Backend/Event/ClientRequest',
25 'TYPO3/CMS/Backend/Event/TriggerRequest'
26 ],
27 function ($, Storage, Icons, Viewport, ClientRequest, TriggerRequest) {
28 if (typeof TYPO3.ModuleMenu !== 'undefined') {
29 return TYPO3.ModuleMenu.App;
30 }
31
32 TYPO3.ModuleMenu = {};
33 TYPO3.ModuleMenu.App = {
34 loadedModule: null,
35 loadedNavigationComponentId: '',
36 availableNavigationComponents: {},
37
38 initialize: function () {
39 var me = this;
40
41 var deferred = $.Deferred();
42 deferred.resolve();
43
44 // load the start module
45 if (top.startInModule && top.startInModule[0] && $('#' + top.startInModule[0]).length > 0) {
46 deferred = me.showModule(
47 top.startInModule[0],
48 top.startInModule[1]
49 );
50 } else {
51 // fetch first module
52 if ($('.t3js-mainmodule:first').attr('id')) {
53 deferred = me.showModule(
54 $('.t3js-mainmodule:first').attr('id')
55 );
56 }
57 // else case: the main module has no entries, this is probably a backend
58 // user with very little access rights, maybe only the logout button and
59 // a user settings module in topbar.
60 }
61
62 deferred.then(function() {
63 // check if module menu should be collapsed or not
64 var state = Storage.Persistent.get('BackendComponents.States.typo3-module-menu');
65 if (state && state.collapsed) {
66 TYPO3.ModuleMenu.App.toggleMenu(state.collapsed === 'true');
67 }
68
69 // check if there are collapsed items in the users' configuration
70 var collapsedMainMenuItems = me.getCollapsedMainMenuItems();
71 $.each(collapsedMainMenuItems, function (key, itm) {
72 if (itm !== true) {
73 return;
74 }
75 var $group = $('#' + key);
76 if ($group.length > 0) {
77 var $groupContainer = $group.find('.modulemenu-group-container');
78 $group.addClass('collapsed').removeClass('expanded');
79 TYPO3.Backend.NavigationContainer.cleanup();
80 $groupContainer.hide().promise().done(function () {
81 TYPO3.Backend.doLayout();
82 });
83 }
84 });
85 me.initializeEvents();
86 });
87 },
88
89 initializeEvents: function () {
90 var me = this;
91 $(document).on('click', '.modulemenu-group .modulemenu-group-header', function () {
92 var $group = $(this).parent('.modulemenu-group');
93 var $groupContainer = $group.find('.modulemenu-group-container');
94
95 TYPO3.Backend.NavigationContainer.cleanup();
96 if ($group.hasClass('expanded')) {
97 me.addCollapsedMainMenuItem($group.attr('id'));
98 $group.addClass('collapsed').removeClass('expanded');
99 $groupContainer.stop().slideUp().promise().done(function () {
100 TYPO3.Backend.doLayout();
101 });
102 } else {
103 me.removeCollapseMainMenuItem($group.attr('id'));
104 $group.addClass('expanded').removeClass('collapsed');
105 $groupContainer.stop().slideDown().promise().done(function () {
106 TYPO3.Backend.doLayout();
107 });
108 }
109
110 });
111 // register clicking on sub modules
112 $(document).on('click', '.modulemenu-item,.t3-menuitem-submodule', function (evt) {
113 evt.preventDefault();
114 me.showModule(
115 $(this).attr('id'),
116 null,
117 evt
118 );
119 });
120 $(document).on('click', '.t3js-topbar-button-modulemenu',
121 function (evt) {
122 evt.preventDefault();
123 TYPO3.ModuleMenu.App.toggleMenu();
124 }
125 );
126 $(document).on('click', '.t3js-scaffold-content-overlay',
127 function (evt) {
128 evt.preventDefault();
129 TYPO3.ModuleMenu.App.toggleMenu(true);
130 }
131 );
132 $(document).on('click', '.t3js-topbar-button-navigationcomponent',
133 function (evt) {
134 evt.preventDefault();
135 TYPO3.Backend.NavigationContainer.toggle();
136 }
137 );
138
139 },
140 /**
141 * @param {Boolean} collapse
142 */
143 toggleMenu: function (collapse) {
144 TYPO3.Backend.NavigationContainer.cleanup();
145
146 var $mainContainer = $('.t3js-scaffold');
147 var expandedClass = 'scaffold-modulemenu-expanded';
148
149 if (typeof collapse === 'undefined') {
150 collapse = $mainContainer.hasClass(expandedClass);
151 }
152 $mainContainer.toggleClass(expandedClass, !collapse);
153 if (!collapse) {
154 $('.scaffold')
155 .removeClass('scaffold-search-expanded')
156 .removeClass('scaffold-toolbar-expanded');
157 }
158
159 // Persist collapsed state in the UC of the current user
160 Storage.Persistent.set(
161 'BackendComponents.States.typo3-module-menu',
162 {
163 collapsed: collapse
164 }
165 );
166
167 TYPO3.Backend.doLayout();
168 },
169
170 /* fetch the data for a submodule */
171 getRecordFromName: function (name) {
172 var $subModuleElement = $('#' + name);
173 return {
174 name: name,
175 navigationComponentId: $subModuleElement.data('navigationcomponentid'),
176 navigationFrameScript: $subModuleElement.data('navigationframescript'),
177 navigationFrameScriptParam: $subModuleElement.data('navigationframescriptparameters'),
178 link: $subModuleElement.find('a').data('link')
179 };
180 },
181
182 /**
183 * @param {string} mod
184 * @param {string} params
185 * @param {Event} [event]
186 * @return {jQuery.Deferred}
187 */
188 showModule: function (mod, params, event) {
189 params = params || '';
190 params = this.includeId(mod, params);
191 var record = this.getRecordFromName(mod);
192 return this.loadModuleComponents(
193 record,
194 params,
195 new ClientRequest('typo3.showModule', event)
196 );
197 },
198
199 /**
200 * @param {object} record
201 * @param {string} params
202 * @param {InteractionRequest} [interactionRequest]
203 * @return {jQuery.Deferred}
204 */
205 loadModuleComponents: function (record, params, interactionRequest) {
206 var mod = record.name;
207
208 var deferred = TYPO3.Backend.ContentContainer.beforeSetUrl(interactionRequest);
209 deferred.then(
210 $.proxy(function() {
211 if (record.navigationComponentId) {
212 this.loadNavigationComponent(record.navigationComponentId);
213 } else if (record.navigationFrameScript) {
214 TYPO3.Backend.NavigationContainer.show('typo3-navigationIframe');
215 this.openInNavFrame(
216 record.navigationFrameScript,
217 record.navigationFrameScriptParam,
218 new TriggerRequest(
219 'typo3.loadModuleComponents',
220 interactionRequest
221 )
222 );
223 } else {
224 TYPO3.Backend.NavigationContainer.hide();
225 }
226
227 this.highlightModuleMenuItem(mod);
228 this.loadedModule = mod;
229 this.openInContentFrame(
230 record.link,
231 params,
232 new TriggerRequest(
233 'typo3.loadModuleComponents',
234 interactionRequest
235 )
236 );
237
238 // compatibility
239 top.currentSubScript = record.link;
240 top.currentModuleLoaded = mod;
241
242 TYPO3.Backend.doLayout();
243 }, this
244 ));
245
246 return deferred;
247 },
248
249 includeId: function (mod, params) {
250 if (typeof mod === 'undefined') {
251 return params;
252 }
253 //get id
254 var section = mod.split('_')[0];
255 if (top.fsMod.recentIds[section]) {
256 params = 'id=' + top.fsMod.recentIds[section] + '&' + params;
257 }
258
259 return params;
260 },
261
262 loadNavigationComponent: function (navigationComponentId) {
263 TYPO3.Backend.NavigationContainer.show(navigationComponentId);
264 if (navigationComponentId === this.loadedNavigationComponentId) {
265 return;
266 }
267 if (this.loadedNavigationComponentId !== '') {
268 $('#navigationComponent-' + this.loadedNavigationComponentId).hide();
269 }
270 if ($('.t3js-scaffold-content-navigation [data-component="' + navigationComponentId + '"]').length < 1) {
271 $('.t3js-scaffold-content-navigation')
272 .append('<div class="scaffold-content-navigation-component" data-component="' + navigationComponentId + '" id="navigationComponent-' + navigationComponentId + '"></div>');
273 }
274 // allow to render the pagetree hard-coded in order to have acceptance tests apply correctly
275 // and to ensure that something is loaded
276 var component = Ext.getCmp(navigationComponentId);
277 if (typeof this.availableNavigationComponents['typo3-pagetree'] === 'undefined') {
278 component = new TYPO3.Components.PageTree.App();
279 component.render('navigationComponent-' + navigationComponentId);
280 this.availableNavigationComponents['typo3-pagetree'] = component;
281 // re-evaluate the component
282 component = Ext.getCmp(navigationComponentId);
283 }
284
285 if (typeof component === 'undefined') {
286 var self = this,
287 deferredComponentExists = $.Deferred();
288
289 function checkIfComponentIdIsAvailable(componentId) {
290 if (typeof self.availableNavigationComponents[componentId] === 'undefined') {
291 setTimeout(function (id) {
292 checkIfComponentIdIsAvailable(id);
293 }, 100, componentId);
294 } else {
295 deferredComponentExists.resolve();
296 }
297 }
298 checkIfComponentIdIsAvailable(navigationComponentId);
299
300 deferredComponentExists.promise().done(function() {
301 component = self.availableNavigationComponents[navigationComponentId]();
302 component.render('navigationComponent-' + navigationComponentId);
303
304 TYPO3.Backend.NavigationContainer.show(navigationComponentId);
305 self.loadedNavigationComponentId = navigationComponentId;
306 });
307 } else {
308 // Tree was previously rendered, and was hidden because a different component was displayed
309 TYPO3.Backend.NavigationContainer.show(navigationComponentId);
310 this.loadedNavigationComponentId = navigationComponentId;
311 }
312 },
313
314 registerNavigationComponent: function (componentId, initCallback) {
315 if (typeof this.availableNavigationComponents[componentId] === 'undefined') {
316 this.availableNavigationComponents[componentId] = initCallback;
317 }
318 },
319
320 /**
321 * @param {string} url
322 * @param {string} params
323 * @param {InteractionRequest} [interactionRequest]
324 * @return {jQuery.Deferred}
325 */
326 openInNavFrame: function (url, params, interactionRequest) {
327 var navUrl = url + (params ? (url.indexOf('?') !== -1 ? '&' : '?') + params : '');
328 var currentUrl = TYPO3.Backend.NavigationContainer.getUrl();
329 var deferred = TYPO3.Backend.NavigationContainer.setUrl(
330 url,
331 new TriggerRequest('typo3.openInNavFrame', interactionRequest)
332 );
333 if (currentUrl !== navUrl) {
334 // if deferred is already resolved, execute directly
335 if (deferred.state() === 'resolved') {
336 TYPO3.Backend.NavigationContainer.refresh();
337 // otherwise hand in future callback
338 } else {
339 deferred.then(TYPO3.Backend.NavigationContainer.refresh);
340 }
341 }
342 return deferred;
343 },
344
345 /**
346 * @param {string} url
347 * @param {string} params
348 * @param {InteractionRequest} [interactionRequest]
349 * @return {jQuery.Deferred}
350 */
351 openInContentFrame: function (url, params, interactionRequest) {
352 var deferred;
353
354 if (top.nextLoadModuleUrl) {
355 deferred = TYPO3.Backend.ContentContainer.setUrl(
356 top.nextLoadModuleUrl,
357 new TriggerRequest('typo3.openInContentFrame', interactionRequest)
358 );
359 top.nextLoadModuleUrl = '';
360 } else {
361 var urlToLoad = url + (params ? (url.indexOf('?') !== -1 ? '&' : '?') + params : '');
362 deferred = TYPO3.Backend.ContentContainer.setUrl(
363 urlToLoad,
364 new TriggerRequest('typo3.openInContentFrame', interactionRequest)
365 );
366 }
367
368 return deferred;
369 },
370
371 highlightModuleMenuItem: function (module, mainModule) {
372 $('.modulemenu-item.active').removeClass('active');
373 $('#' + module).addClass('active');
374 },
375
376 // refresh the HTML by fetching the menu again
377 refreshMenu: function () {
378 $.ajax(TYPO3.settings.ajaxUrls['modulemenu']).done(function (result) {
379 $('#menu').replaceWith(result.menu);
380 if (top.currentModuleLoaded) {
381 TYPO3.ModuleMenu.App.highlightModuleMenuItem(top.currentModuleLoaded);
382 }
383 TYPO3.Backend.doLayout();
384 });
385 },
386
387 reloadFrames: function () {
388 TYPO3.Backend.NavigationContainer.refresh();
389 TYPO3.Backend.ContentContainer.refresh();
390 },
391
392 /**
393 * fetches all module menu elements in the local storage that should be collapsed
394 * @returns {*}
395 */
396 getCollapsedMainMenuItems: function () {
397 if (TYPO3.Storage.Persistent.isset('modulemenu')) {
398 return JSON.parse(TYPO3.Storage.Persistent.get('modulemenu'));
399 } else {
400 return {};
401 }
402 },
403
404 /**
405 * adds a module menu item to the local storage
406 * @param item
407 */
408 addCollapsedMainMenuItem: function (item) {
409 var existingItems = this.getCollapsedMainMenuItems();
410 existingItems[item] = true;
411 TYPO3.Storage.Persistent.set('modulemenu', JSON.stringify(existingItems));
412 },
413
414 /**
415 * removes a module menu item from the local storage
416 * @param item
417 */
418 removeCollapseMainMenuItem: function (item) {
419 var existingItems = this.getCollapsedMainMenuItems();
420 delete existingItems[item];
421 TYPO3.Storage.Persistent.set('modulemenu', JSON.stringify(existingItems));
422 }
423
424 };
425 // start the module menu app
426 TYPO3.ModuleMenu.App.initialize();
427 return TYPO3.ModuleMenu;
428 }
429 );