a0559567218b1814a146eb12e6f4f4108e726568
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / LayoutModule / DragDrop.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/LayoutModule/DragDrop
16 * this JS code does the drag+drop logic for the Layout module (Web => Page)
17 * based on jQuery UI
18 */
19 define(['jquery', 'jquery-ui/droppable'], function($) {
20 'use strict';
21
22 /**
23 *
24 * @type {{contentIdentifier: string, dragIdentifier: string, dragHeaderIdentifier: string, dropZoneIdentifier: string, columnIdentifier: string, validDropZoneClass: string, dropPossibleHoverClass: string, addContentIdentifier: string, originalStyles: string}}
25 * @exports TYPO3/CMS/Backend/LayoutModule/DragDrop
26 */
27 var DragDrop = {
28 contentIdentifier: '.t3js-page-ce',
29 dragIdentifier: '.t3-page-ce-dragitem',
30 dragHeaderIdentifier: '.t3js-page-ce-draghandle',
31 dropZoneIdentifier: '.t3js-page-ce-dropzone-available',
32 columnIdentifier: '.t3js-page-column',
33 validDropZoneClass: 'active',
34 dropPossibleHoverClass: 't3-page-ce-dropzone-possible',
35 addContentIdentifier: '.t3js-page-new-ce',
36 clone: true,
37 originalStyles: ''
38 };
39
40 /**
41 * initializes Drag+Drop for all content elements on the page
42 */
43 DragDrop.initialize = function() {
44 $(DragDrop.contentIdentifier).draggable({
45 handle: DragDrop.dragHeaderIdentifier,
46 scope: 'tt_content',
47 cursor: 'move',
48 distance: 20,
49 addClasses: 'active-drag',
50 revert: 'invalid',
51 zIndex: 100,
52 start: function(evt, ui) {
53 DragDrop.onDragStart($(this));
54 },
55 stop: function(evt, ui) {
56 DragDrop.onDragStop($(this));
57 }
58 });
59
60 $(DragDrop.dropZoneIdentifier).droppable({
61 accept: this.contentIdentifier,
62 scope: 'tt_content',
63 tolerance: 'pointer',
64 over: function(evt, ui) {
65 DragDrop.onDropHoverOver($(ui.draggable), $(this));
66 },
67 out: function(evt, ui) {
68 DragDrop.onDropHoverOut($(ui.draggable), $(this));
69 },
70 drop: function(evt, ui) {
71 DragDrop.onDrop($(ui.draggable), $(this), evt);
72 }
73 });
74 };
75
76 /**
77 * called when a draggable is selected to be moved
78 * @param $element a jQuery object for the draggable
79 * @private
80 */
81 DragDrop.onDragStart = function($element) {
82 // Add css class for the drag shadow
83 DragDrop.originalStyles = $element.get(0).style.cssText;
84 $element.children(DragDrop.dragIdentifier).addClass('dragitem-shadow');
85 $element.append('<div class="ui-draggable-copy-message">' + TYPO3.lang['dragdrop.copy.message'] + '</div>');
86 // Hide create new element button
87 $element.children(DragDrop.dropZoneIdentifier).addClass('drag-start');
88 $element.closest(DragDrop.columnIdentifier).removeClass('active');
89
90 $element.parents(DragDrop.columnHolderIdentifier).find(DragDrop.addContentIdentifier).hide();
91 $element.find(DragDrop.dropZoneIdentifier).hide();
92
93 $(DragDrop.dropZoneIdentifier).each(function() {
94 var $me = $(this);
95 if ($me.parent().find('.t3js-toggle-new-content-element-wizard').length) {
96 $me.addClass(DragDrop.validDropZoneClass);
97 } else {
98 $me.closest(DragDrop.contentIdentifier).find('> ' + DragDrop.addContentIdentifier + ', > > ' + DragDrop.addContentIdentifier).show();
99 }
100 });
101 };
102
103 /**
104 * called when a draggable is released
105 * @param $element a jQuery object for the draggable
106 * @private
107 */
108 DragDrop.onDragStop = function($element) {
109 // Remove css class for the drag shadow
110 $element.children(DragDrop.dragIdentifier).removeClass('dragitem-shadow');
111 // Show create new element button
112 $element.children(DragDrop.dropZoneIdentifier).removeClass('drag-start');
113 $element.closest(DragDrop.columnIdentifier).addClass('active');
114 $element.parents(DragDrop.columnHolderIdentifier).find(DragDrop.addContentIdentifier).show();
115 $element.find(DragDrop.dropZoneIdentifier).show();
116 $element.find('.ui-draggable-copy-message').remove();
117
118 // Reset inline style
119 $element.get(0).style.cssText = DragDrop.originalStyles;
120
121 $(DragDrop.dropZoneIdentifier + '.' + DragDrop.validDropZoneClass).removeClass(DragDrop.validDropZoneClass);
122 };
123
124 /**
125 * adds CSS classes when hovering over a dropzone
126 * @param $draggableElement
127 * @param $droppableElement
128 * @private
129 */
130 DragDrop.onDropHoverOver = function($draggableElement, $droppableElement) {
131 if ($droppableElement.hasClass(DragDrop.validDropZoneClass)) {
132 $droppableElement.addClass(DragDrop.dropPossibleHoverClass);
133 }
134 };
135
136 /**
137 * removes the CSS classes after hovering out of a dropzone again
138 * @param $draggableElement
139 * @param $droppableElement
140 * @private
141 */
142 DragDrop.onDropHoverOut = function($draggableElement, $droppableElement) {
143 $droppableElement.removeClass(DragDrop.dropPossibleHoverClass);
144 };
145
146 /**
147 * this method does the whole logic when a draggable is dropped on to a dropzone
148 * sending out the request and afterwards move the HTML element in the right place.
149 *
150 * @param $draggableElement
151 * @param $droppableElement
152 * @param {Event} evt the event
153 * @private
154 */
155 DragDrop.onDrop = function($draggableElement, $droppableElement, evt) {
156 var newColumn = DragDrop.getColumnPositionForElement($droppableElement);
157
158 $droppableElement.removeClass(DragDrop.dropPossibleHoverClass);
159 var $pasteAction = typeof $draggableElement === 'number';
160
161 // send an AJAX requst via the AjaxDataHandler
162 var contentElementUid = $pasteAction ? $draggableElement : parseInt($draggableElement.data('uid'));
163 if (contentElementUid > 0) {
164 var parameters = {};
165 // add the information about a possible column position change
166 var targetFound = $droppableElement.closest(DragDrop.contentIdentifier).data('uid');
167 // the item was moved to the top of the colPos, so the page ID is used here
168 var targetPid = 0;
169 if (typeof targetFound === 'undefined') {
170 // the actual page is needed
171 targetPid = $('[data-page]').first().data('page');
172 } else {
173 // the negative value of the content element after where it should be moved
174 targetPid = 0 - parseInt(targetFound);
175 }
176 var language = parseInt($droppableElement.closest('[data-language-uid]').data('language-uid'));
177 var colPos = 0;
178 if (targetPid !== 0) {
179 colPos = newColumn;
180 }
181 parameters['cmd'] = {tt_content: {}};
182 parameters['data'] = {tt_content: {}};
183 var copyAction = (evt && evt.originalEvent.ctrlKey || $droppableElement.hasClass('t3js-paste-copy'));
184 if (copyAction) {
185 parameters['cmd']['tt_content'][contentElementUid] = {
186 copy: {
187 action: 'paste',
188 target: targetPid,
189 update: {
190 colPos: colPos,
191 sys_language_uid: language
192 }
193 }
194 };
195 DragDrop.ajaxAction($droppableElement, $draggableElement, parameters, copyAction, $pasteAction);
196 } else {
197 parameters['data']['tt_content'][contentElementUid] = {
198 colPos: colPos,
199 sys_language_uid: language
200 };
201 if ($pasteAction) {
202 parameters = {
203 CB: {
204 paste: 'tt_content|' + targetPid,
205 update: {
206 colPos: colPos,
207 sys_language_uid: language
208 }
209 }
210 };
211 } else {
212 parameters['cmd']['tt_content'][contentElementUid] = {move: targetPid};
213 }
214 // fire the request, and show a message if it has failed
215 DragDrop.ajaxAction($droppableElement, $draggableElement, parameters, copyAction, $pasteAction);
216 }
217 }
218 };
219
220 /**
221 * this method does the actual AJAX request for both, the move and the copy action.
222 *
223 * @param $droppableElement
224 * @param $draggableElement
225 * @param parameters
226 * @param $copyAction
227 * @param $pasteAction
228 * @private
229 */
230 DragDrop.ajaxAction = function($droppableElement, $draggableElement, parameters, $copyAction, $pasteAction) {
231 require(['TYPO3/CMS/Backend/AjaxDataHandler'], function(DataHandler) {
232 DataHandler.process(parameters).done(function(result) {
233 if (!result.hasErrors) {
234 // insert draggable on the new position
235 if (!$pasteAction) {
236 if (!$droppableElement.parent().hasClass(DragDrop.contentIdentifier.substring(1))) {
237 $draggableElement.detach().css({top: 0, left: 0})
238 .insertAfter($droppableElement.closest(DragDrop.dropZoneIdentifier));
239 } else {
240 $draggableElement.detach().css({top: 0, left: 0})
241 .insertAfter($droppableElement.closest(DragDrop.contentIdentifier));
242 }
243 }
244 self.location.reload(true);
245 }
246 });
247 });
248 };
249
250 /**
251 * returns the next "upper" container colPos parameter inside the code
252 * @param $element
253 * @return int|null the colPos
254 */
255 DragDrop.getColumnPositionForElement = function($element) {
256 var $columnContainer = $element.closest('[data-colpos]');
257 if ($columnContainer.length && $columnContainer.data('colpos') !== 'undefined') {
258 return $columnContainer.data('colpos');
259 } else {
260 return false;
261 }
262 };
263
264 $(DragDrop.initialize);
265 return DragDrop;
266 });