From 3274c2c116d342cf1ee800fdfa5ca45b14e86646 Mon Sep 17 00:00:00 2001
From: Jo Hasenau <info@cybercraft.de>
Date: Fri, 4 Mar 2016 15:48:45 +0100
Subject: [PATCH] [TASK] Switched D&D back from sortable to draggable and
 adjusted dropzones

Resolves: #66540
Releases: master
Change-Id: I982bb63a14409fd4510126d3d4b88ee85562967e
Reviewed-on: https://review.typo3.org/47058
Reviewed-by: Marius Wieschollek <marius.typo3@mdns.eu>
Tested-by: Marius Wieschollek <marius.typo3@mdns.eu>
Reviewed-by: Jan Helke <typo3@helke.de>
Tested-by: Jan Helke <typo3@helke.de>
Reviewed-by: Bjoern Jacob <bjoern.jacob@tritum.de>
Tested-by: Bjoern Jacob <bjoern.jacob@tritum.de>
Reviewed-by: Karthikeyan Palaniswamy <karthikeyan@forethought.de>
Tested-by: Karthikeyan Palaniswamy <karthikeyan@forethought.de>
Reviewed-by: Daniel Goerz <ervaude@gmail.com>
Tested-by: Daniel Goerz <ervaude@gmail.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
---
 .../Public/Less/TYPO3/_module_web_page.less   |  26 +-
 .../JavaScript/LayoutModule/DragDrop.js       | 255 ++++++++++--------
 .../t3skin/Resources/Public/Css/backend.css   |  23 +-
 3 files changed, 185 insertions(+), 119 deletions(-)

diff --git a/Build/Resources/Public/Less/TYPO3/_module_web_page.less b/Build/Resources/Public/Less/TYPO3/_module_web_page.less
index 59847cdcc5fd..536ff6beed45 100644
--- a/Build/Resources/Public/Less/TYPO3/_module_web_page.less
+++ b/Build/Resources/Public/Less/TYPO3/_module_web_page.less
@@ -27,7 +27,8 @@
 @page-ce-dropzone-bg: @state-success-bg;
 @page-ce-dropzone-border: @state-success-border;
 @page-ce-dropzone-border-radius: @page-ce-border-radius;
-
+@page-ce-dropzone-possible-bg: @state-warning-bg;
+@page-ce-dropzone-possible-border: @state-warning-border;
 
 //
 // Grid
@@ -214,17 +215,28 @@
 //
 // Dropzone
 //
