Skip to content
Snippets Groups Projects
Select Git revision
  • 3352059-string-previous-revision
  • 8.x-1.x default
  • 7.x-1.x
  • 8.x-1.15
  • 8.x-1.14
  • 8.x-1.13
  • 8.x-1.12
  • 8.x-1.11
  • 8.x-1.10
  • 8.x-1.9
  • 8.x-1.8
  • 8.x-1.7
  • 8.x-1.6
  • 8.x-1.5
  • 8.x-1.4
  • 8.x-1.3
  • 8.x-1.2
  • 8.x-1.1
  • 7.x-1.0-rc5
  • 8.x-1.0
  • 8.x-1.0-rc5
  • 8.x-1.0-rc4
  • 7.x-1.0-rc4
23 results

paragraphs.dragdrop.js

Blame
  • sasanikolic's avatar
    Issue #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
    dd0d13e2
    History
    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);