javascriptdragula

Dragula: How to always move an item to end of list


I am creating a drag and drop page using Dragula. It works very well. But in my case, I need move the item to the end of the list. Always.

This is my Javascript Code

dragula([
        document.getElementById('left'),
        document.getElementById('right')
    ])

    .on('drag', function (el) {

        // add 'is-moving' class to element being dragged
        el.classList.add('is-moving');
        console.log(el.classList);
    })
    .on('dragend', function (el) {

        // remove 'is-moving' class from element after dragging has stopped
        el.classList.remove('is-moving');

        // add the 'is-moved' class for 600ms then remove it
        window.setTimeout(function () {
            el.classList.add('is-moved');
            window.setTimeout(function () {
                el.classList.remove('is-moved');
            }, 600);
        }, 100);
    });


var createOptions = (function () {
    var dragOptions = document.querySelectorAll('.drag-options');

    // these strings are used for the checkbox labels
    var options = ['Research', 'Strategy', 'Inspiration', 'Execution'];

    // create the checkbox and labels here, just to keep the html clean. append the <label> to '.drag-options'
    function create() {
        for (var i = 0; i < dragOptions.length; i++) {

            options.forEach(function (item) {
                var checkbox = document.createElement('input');
                var label = document.createElement('label');
                var span = document.createElement('span');
                checkbox.setAttribute('type', 'checkbox');
                span.innerHTML = item;
                label.appendChild(span);
                label.insertBefore(checkbox, label.firstChild);
                label.classList.add('drag-options-label');
                dragOptions[i].appendChild(label);
            });

        }
    }

    return {
        create: create
    }


}());

var showOptions = (function () {

    // the 3 dot icon
    var more = document.querySelectorAll('.drag-header-more');

    function show() {
        // show 'drag-options' div when the more icon is clicked
        var target = this.getAttribute('data-target');
        var options = document.getElementById(target);
        options.classList.toggle('active');
    }


    function init() {
        for (i = 0; i < more.length; i++) {
            more[i].addEventListener('click', show, false);
        }
    }

    return {
        init: init
    }
}());

var leftList = document.querySelector('#left');
var rightList = document.querySelector('#right');
var list = document.querySelectorAll('#right li, #left li');
var itemMoving = undefined;
for (var i = 0; i < list.length; i++) {
    list[i].addEventListener('click', function () {
        if (this.parentNode.id == 'right') {
            itemMoving = this;
            leftList.appendChild(this);
        } else {
            itemMoving = this;
            rightList.appendChild(this);
        }

        // add the 'is-moved' class for 600ms then remove it
        window.setTimeout(function () {
            itemMoving.classList.add('is-moved');
            window.setTimeout(function () {
                itemMoving.classList.remove('is-moved');
            }, 600);
        }, 100);

    });
}

createOptions.create();
showOptions.init();

This is my running code On Code Pen