-.t3-page-ce-dropzone-available.active,
-.t3-page-ce-dropzone-possible {
+.t3-page-ce-dropzone-available.active {
+	border: 1px dashed @page-ce-dropzone-possible-border;
+	border-radius: @page-ce-dropzone-border-radius;
+background-color: @page-ce-dropzone-possible-bg;
+	height: 27px;
+}
+.t3-page-ce-dropzone-available.active.t3-page-ce-dropzone-possible {
 	border: 1px dashed @page-ce-dropzone-border;
 	border-radius: @page-ce-dropzone-border-radius;
 	background-color: @page-ce-dropzone-bg;
+	margin: -38px 0 -37px 0;
+	padding: 50px 10px;
+	z-index: 500;
+	position: relative;
+	opacity: 0.65;
 }
-.t3-page-ce-dropzone-available.active {
-	height: 2em;
+.t3-page-ce-dragitem.dragitem-shadow {
+	opacity: 0.65;
+	box-shadow: 0 1px 24px rgba(0, 0, 0, 0.50);
 }
-.t3-page-ce-dropzone-possible {
-	margin: @page-grid-spacing;
+.t3-page-ce.ui-draggable-handle {
+	height: auto!important;
 }
 
 
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/LayoutModule/DragDrop.js b/typo3/sysext/backend/Resources/Public/JavaScript/LayoutModule/DragDrop.js
index 7d26a4a5dee4..f914248279f6 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/LayoutModule/DragDrop.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/LayoutModule/DragDrop.js
@@ -16,161 +16,204 @@
  * this JS code does the drag+drop logic for the Layout module (Web => Page)
  * based on jQuery UI
  */
-define(['jquery', 'jquery-ui/sortable'], function ($) {
+define(['jquery', 'jquery-ui/droppable'], function ($) {
 	'use strict';
 
 	/**
 	 *
-	 * @type {{contentIdentifier: string, dragIdentifier: string, dropZoneAvailableIdentifier: string, dropPossibleClass: string, sortableItemsIdentifier: string, columnIdentifier: string, columnHolderIdentifier: string, addContentIdentifier: string, langClassPrefix: string}}
+	 * @type {{contentIdentifier: string, dragIdentifier: string, dragHeaderIdentifier: string, dropZoneIdentifier: string, columnIdentifier: string, validDropZoneClass: string, dropPossibleHoverClass: string, addContentIdentifier: string}}
 	 * @exports TYPO3/CMS/Backend/LayoutModule/DragDrop
 	 */
 	var DragDrop = {
 		contentIdentifier: '.t3js-page-ce',
-		dragIdentifier: '.t3js-page-ce-draghandle',
-		dropZoneAvailableIdentifier: '.t3js-page-ce-dropzone-available',
-		dropPossibleClass: 't3-page-ce-dropzone-possible',
-		sortableItemsIdentifier: '.t3js-page-ce-sortable',
+		dragIdentifier: '.t3-page-ce-dragitem',
+		dragHeaderIdentifier: '.t3js-page-ce-draghandle',
+		dropZoneIdentifier: '.t3js-page-ce-dropzone-available',
 		columnIdentifier: '.t3js-page-column',
-		columnHolderIdentifier: '.t3js-page-columns',
+		validDropZoneClass: 'active',
+		dropPossibleHoverClass: 't3-page-ce-dropzone-possible',
 		addContentIdentifier: '.t3js-page-new-ce',
-		langClassPrefix: '.t3js-sortable-lang-'
+		clone: true
 	};
 
 	/**
 	 * initializes Drag+Drop for all content elements on the page
 	 */
 	DragDrop.initialize = function() {
-		$('td[data-language-uid]').each(function() {
-			var connectWithClassName = DragDrop.langClassPrefix + $(this).data('language-uid');
-			$(connectWithClassName).sortable({
-				items: DragDrop.sortableItemsIdentifier,
-				connectWith: connectWithClassName,
-				handle: DragDrop.dragIdentifier,
-				distance: 20,
-				cursor: 'move',
-				helper: 'clone',
-				placeholder: DragDrop.dropPossibleClass,
-				tolerance: 'pointer',
-				start: function(e, ui) {
-					DragDrop.onSortStart($(this), ui);
-					$(this).addClass('t3-is-dragged');
-				},
-				stop: function(e, ui) {
-					DragDrop.onSortStop($(this), ui);
-					$(this).removeClass('t3-is-dragged');
-				},
-				change: function(e, ui) {
-					DragDrop.onSortChange($(this), ui);
-				},
-				update: function(e, ui) {
-					if (this === ui.item.parent()[0]) {
-						DragDrop.onSortUpdate($(this), ui);
-					}
-				}
-			}).disableSelection();
+		$(DragDrop.contentIdentifier).draggable({
+			handle: this.dragHeaderIdentifier,
+			scope: 'tt_content',
+			cursor: 'move',
+			distance: 20,
+			addClasses: 'active-drag',
+			revert: 'invalid',
+			zIndex: 100,
+			start: function (evt, ui) {
+				DragDrop.onDragStart($(this));
+			},
+			stop: function (evt, ui) {
+				DragDrop.onDragStop($(this));
+			}
+		});
+
+		$(DragDrop.dropZoneIdentifier).droppable({
+			accept: this.contentIdentifier,
+			scope: 'tt_content',
+			tolerance: 'pointer',
+			over: function (evt, ui) {
+				DragDrop.onDropHoverOver($(ui.draggable), $(this));
+			},
+			out: function (evt, ui) {
+				DragDrop.onDropHoverOut($(ui.draggable), $(this));
+			},
+			drop: function (evt, ui) {
+				DragDrop.onDrop($(ui.draggable), $(this), evt);
+			}
 		});
 	};
 
 	/**
-	 * Called when an item is about to be moved
-	 *
-	 * @param {Object} $container
-	 * @param {Object} ui
+	 * called when a draggable is selected to be moved
+	 * @param $element a jQuery object for the draggable
+	 * @private
 	 */
-	DragDrop.onSortStart = function($container, ui) {
-		var $item = $(ui.item),
-			$helper = $(ui.helper),
-			$placeholder = $(ui.placeholder);
-
-		$placeholder.height($item.height() - $helper.find(DragDrop.addContentIdentifier).height());
-		DragDrop.changeDropzoneVisibility($container, $item);
-
-		// show all dropzones, except the own
-		$helper.find(DragDrop.dropZoneAvailableIdentifier).removeClass('active');
-		$container.parents(DragDrop.columnHolderIdentifier).find(DragDrop.addContentIdentifier).hide();
+	DragDrop.onDragStart = function ($element) {
+		// Add css class for the drag shadow
+		$element.children(DragDrop.dragIdentifier).addClass('dragitem-shadow');
+		// Hide create new element button
+		$element.children(DragDrop.dropZoneIdentifier).addClass('drag-start');
+		$element.closest(DragDrop.columnIdentifier).removeClass('active');
+
+		$element.parents(DragDrop.columnHolderIdentifier).find(DragDrop.addContentIdentifier).hide();
+		$element.find(DragDrop.dropZoneIdentifier).hide();
+
+		// make the drop zones visible (all except the previous one in the current list)
+		var $previousDropZone = $element.prev().children(DragDrop.dropZoneIdentifier);
+		$(DragDrop.dropZoneIdentifier).not($previousDropZone).each(function () {
+			if (
+				$(this).parent().find('.icon-actions-document-new').length
+			) {
+				$(this).addClass(DragDrop.validDropZoneClass);
+			} else {
+				$(this).closest(DragDrop.contentIdentifier).find('> ' + DragDrop.addContentIdentifier + ', > > ' + DragDrop.addContentIdentifier).show();
+			}
+		});
 	};
 
 	/**
-	 * Called when the sorting stopped
-	 *
-	 * @param {Object} $container
-	 * @param {Object} ui
+	 * called when a draggable is released
+	 * @param $element a jQuery object for the draggable
+	 * @private
 	 */
-	DragDrop.onSortStop = function($container, ui) {
-		var $allColumns = $container.parents(DragDrop.columnHolderIdentifier);
-		$allColumns.find(DragDrop.addContentIdentifier).show();
-		$allColumns.find(DragDrop.dropZoneAvailableIdentifier + '.active').removeClass('active');
+	DragDrop.onDragStop = function ($element) {
+		// Remove css class for the drag shadow
+		$element.children(DragDrop.dragIdentifier).removeClass('dragitem-shadow');
+		// Show create new element button
+		$element.children(DragDrop.dropZoneIdentifier).removeClass('drag-start');
+		$element.closest(DragDrop.columnIdentifier).addClass('active');
+		$element.parents(DragDrop.columnHolderIdentifier).find(DragDrop.addContentIdentifier).show();
+		$element.find(DragDrop.dropZoneIdentifier).show();
+		$(DragDrop.dropZoneIdentifier + '.' + DragDrop.validDropZoneClass).removeClass(DragDrop.validDropZoneClass);
 	};
 
 	/**
-	 * Called when the index of the element in the sortable list has changed
-	 *
-	 * @param {Object} $container
-	 * @param {Object} ui
+	 * adds CSS classes when hovering over a dropzone
+	 * @param $draggableElement
+	 * @param $droppableElement
+	 * @private
 	 */
-	DragDrop.onSortChange = function($container, ui) {
-		var $placeholder = $(ui.placeholder);
-		DragDrop.changeDropzoneVisibility($container, $placeholder);
+	DragDrop.onDropHoverOver = function ($draggableElement, $droppableElement) {
+		if ($droppableElement.hasClass(DragDrop.validDropZoneClass)) {
+			$droppableElement.addClass(DragDrop.dropPossibleHoverClass);
+		}
 	};
 
 	/**
-	 *
-	 * @param {Object} $container
-	 * @param {Object} $subject
+	 * removes the CSS classes after hovering out of a dropzone again
+	 * @param $draggableElement
+	 * @param $droppableElement
+	 * @private
 	 */
-	DragDrop.changeDropzoneVisibility = function($container, $subject) {
-		var $prev = $subject.prev(':visible'),
-			droppableClassName = DragDrop.langClassPrefix + $container.data('language-uid');
-
-		if ($prev.length === 0) {
-			$prev = $subject.prevUntil(':visible').last().prev();
-		}
-		$container.parents(DragDrop.columnHolderIdentifier).find(droppableClassName).find(DragDrop.contentIdentifier + ':not(.ui-sortable-helper)').not($prev).find(DragDrop.dropZoneAvailableIdentifier).addClass('active');
-		$prev.find(DragDrop.dropZoneAvailableIdentifier + '.active').removeClass('active');
+	DragDrop.onDropHoverOut = function ($draggableElement, $droppableElement) {
+		$droppableElement.removeClass(DragDrop.dropPossibleHoverClass);
 	};
 
 	/**
-	 * Called when the new position of the element gets stored
+	 * this method does the whole logic when a draggable is dropped on to a dropzone
+	 * sending out the request and afterwards move the HTML element in the right place.
 	 *
-	 * @param {Object} $container
-	 * @param {Object} ui
+	 * @param $draggableElement
+	 * @param $droppableElement
+	 * @private
 	 */
-	DragDrop.onSortUpdate = function($container, ui) {
-		var $selectedItem = $(ui.item),
-			contentElementUid = parseInt($selectedItem.data('uid')),
-			parameters = {};
+	DragDrop.onDrop = function ($draggableElement, $droppableElement, evt) {
+		var newColumn = DragDrop.getColumnPositionForElement($droppableElement);
+
+		$droppableElement.removeClass(DragDrop.dropPossibleHoverClass);
+		var $pasteAction = typeof $draggableElement === 'number';
 
 		// send an AJAX requst via the AjaxDataHandler
+		var contentElementUid = $pasteAction ? $draggableElement : parseInt($draggableElement.data('uid'));
 		if (contentElementUid > 0) {
-
+			var parameters = {};
 			// add the information about a possible column position change
+			var targetFound = $droppableElement.closest(DragDrop.contentIdentifier).data('uid');
+			// the item was moved to the top of the colPos, so the page ID is used here
+			var targetPid = 0;
+			if (typeof targetFound === 'undefined') {
+				// the actual page is needed
+				targetPid = $('[data-page]').first().data('page');
+			} else {
+				// the negative value of the content element after where it should be moved
+				targetPid = 0 - parseInt(targetFound);
+			}
+			var language = parseInt($droppableElement.closest('[data-language-uid]').data('language-uid'));
+			var colPos = 0;
+			if (targetPid !== 0) {
+				colPos = newColumn;
+			}
+			parameters['cmd'] = {tt_content: {}};
 			parameters['data'] = {tt_content: {}};
-			parameters['data']['tt_content'][contentElementUid] = {colPos: parseInt($container.data('colpos'))};
+			parameters['data']['tt_content'][contentElementUid] = {
+				colPos: colPos,
+				sys_language_uid: language
+			};
+			parameters['cmd']['tt_content'][contentElementUid] = {move: targetPid};
+			// fire the request, and show a message if it has failed
+			require(['TYPO3/CMS/Backend/AjaxDataHandler'], function (DataHandler) {
+				DataHandler.process(parameters).done(function (result) {
+					if (!result.hasErrors) {
+						// insert draggable on the new position
+						if (!$droppableElement.parent().hasClass(DragDrop.contentIdentifier.substring(1))) {
+							$draggableElement.detach().css({top: 0, left: 0})
+								.insertAfter($droppableElement.closest(DragDrop.dropZoneIdentifier));
+						} else {
+							$draggableElement.detach().css({top: 0, left: 0})
+								.insertAfter($droppableElement.closest(DragDrop.contentIdentifier));
+						}
+						if ($('.t3js-page-lang-column').length) {
+							self.location.reload(true);
+						}
+					}
+				});
+			});
 		}
+	};
 
-		var targetContentElementUid = $selectedItem.prev().data('uid');
-		// the item was moved to the top of the colPos, so the page ID is used here
-		if (typeof targetContentElementUid === 'undefined') {
-			// the actual page is needed
-			targetContentElementUid = parseInt($container.find(DragDrop.contentIdentifier).first().data('page'));
+	/**
+	 * returns the next "upper" container colPos parameter inside the code
+	 * @param $element
+	 * @return int|null the colPos
+	 */
+	DragDrop.getColumnPositionForElement = function ($element) {
+		var $columnContainer = $element.closest('[data-colpos]');
+		if ($columnContainer.length && $columnContainer.data('colpos') !== 'undefined') {
+			return $columnContainer.data('colpos');
 		} else {
-			// the negative value of the content element after where it should be moved
-			targetContentElementUid = parseInt(targetContentElementUid) * -1;
+			return false;
 		}
-
-		parameters['cmd'] = {tt_content: {}};
-		parameters['cmd']['tt_content'][contentElementUid] = {move: targetContentElementUid};
-		// fire the request, and show a message if it has failed
-		require(['TYPO3/CMS/Backend/AjaxDataHandler'], function(DataHandler) {
-			DataHandler.process(parameters).done(function(result) {
-				if (result.hasErrors) {
-					$container.sortable('cancel');
-				}
-			});
-		});
 	};
 
 	$(DragDrop.initialize);
-
 	return DragDrop;
 });
diff --git a/typo3/sysext/t3skin/Resources/Public/Css/backend.css b/typo3/sysext/t3skin/Resources/Public/Css/backend.css
index 3a372a3487af..04013244e9b0 100644
--- a/typo3/sysext/t3skin/Resources/Public/Css/backend.css
+++ b/typo3/sysext/t3skin/Resources/Public/Css/backend.css
@@ -12253,17 +12253,28 @@ iframe {
 .t3-page-ce-hidden:hover {
   opacity: 1.0;
 }
-.t3-page-ce-dropzone-available.active,
-.t3-page-ce-dropzone-possible {
+.t3-page-ce-dropzone-available.active {
+  border: 1px dashed #e8a33d;
+  border-radius: 2px;
+  background-color: #fbefdd;
+  height: 27px;
+}
+.t3-page-ce-dropzone-available.active.t3-page-ce-dropzone-possible {
   border: 1px dashed #79a548;
   border-radius: 2px;
   background-color: #d1e2bd;
+  margin: -38px 0 -37px 0;
+  padding: 50px 10px;
+  z-index: 500;
+  position: relative;
+  opacity: 0.65;
 }
-.t3-page-ce-dropzone-available.active {
-  height: 2em;
+.t3-page-ce-dragitem.dragitem-shadow {
+  opacity: 0.65;
+  box-shadow: 0 1px 24px rgba(0, 0, 0, 0.5);
 }
-.t3-page-ce-dropzone-possible {
-  margin: 10px;
+.t3-page-ce.ui-draggable-handle {
+  height: auto!important;
 }
 .t3-page-ce-dragitem .t3-page-ce-header-draggable:hover {
   cursor: move;
-- 
GitLab