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