2 * This file is part of the TYPO3 CMS project.
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.
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
11 * The TYPO3 project - inspiring people to share!
15 * Module: TYPO3/CMS/Backend/LayoutModule/DragDrop
16 * this JS code does the drag+drop logic for the Layout module (Web => Page)
19 import * as $ from 'jquery';
20 import 'jquery-ui/droppable';
21 import DataHandler from 'TYPO3/CMS/Backend/AjaxDataHandler';
22 import ResponseInterface from 'TYPO3/CMS/Backend/AjaxDataHandler/ResponseInterface';
25 interface Parameters {
26 cmd?: { tt_content: { [key: string]: any } };
27 data?: { tt_content: { [key: string]: any } };
28 CB?: { paste: string, update: { colPos: number | boolean, sys_language_uid: number }};
31 interface DroppableEventUIParam {
34 position: { top: number; left: number; };
35 offset: { top: number; left: number; };
40 private static readonly contentIdentifier: string = '.t3js-page-ce';
41 private static readonly dragIdentifier: string = '.t3-page-ce-dragitem';
42 private static readonly dragHeaderIdentifier: string = '.t3js-page-ce-draghandle';
43 private static readonly dropZoneIdentifier: string = '.t3js-page-ce-dropzone-available';
44 private static readonly columnIdentifier: string = '.t3js-page-column';
45 private static readonly validDropZoneClass: string = 'active';
46 private static readonly dropPossibleHoverClass: string = 't3-page-ce-dropzone-possible';
47 private static readonly addContentIdentifier: string = '.t3js-page-new-ce';
48 private static originalStyles: string = '';
51 * initializes Drag+Drop for all content elements on the page
53 public static initialize(): void {
54 $(DragDrop.contentIdentifier).draggable({
55 handle: DragDrop.dragHeaderIdentifier,
59 // removed because of incompatible types:
60 // addClasses: 'active-drag',
63 start: (evt: JQueryEventObject, ui: DroppableEventUIParam): void => {
64 DragDrop.onDragStart($(evt.target));
66 stop: (evt: JQueryEventObject, ui: DroppableEventUIParam): void => {
67 DragDrop.onDragStop($(evt.target));
71 $(DragDrop.dropZoneIdentifier).droppable({
72 accept: this.contentIdentifier,
75 over: (evt: JQueryEventObject, ui: DroppableEventUIParam): void => {
76 DragDrop.onDropHoverOver($(ui.draggable), $(evt.target));
78 out: (evt: JQueryEventObject, ui: DroppableEventUIParam): void => {
79 DragDrop.onDropHoverOut($(ui.draggable), $(evt.target));
81 drop: (evt: JQueryEventObject, ui: DroppableEventUIParam): void => {
82 DragDrop.onDrop($(ui.draggable), $(evt.target), evt);
88 * called when a draggable is selected to be moved
89 * @param $element a jQuery object for the draggable
92 public static onDragStart($element: JQuery): void {
93 // Add css class for the drag shadow
94 DragDrop.originalStyles = $element.get(0).style.cssText;
95 $element.children(DragDrop.dragIdentifier).addClass('dragitem-shadow');
96 $element.append('<div class="ui-draggable-copy-message">' + TYPO3.lang['dragdrop.copy.message'] + '</div>');
97 // Hide create new element button
98 $element.children(DragDrop.dropZoneIdentifier).addClass('drag-start');
99 $element.closest(DragDrop.columnIdentifier).removeClass('active');
101 // TODO decide what to do with this
102 // $element.parents(DragDrop.columnHolderIdentifier).find(DragDrop.addContentIdentifier).hide();
103 $element.find(DragDrop.dropZoneIdentifier).hide();
105 $(DragDrop.dropZoneIdentifier).each((index: number, element: HTMLElement): void => {
106 const $me = $(element);
107 if ($me.parent().find('.t3js-toggle-new-content-element-wizard').length) {
108 $me.addClass(DragDrop.validDropZoneClass);
110 $me.closest(DragDrop.contentIdentifier)
111 .find('> ' + DragDrop.addContentIdentifier + ', > > ' + DragDrop.addContentIdentifier).show();
117 * called when a draggable is released
118 * @param $element a jQuery object for the draggable
121 public static onDragStop($element: JQuery): void {
122 // Remove css class for the drag shadow
123 $element.children(DragDrop.dragIdentifier).removeClass('dragitem-shadow');
124 // Show create new element button
125 $element.children(DragDrop.dropZoneIdentifier).removeClass('drag-start');
126 $element.closest(DragDrop.columnIdentifier).addClass('active');
127 // TODO decide what to do with this
128 // $element.parents(DragDrop.columnHolderIdentifier).find(DragDrop.addContentIdentifier).show();
129 $element.find(DragDrop.dropZoneIdentifier).show();
130 $element.find('.ui-draggable-copy-message').remove();
132 // Reset inline style
133 $element.get(0).style.cssText = DragDrop.originalStyles;
135 $(DragDrop.dropZoneIdentifier + '.' + DragDrop.validDropZoneClass).removeClass(DragDrop.validDropZoneClass);
139 * adds CSS classes when hovering over a dropzone
140 * @param $draggableElement
141 * @param $droppableElement
144 public static onDropHoverOver($draggableElement: JQuery, $droppableElement: JQuery): void {
145 if ($droppableElement.hasClass(DragDrop.validDropZoneClass)) {
146 $droppableElement.addClass(DragDrop.dropPossibleHoverClass);
151 * removes the CSS classes after hovering out of a dropzone again
152 * @param $draggableElement
153 * @param $droppableElement
156 public static onDropHoverOut($draggableElement: JQuery, $droppableElement: JQuery): void {
157 $droppableElement.removeClass(DragDrop.dropPossibleHoverClass);
161 * this method does the whole logic when a draggable is dropped on to a dropzone
162 * sending out the request and afterwards move the HTML element in the right place.
164 * @param $draggableElement
165 * @param $droppableElement
166 * @param {Event} evt the event
169 public static onDrop($draggableElement: number | JQuery, $droppableElement: JQuery, evt: JQueryEventObject): void {
170 const newColumn = DragDrop.getColumnPositionForElement($droppableElement);
172 $droppableElement.removeClass(DragDrop.dropPossibleHoverClass);
173 const $pasteAction = typeof $draggableElement === 'number';
175 // send an AJAX requst via the AjaxDataHandler
176 const contentElementUid: number = $pasteAction === true ?
177 <number>$draggableElement :
178 parseInt((<JQuery>$draggableElement).data('uid'), 10);
180 if (typeof(contentElementUid) === 'number' && contentElementUid > 0) {
181 let parameters: Parameters = {};
182 // add the information about a possible column position change
183 const targetFound = $droppableElement.closest(DragDrop.contentIdentifier).data('uid');
184 // the item was moved to the top of the colPos, so the page ID is used here
187 if (typeof targetFound === 'undefined') {
188 // the actual page is needed
189 targetPid = $('[data-page]').first().data('page');
191 // the negative value of the content element after where it should be moved
192 targetPid = 0 - parseInt(targetFound, 10);
195 const language = parseInt($droppableElement.closest('[data-language-uid]').data('language-uid'), 10);
196 let colPos: number | boolean = 0;
197 if (targetPid !== 0) {
200 parameters.cmd = {tt_content: {}};
201 parameters.data = {tt_content: {}};
203 const copyAction = (evt && (<JQueryInputEventObject>evt.originalEvent).ctrlKey || $droppableElement.hasClass('t3js-paste-copy'));
205 parameters.cmd.tt_content[contentElementUid] = {
211 sys_language_uid: language
215 // TODO Make sure we actually have a JQuery object here, not only cast it
216 DragDrop.ajaxAction($droppableElement, <JQuery>$draggableElement, parameters, copyAction, $pasteAction);
218 parameters.data.tt_content[contentElementUid] = {
220 sys_language_uid: language
225 paste: 'tt_content|' + targetPid,
228 sys_language_uid: language
233 parameters.cmd.tt_content[contentElementUid] = {move: targetPid};
235 // fire the request, and show a message if it has failed
236 DragDrop.ajaxAction($droppableElement, <JQuery>$draggableElement, parameters, copyAction, $pasteAction);
242 * this method does the actual AJAX request for both, the move and the copy action.
244 * @param $droppableElement
245 * @param $draggableElement
248 * @param $pasteAction
251 public static ajaxAction($droppableElement: JQuery, $draggableElement: JQuery, parameters: Parameters,
252 $copyAction: boolean, $pasteAction: boolean): void {
253 DataHandler.process(parameters).done(function(result: ResponseInterface): void {
254 if (result.hasErrors) {
258 // insert draggable on the new position
260 if (!$droppableElement.parent().hasClass(DragDrop.contentIdentifier.substring(1))) {
261 $draggableElement.detach().css({top: 0, left: 0})
262 .insertAfter($droppableElement.closest(DragDrop.dropZoneIdentifier));
264 $draggableElement.detach().css({top: 0, left: 0})
265 .insertAfter($droppableElement.closest(DragDrop.contentIdentifier));
268 self.location.reload(true);
273 * returns the next "upper" container colPos parameter inside the code
275 * @return int|null the colPos
277 public static getColumnPositionForElement($element: JQuery): number | boolean {
278 const $columnContainer = $element.closest('[data-colpos]');
279 if ($columnContainer.length && $columnContainer.data('colpos') !== 'undefined') {
280 return $columnContainer.data('colpos');
287 export default DragDrop;
289 $(DragDrop.initialize);