Select Git revision
paragraphs.dragdrop.js
Forked from
project / paragraphs
49 commits behind, 1 commit ahead of the upstream repository.

#3090023 by sasanikolic, Berdir, miro_dietiker: Last nested paragraph...
Saša Nikolič authored and
Sascha Grossenbacher
committed
Issue #3090023 by sasanikolic, Berdir, miro_dietiker: Last nested paragraph height issue with updated Sortable.js
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
paragraphs.dragdrop.js 8.50 KiB
/**
* @file
* Paragraphs drag and drop handling and integration with the Sortable library.
*/
(function ($, Drupal) {
'use strict';
/**
* jQuery plugin for Sortable
*
* Registers Sortable under a custom name to prevent a collision with jQuery
* UI.
*
* @param {Object|String} options
* @param {..*} [args]
* @returns {jQuery|*}
*/
$.fn.paragraphsSortable = function (options) {
var retVal,
args = arguments;
this.each(function () {
var $el = $(this),
sortable = $el.data('sortable');
if (!sortable && (options instanceof Object || !options)) {
sortable = new Sortable(this, options);
$el.data('sortable', sortable);
}
if (sortable) {
if (options === 'widget') {
return sortable;
}
else if (options === 'destroy') {
sortable.destroy();
$el.removeData('sortable');
}
else if (typeof sortable[options] === 'function') {
retVal = sortable[options].apply(sortable, [].slice.call(args, 1));
}
else if (options in sortable.options) {
retVal = sortable.option.apply(sortable, args);
}
}
});
return (retVal === void 0) ? this : retVal;
};
Drupal.behaviors.paragraphsDraggable = {
attach: function (context) {
// Prevent default click handling on the drag handle.
$('.paragraphs-dragdrop__handle', context).on('click', function (event) {
event.preventDefault();
});
// Initialize drag and drop.
$('ul.paragraphs-dragdrop__list', context).each(function (i, item) {
$(item).paragraphsSortable({
group: "paragraphs",
sort: true,
handle: ".paragraphs-dragdrop__handle",
onMove: isAllowed,
onEnd: function(evt) {
handleReorder(evt);
endDragClasses();
},
onStart: startDragClasses,
});
});
/**
* Callback to update weight and path information.
*
* @param evt
* The Sortable event.
*/
function handleReorder(evt) {
// Update both the source and target children.
if (evt.from === evt.to) {
updateWeightsAndPath($(evt.to).children('li'));
}
else {
updateWeightsAndPath($(evt.from).children('li'));
updateWeightsAndPath($(evt.to).children('li'));
}
endDragClasses();
}
/**
* Update weight and recursively update path of the provided paragraphs.
*
* @param $items
* Drag and drop items.
*/
function updateWeightsAndPath($items) {
$items.each(function (index, value) {
// Update the weight in the weight of the current element, avoid
// matching child weights by selecting the first.
var $currentItem = $(value);
var $weight = $currentItem.find('.paragraphs-dragdrop__weight:first');
$weight.val(index);
// Update the path of the current element and then update all nested
// elements.
updatePaths($currentItem, $currentItem.parent());
$currentItem.find('> div > ul').each(function () {
updateNestedPath(this, index, $currentItem);
});
})
}
/**
* Update the path field based on the parent.
*
* @param $item
* A list item.
* @param $parent
* The parent of the list item.
*/
function updatePaths($item, $parent) {
// Select the first path field which is the one from the current
// element.
var $pathField = $item.find('.paragraphs-dragdrop__path:first');
var newPath = $parent.attr('data-paragraphs-dragdrop-path');
$pathField.val(newPath);
}
/**
* Update nested paragraphs for a field/list.
*
* @param childList
* The paragraph field/list, parent of the children to be updated.
* @param parentIndex
* The index of the parent list item.
* @param $parentListItem
* The parent list item.
*/
function updateNestedPath(childList, parentIndex, $parentListItem) {
var sortablePath = childList.getAttribute('data-paragraphs-dragdrop-path');
var newParent = $parentListItem.parent().attr('data-paragraphs-dragdrop-path');
// Update the data attribute of the list based on the parent index and
// list item.
sortablePath = newParent + "][" + parentIndex + sortablePath.substr(sortablePath.lastIndexOf("]"));
childList.setAttribute('data-paragraphs-dragdrop-path', sortablePath);
// Now update the children.
$(childList).children().each(function (childIndex) {
var $childListItem = $(this);
updatePaths($childListItem, $(childList), childIndex);
$(this).find('> div > ul').each(function () {
var nestedChildList = this;
updateNestedPath(nestedChildList, childIndex, $childListItem);
});
});
}
/**
* Callback to check if a paragraph item can be dropped into a position.
*
* @param evt
* The Sortable event.
* @param originalEvent
* The original Sortable event.
*
* @returns {boolean|*}
* True if the type is allowed and there is enough room.
*/
function isAllowed(evt, originalEvent) {
var dragee = evt.dragged;
var target = evt.to;
var drageeType = dragee.dataset.paragraphsDragdropBundle;
var allowedTypes = target.dataset.paragraphsDragdropAllowedTypes;
var hasSameContainer = evt.to === evt.from;
var allowed = hasSameContainer || (contains(drageeType, allowedTypes) && hasRoom(target));
targetAllowedClasses(target, allowed);
return allowed;
}
/**
* Checks if the target has room.
*
* @param target
* The target list/paragraph field.
*
* @returns {boolean}
* True if the field is unlimited or limit is not reached yet.
*/
function hasRoom(target) {
var cardinality = target.dataset.paragraphsDragdropCardinality;
var occupants = target.childNodes.length;
var isLimited = parseInt(cardinality, 10) !== -1;
var hasRoom = cardinality > occupants;
return hasRoom || !isLimited;
}
/**
* Checks if the paragraph type is allowed in the target type list.
*
* @param candidate
* The paragraph type.
* @param set
* Comma separated list of target types.
*
* @returns {boolean}
* TRUE if the target type is allowed.
*/
function contains(candidate, set) {
set = set.split(',');
var l = set.length;
for(var i = 0; i < l; i++) {
if(set[i] === candidate) {
return true;
}
}
return false;
}
/**
* Provides a helper class indicating drag status on <html> element when
* dragging starts.
*/
function startDragClasses() {
$('html').addClass('is-dragging-paragraphs');
// Fix race condition when the drag event start results in a scrollbar
// position change triggered by a collapsing item with children. Add a
// min-height for the current height as a workaround.
$('.paragraphs-dragdrop__list').eq(0).css('min-height', function() {
return $(this).height();
});
}
/**
* Provides a helper class indicating a valid drop target via isAllowed().
*
* @param target
* The target list/paragraph field.
*
* @param {boolean} allowed
* TRUE if the target type is allowed.
*/
function targetAllowedClasses(target, allowed) {
$('.is-droppable-target').removeClass('is-droppable-target');
if (allowed) {
$(target).addClass('is-droppable-target');
}
}
/**
* Removes helper classes when dragging ends.
*/
function endDragClasses() {
$('html').removeClass('is-dragging-paragraphs');
$('.is-droppable-target').removeClass('is-droppable-target');
// Remove the custom min-height definition added in startDragClasses().
$('.paragraphs-dragdrop__list').eq(0).removeAttr('style');
}
// Fix for an iOS 10 bug. Binding empty event handler on the touchmove
// event.
window.addEventListener('touchmove', function () {
})
}
}
})(jQuery, Drupal);