Solution

  • You can hook into dragula's shadow event like this, forcing the shadow copy of the dragged element to be appended to the container:

    .on('shadow', function (el, container, source) {
      // check if the shadow copy is not already the last child of the container
      if (el !== container.children[container.children.length-1]) {
        // otherwise: make it so
        container.appendChild(el);
      }
    })
    

    Working example (best viewed fullscreen):

    dragula([
        document.getElementById('left'),
        document.getElementById('right')
      ])
    
      .on('drag', function(el) {
    
        // add 'is-moving' class to element being dragged
        el.classList.add('is-moving');
      })
      .on('shadow', function(el, container, source) {
        if (el !== container.children[container.children.length - 1]) {
          container.appendChild(el);
        }
      })
      .on('dragend', function(el) {
    
        // remove 'is-moving' class from element after dragging has stopped
        el.classList.remove('is-moving');
    
        // add the 'is-moved' class for 600ms then remove it
        window.setTimeout(function() {
          el.classList.add('is-moved');
          window.setTimeout(function() {
            el.classList.remove('is-moved');
          }, 600);
        }, 100);
      });
    
    
    var createOptions = (function() {
      var dragOptions = document.querySelectorAll('.drag-options');
    
      // these strings are used for the checkbox labels
      var options = ['Research', 'Strategy', 'Inspiration', 'Execution'];
    
      // create the checkbox and labels here, just to keep the html clean. append the <label> to '.drag-options'
      function create() {
        for (var i = 0; i < dragOptions.length; i++) {
    
          options.forEach(function(item) {
            var checkbox = document.createElement('input');
            var label = document.createElement('label');
            var span = document.createElement('span');
            checkbox.setAttribute('type', 'checkbox');
            span.innerHTML = item;
            label.appendChild(span);
            label.insertBefore(checkbox, label.firstChild);
            label.classList.add('drag-options-label');
            dragOptions[i].appendChild(label);
          });
    
        }
      }
    
      return {
        create: create
      }
    
    
    }());
    
    var showOptions = (function() {
    
      // the 3 dot icon
      var more = document.querySelectorAll('.drag-header-more');
    
      function show() {
        // show 'drag-options' div when the more icon is clicked
        var target = this.getAttribute('data-target');
        var options = document.getElementById(target);
        options.classList.toggle('active');
      }
    
    
      function init() {
        for (i = 0; i < more.length; i++) {
          more[i].addEventListener('click', show, false);
        }
      }
    
      return {
        init: init
      }
    }());
    
    var leftList = document.querySelector('#left');
    var rightList = document.querySelector('#right');
    var list = document.querySelectorAll('#right li, #left li');
    var itemMoving = undefined;
    for (var i = 0; i < list.length; i++) {
      list[i].addEventListener('click', function() {
        if (this.parentNode.id == 'right') {
          itemMoving = this;
          leftList.appendChild(this);
        } else {
          itemMoving = this;
          rightList.appendChild(this);
        }
    
        // add the 'is-moved' class for 600ms then remove it
        window.setTimeout(function() {
          itemMoving.classList.add('is-moved');
          window.setTimeout(function() {
            itemMoving.classList.remove('is-moved');
          }, 600);
        }, 100);
    
      });
    }
    
    createOptions.create();
    showOptions.init();
    * {
      box-sizing: border-box;
    }
    
    body {
      background: #33363D;
      color: white;
      font-family: 'Lato';
      font-weight: 300;
      line-height: 1.5;
      -webkit-font-smoothing: antialiased;
    }
    
    ul {
      list-style-type: none;
      margin: 0;
      padding: 0;
    }
    
    .drag-container {
      max-width: 1000px;
      margin: 20px auto;
    }
    
    .drag-list {
      display: flex;
      align-items: flex-start;
    }
    
    @media (max-width: 690px) {
      .drag-list {
        display: block;
      }
    }
    
    .drag-column {
      flex: 1;
      margin: 0 10px;
      position: relative;
      background: rgba(0, 0, 0, 0.2);
      overflow: hidden;
    }
    
    @media (max-width: 690px) {
      .drag-column {
        margin-bottom: 30px;
      }
    }
    
    .drag-column h2 {
      font-size: 0.8rem;
      margin: 0;
      text-transform: uppercase;
      font-weight: 600;
    }
    
    .drag-column-on-hold .drag-column-header,
    .drag-column-on-hold .is-moved,
    .drag-column-on-hold .drag-options {
      background: #FB7D44;
    }
    
    .drag-column-in-progress .drag-column-header,
    .drag-column-in-progress .is-moved,
    .drag-column-in-progress .drag-options {
      background: #2A92BF;
    }
    
    .drag-column-needs-review .drag-column-header,
    .drag-column-needs-review .is-moved,
    .drag-column-needs-review .drag-options {
      background: #F4CE46;
    }
    
    .drag-column-approved .drag-column-header,
    .drag-column-approved .is-moved,
    .drag-column-approved .drag-options {
      background: #00B961;
    }
    
    .drag-column-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 10px;
    }
    
    .drag-inner-list {
      min-height: 50px;
    }
    
    .drag-item {
      margin: 10px;
      height: 100px;
      background: rgba(0, 0, 0, 0.4);
      transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
    }
    
    .drag-item.is-moving {
      -webkit-transform: scale(1.5);
      transform: scale(1.5);
      background: rgba(0, 0, 0, 0.8);
    }
    
    .drag-header-more {
      cursor: pointer;
    }
    
    .drag-options {
      position: absolute;
      top: 44px;
      left: 0;
      width: 100%;
      height: 100%;
      padding: 10px;
      -webkit-transform: translateX(100%);
      transform: translateX(100%);
      opacity: 0;
      transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
    }
    
    .drag-options.active {
      -webkit-transform: translateX(0);
      transform: translateX(0);
      opacity: 1;
    }
    
    .drag-options-label {
      display: block;
      margin: 0 0 5px 0;
    }
    
    .drag-options-label input {
      opacity: 0.6;
    }
    
    .drag-options-label span {
      display: inline-block;
      font-size: 0.9rem;
      font-weight: 400;
      margin-left: 5px;
    }
    
    
    /* Dragula CSS  */
    
    .gu-mirror {
      position: fixed !important;
      margin: 0 !important;
      z-index: 9999 !important;
      opacity: 0.8;
      list-style-type: none;
    }
    
    .gu-hide {
      display: none !important;
    }
    
    .gu-unselectable {
      -webkit-user-select: none !important;
      -moz-user-select: none !important;
      -ms-user-select: none !important;
      user-select: none !important;
    }
    
    .gu-transit {
      opacity: 0.2;
    }
    
    
    /* Demo info */
    
    .section {
      padding: 20px;
      text-align: center;
    }
    
    .section a {
      color: white;
      text-decoration: none;
      font-weight: 300;
    }
    
    .section h4 {
      font-weight: 400;
    }
    
    .section h4 a {
      font-weight: 600;
    }
    
    .imgProfile {
      position: relative;
      left: 4px;
      top: 10px;
      height: 50px;
      width: 50px;
      border-radius: 50%;
    }
    
    .nomeProfile {
      position: relative;
      left: 66px;
      top: -47px;
      height: 44px;
    }
    <div class="drag-container">
      <ul class="drag-list">
    
        <li class="drag-column drag-column-approved">
          <span class="drag-column-header">
    					<h2>Disponível</h2>
    				</span>
          <div class="drag-options" id="options4"></div>
          <ul class="drag-inner-list" id="left">
            <li class="drag-item">
              <img class="imgProfile" src="https://storage.googleapis.com/montu-bucket/00_base/base_img_avatar.png" alt="Avatar">
              <h4 class="nomeProfile">User A</h4>
            </li>
            <li class="drag-item">
              <img class="imgProfile" src="https://storage.googleapis.com/montu-bucket/00_base/base_img_avatar.png" alt="Avatar">
              <h4 class="nomeProfile">User B</h4>
            </li>
            <li class="drag-item">
              <img class="imgProfile" src="https://storage.googleapis.com/montu-bucket/00_base/base_img_avatar.png" alt="Avatar">
              <h4 class="nomeProfile">User C</h4>
            </li>
            <li class="drag-item">
              <img class="imgProfile" src="https://storage.googleapis.com/montu-bucket/00_base/base_img_avatar.png" alt="Avatar">
              <h4 class="nomeProfile">User D</h4>
            </li>
          </ul>
        </li>
    
        <li class="drag-column drag-column-on-hold">
          <span class="drag-column-header">
    					<h2>Em Atendimento</h2>
    				</span>
    
          <div class="drag-options" id="options1"></div>
    
          <ul class="drag-inner-list" id="right">
          </ul>
        </li>
      </ul>
    </div>
    
    <script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/45226/dragula.min.js"></script>

    In order to keep the shadow copy in the dynamic position and only append the item after drop, you can simply change shadow to drop, but from a user experience point of view, the visual aid should represent the real final position.