057c275ef0f87e7bd625a651f3eb81b7c9b8ece2
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / ContextMenu.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 * Module: TYPO3/CMS/Backend/ContextMenu
16 * Javascript container used to load the context menu via AJAX
17 * to render the result in a layer next to the mouse cursor
18 */
19 define(['jquery', 'TYPO3/CMS/Backend/ContextMenuActions'], function ($, ContextMenuActions) {
20
21 /**
22 *
23 * @type {{mousePos: {X: null, Y: null}, delayContextMenuHide: boolean}}
24 * @exports TYPO3/CMS/Backend/ContextMenu
25 */
26 var ContextMenu = {
27 mousePos: {
28 X: null,
29 Y: null
30 },
31 delayContextMenuHide: false,
32 record: {
33 uid: null,
34 table: null
35 }
36 };
37
38 /**
39 * Initialize events
40 */
41 ContextMenu.initializeEvents = function () {
42 $(document).on('click contextmenu', '.t3js-contextmenutrigger', function (event) {
43 // if there is an other "inline" onclick setting, context menu is not triggered
44 // usually this is the case for the foldertree
45 if ($(this).prop('onclick') && event.type === 'click') {
46 return;
47 }
48 event.preventDefault();
49 ContextMenu.show(
50 $(this).data('table'),
51 $(this).data('uid'),
52 $(this).data('context'),
53 $(this).data('iteminfo'),
54 $(this).data('parameters')
55 );
56 });
57
58 // register mouse movement inside the document
59 $(document).on('mousemove', ContextMenu.storeMousePositionEvent);
60 };
61
62 /**
63 * Main function, called from most context menu links
64 *
65 * @param {String} table Table from where info should be fetched
66 * @param {(String|Number)} uid The UID of the item
67 * @param {String} context Context of the item
68 * @param {String} enDisItems Items to disable / enable
69 * @param {String} addParams Additional params
70 * @return void
71 */
72 ContextMenu.show = function (table, uid, context, enDisItems, addParams) {
73 ContextMenu.record = null;
74 ContextMenu.record = {table: table, uid: uid};
75
76 var parameters = '';
77
78 if (typeof table !== 'undefined') {
79 parameters += 'table=' + encodeURIComponent(table);
80 }
81 if (typeof uid !== 'undefined') {
82 parameters += (parameters.length > 0 ? '&' : '') + 'uid=' + uid;
83 }
84 if (typeof context !== 'undefined') {
85 parameters += (parameters.length > 0 ? '&' : '') + 'context=' + context;
86 }
87 if (typeof enDisItems !== 'undefined') {
88 parameters += (parameters.length > 0 ? '&' : '') + 'enDisItems=' + enDisItems;
89 }
90 if (typeof addParams !== 'undefined') {
91 parameters += (parameters.length > 0 ? '&' : '') + 'addParams=' + addParams;
92 }
93 this.fetch(parameters);
94 };
95
96 /**
97 * Make the AJAX request
98 *
99 * @param {array} parameters Parameters sent to the server
100 * @return void
101 */
102 ContextMenu.fetch = function (parameters) {
103 var url = TYPO3.settings.ajaxUrls['contextmenu'];
104 if (parameters) {
105 url += ((url.indexOf('?') == -1) ? '?' : '&') + parameters;
106 }
107 $.ajax(url).done(function (response) {
108 if (typeof response !== "undefined" && Object.keys(response).length > 0) {
109 ContextMenu.populateData(response, 0);
110 }
111 });
112 };
113
114 /**
115 * fills the context menu with content and displays it correctly
116 * depending on the mouse position
117 *
118 * @param {array} items The data that will be put in the menu
119 * @param {Number} level The depth of the context menu
120 */
121 ContextMenu.populateData = function (items, level) {
122 this.initializeContextMenuContainer();
123
124 level = parseInt(level, 10) || 0;
125 var $obj = $('#contentMenu' + level);
126
127 if ($obj.length && (level === 0 || $('#contentMenu' + (level - 1)).is(':visible'))) {
128 var elements = ContextMenu.drawMenu(items, level);
129 $obj.html('<div class="list-group">' + elements + '</div>');
130
131 $('a.list-group-item', $obj).click(function (event) {
132 event.preventDefault();
133
134 if ($(this).hasClass('list-group-item-submenu')) {
135 ContextMenu.openSubmenu(level, $(this));
136 return;
137 }
138
139 var callbackName = $(this).data('callback-action');
140 var callbackModule = $(this).data('callback-module');
141 var clickItem = $(this);
142 if (callbackModule) {
143 require([callbackModule], function (callbackModule) {
144 callbackModule[callbackName].bind(clickItem)(ContextMenu.record.table, ContextMenu.record.uid);
145 });
146 } else if (ContextMenuActions && ContextMenuActions[callbackName]) {
147 ContextMenuActions[callbackName].bind(clickItem)(ContextMenu.record.table, ContextMenu.record.uid);
148 } else {
149 console.log('action: ' + callbackName + ' not found');
150 }
151 ContextMenu.hideAll();
152 });
153
154 $obj.css(ContextMenu.getPosition($obj)).show();
155 }
156 };
157
158 ContextMenu.openSubmenu = function (level, $item) {
159 var $obj = $('#contentMenu' + (level + 1)).html('');
160 $item.next().find('.list-group').clone(true).appendTo($obj);
161 $obj.css(ContextMenu.getPosition($obj)).show();
162 };
163
164 ContextMenu.getPosition = function ($obj) {
165 var x = this.mousePos.X;
166 var y = this.mousePos.Y;
167 var dimsWindow = {
168 width: $(document).width() - 20, // saving margin for scrollbars
169 height: $(document).height()
170 };
171
172 // dimensions for the context menu
173 var dims = {
174 width: $obj.width(),
175 height: $obj.height()
176 };
177
178 var relative = {
179 X: this.mousePos.X - $(document).scrollLeft(),
180 Y: this.mousePos.Y - $(document).scrollTop()
181 };
182
183 // adjusting the Y position of the layer to fit it into the window frame
184 // if there is enough space above then put it upwards,
185 // otherwise adjust it to the bottom of the window
186 if (dimsWindow.height - dims.height < relative.Y) {
187 if (relative.Y > dims.height) {
188 y -= (dims.height - 10);
189 } else {
190 y += (dimsWindow.height - dims.height - relative.Y);
191 }
192 }
193 // adjusting the X position like Y above, but align it to the left side of the viewport if it does not fit completely
194 if (dimsWindow.width - dims.width < relative.X) {
195 if (relative.X > dims.width) {
196 x -= (dims.width - 10);
197 } else if ((dimsWindow.width - dims.width - relative.X) < $(document).scrollLeft()) {
198 x = $(document).scrollLeft();
199 } else {
200 x += (dimsWindow.width - dims.width - relative.X);
201 }
202 }
203 return {left: x + 'px', top: y + 'px'};
204 };
205
206 /**
207 * fills the context menu with content and displays it correctly
208 * depending on the mouse position
209 *
210 * @param {array} items The data that will be put in the menu
211 * @param {Number} level The depth of the context menu
212 */
213 ContextMenu.drawMenu = function (items, level) {
214 var elements = '';
215 $.each(items, function (key, value) {
216 if (value.type === 'item') {
217 elements += ContextMenu.drawActionItem(value);
218 } else if (value.type === 'divider') {
219 elements += '<a class="list-group-item list-group-item-divider"></a>';
220 } else if (value.type === 'submenu' || value.childItems) {
221 elements += '<a class="list-group-item list-group-item-submenu"><span class="list-group-item-icon">' + value.icon + '</span> ' + value.label + '&nbsp;&nbsp;<span class="fa fa-caret-right"></span></a>';
222 var childElements = ContextMenu.drawMenu(value.childItems, 1);
223 elements += '<div class="context-menu contentMenu' + (level + 1) + '" style="display:none;"><div class="list-group">' + childElements + '</div></div>';
224
225 }
226 });
227 return elements;
228 };
229
230 ContextMenu.drawActionItem = function (value) {
231 var attributes = value.additionalAttributes || [];
232 $attributesString = '';
233 for (var attribute in attributes) {
234 $attributesString += ' ' + attribute + '="' + attributes[attribute] + '"';
235 }
236
237 return '<a class="list-group-item"'
238 + ' data-callback-action="' + value.callbackAction + '"'
239 + $attributesString + '><span class="list-group-item-icon">' + value.icon + '</span> ' + value.label + '</a>';
240 };
241 /**
242 * event handler function that saves the
243 * actual position of the mouse
244 * in the context menu object
245 *
246 * @param {Event} event The event object
247 */
248 ContextMenu.storeMousePositionEvent = function (event) {
249 ContextMenu.mousePos.X = event.pageX;
250 ContextMenu.mousePos.Y = event.pageY;
251 ContextMenu.mouseOutFromMenu('#contentMenu0');
252 ContextMenu.mouseOutFromMenu('#contentMenu1');
253 };
254
255 /**
256 * hides a visible menu if the mouse has moved outside
257 * of the object
258 *
259 * @param {Object} obj The object to hide
260 */
261 ContextMenu.mouseOutFromMenu = function (obj) {
262 var $element = $(obj);
263
264 if ($element.length > 0 && $element.is(':visible') && !this.within($element, this.mousePos.X, this.mousePos.Y)) {
265 this.hide($element);
266 } else if ($element.length > 0 && $element.is(':visible')) {
267 this.delayContextMenuHide = true;
268 }
269 };
270
271 /**
272 *
273 * @param {Object} $element
274 * @param {Number} x
275 * @param {Number} y
276 * @returns {Boolean}
277 */
278 ContextMenu.within = function ($element, x, y) {
279 var offset = $element.offset();
280 return (
281 y >= offset.top &&
282 y < offset.top + $element.height() &&
283 x >= offset.left &&
284 x < offset.left + $element.width()
285 );
286 };
287
288 /**
289 * hides a context menu
290 *
291 * @param {Object} obj The context menu object to hide
292 */
293 ContextMenu.hide = function (obj) {
294 this.delayContextMenuHide = false;
295 window.setTimeout(function () {
296 if (!ContextMenu.delayContextMenuHide) {
297 $(obj).hide();
298 }
299 }, 500);
300 };
301
302 /**
303 * hides all context menus
304 */
305 ContextMenu.hideAll = function () {
306 this.hide('#contentMenu0');
307 this.hide('#contentMenu1');
308 };
309
310 /**
311 * manipulates the DOM to add the divs needed for context menu the bottom of the <body>-tag
312 */
313 ContextMenu.initializeContextMenuContainer = function () {
314 if ($('#contentMenu0').length === 0) {
315 var code = '<div id="contentMenu0" class="context-menu"></div><div id="contentMenu1" class="context-menu" style="display: block;"></div>';
316 $('body').append(code);
317 }
318 };
319
320 ContextMenu.initializeEvents();
321
322 return ContextMenu;
323 });