3 * Paragraphs drag and drop handling and integration with the Sortable library.
6 (function ($, Drupal) {
11 * jQuery plugin for Sortable
13 * Registers Sortable under a custom name to prevent a collision with jQuery
16 * @param {Object|String} options
20 $.fn.paragraphsSortable = function (options) {
24 this.each(function () {
26 sortable = $el.data('sortable');
28 if (!sortable && (options instanceof Object || !options)) {
29 sortable = new Sortable(this, options);
30 $el.data('sortable', sortable);
34 if (options === 'widget') {
37 else if (options === 'destroy') {
39 $el.removeData('sortable');
41 else if (typeof sortable[options] === 'function') {
42 retVal = sortable[options].apply(sortable, [].slice.call(args, 1));
44 else if (options in sortable.options) {
45 retVal = sortable.option.apply(sortable, args);
50 return (retVal === void 0) ? this : retVal;
54 Drupal.behaviors.paragraphsDraggable = {
55 attach: function (context) {
57 // Initialize drag and drop.
58 $('ul.paragraphs-dragdrop', context).each(function (i, item) {
59 $(item).paragraphsSortable({
62 handle: ".tabledrag-handle",
69 * Callback to update weight and path information.
74 function handleReorder(evt) {
75 var $item = $(evt.item);
76 var $parent = $item.closest('.paragraphs-dragdrop');
77 var $children = $parent.children('li');
78 var $srcParent = $(evt.to);
79 var $srcChildren = $srcParent.children('li');
81 // Update both the source and target children.
82 updateWeightsAndPath($srcChildren);
83 updateWeightsAndPath($children);
88 * Update weight and recursively update path of the provided paragraphs.
91 * Drag and drop items.
93 function updateWeightsAndPath($items) {
94 $items.each(function (index, value) {
96 // Update the weight in the weight of the current element, avoid
97 // matching child weights by selecting the first.
98 var $currentItem = $(value);
99 var $weight = $currentItem.find('.paragraphs-dragdrop__weight:first');
102 // Update the path of the current element and then update all nested
104 updatePaths($currentItem, $currentItem.parent());
105 $currentItem.find('> div > ul').each(function () {
106 updateNestedPath(this, index, $currentItem);
112 * Update the path field based on the parent.
117 * The parent of the list item.
119 function updatePaths($item, $parent) {
120 // Select the first path field which is the one from the current
122 var $pathField = $item.find('.paragraphs-dragdrop__path:first');
123 var newPath = $parent.attr('data-paragraphs-dragdrop-path');
124 $pathField.val(newPath);
128 * Update nested paragraphs for a field/list.
131 * The paragraph field/list, parent of the children to be updated.
133 * The index of the parent list item.
134 * @param $parentListItem
135 * The parent list item.
137 function updateNestedPath(childList, parentIndex, $parentListItem) {
139 var sortablePath = childList.getAttribute('data-paragraphs-dragdrop-path');
140 var newParent = $parentListItem.parent().attr('data-paragraphs-dragdrop-path');
142 // Update the data attribute of the list based on the parent index and
144 sortablePath = newParent + "][" + parentIndex + sortablePath.substr(sortablePath.lastIndexOf("]"));
145 childList.setAttribute('data-paragraphs-dragdrop-path', sortablePath);
147 // Now update the children.
148 $(childList).children().each(function (childIndex) {
149 var $childListItem = $(this);
150 updatePaths($childListItem, $(childList), childIndex);
151 $(this).find('> div > ul').each(function () {
152 var nestedChildList = this;
153 updateNestedPath(nestedChildList, childIndex, $childListItem);
160 * Callback to check if a paragraph item can be dropped into a position.
163 * The Sortable event.
164 * @param originalEvent
165 * The original Sortable event.
167 * @returns {boolean|*}
168 * True if the type is allowed and there is enough room.
170 function isAllowed(evt, originalEvent) {
171 var dragee = evt.dragged;
173 var drageeType = dragee.dataset.paragraphsDragdropBundle;
174 var allowedTypes = target.dataset.paragraphsDragdropAllowedTypes;
175 var hasSameContainer = evt.to === evt.from;
176 return hasSameContainer || (contains(drageeType, allowedTypes) && hasRoom(target));
180 * Checks if the target has room.
183 * The target list/paragraph field.
186 * True if the field is unlimited or limit is not reached yet.
188 function hasRoom(target) {
190 var cardinality = target.dataset.paragraphsDragdropCardinality;
191 var occupants = target.childNodes.length;
192 var isLimited = parseInt(cardinality, 10) !== -1;
193 var hasRoom = cardinality > occupants;
195 return hasRoom || !isLimited;
199 * Checks if the paragraph type is allowed in the target type list.
202 * The paragraph type.
204 * Comma separated list of target types.
207 * TRUE if the target type is allowed.
209 function contains(candidate, set) {
210 set = set.split(',');
213 for(var i = 0; i < l; i++) {
214 if(set[i] === candidate) {
221 // Fix for an iOS 10 bug. Binding empty event handler on the touchmove
223 window.addEventListener('touchmove', function () {