Search code examples
javascripthtmlcssdrag-and-dropdraggable

Javascript Drag and Drop Hover Temporary Div Placeholder similar to Trello?


I have a basic drag and drop trello-like kanban board. You can drag tasks between different grey boxes. It uses HTML drag and drop API found here https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API.

var dropTarget = document.querySelector(".drop-target");
var draggables = document.querySelectorAll(".drag-task");

// Tells the other side what data is being passed (e.g. the ID is targeted)
draggables.forEach(item => {
  item.addEventListener("dragstart", function(ev){
    ev.dataTransfer.setData("srcId", ev.target.id);
  });
})
// The end destination, prevent browsers default drag and drop (disabling breaks feature)
// because it's disabled by browsers by default
dropTarget.addEventListener('dragover', function(ev) {
  ev.preventDefault();
});
// End destination where item is dropped into
dropTarget.addEventListener('drop', function(ev) {
  ev.preventDefault();
  let target = ev.target;
  let droppable  = target.classList.contains('drag-box');
  let srcId = ev.dataTransfer.getData("srcId");

  if (droppable) {
    ev.target.appendChild(document.getElementById(srcId));
  }
});
/***********DRAGGABLE BACKGROUND ****************/
.drag-box {
  background-color: lightgray;
  float: right;
  width: 120px;
  min-height: 50px;
  padding-bottom: 30px;
  height: auto;
  margin: 30px;
}
.drag-task {
  background-color: white;
  margin: 15px;
}
.drop-active {
  border: 1px dashed red;
}
<div class="drop-target">
    <div class="drag-box">
      <div class="drag-card">
        <div draggable="true" id="task1" class="drag-task">Test Card 1</div>
      </div>
      <div class="drag-card">
        <div draggable="true" id="task2" class="drag-task">Test Card 2</div>
      </div>
      <div class="drag-card">
        <div draggable="true" id="task3" class="drag-task">Test Card 3</div>
      </div>
    </div>
    <div class="drag-box">
    </div>
    <div class="drag-box">
    </div>
  </div>

What I want to do to achieve is an effect similar to this gif found here. This creates another <div> element on the same level as drag-card class on a draghover effect, and repositions itself accordingly.

enter image description here

I know I have to use dragover and dragleave event listeners but that's as far as I got. I added this code at the end of the file. I have never used drag event listeners so this is new to me.

var makeHoverElement= true;
dropTarget.addEventListener("dragover", function(ev){
  if(makeHoverElement){
    let newNode =document.createElement('div');
    newNode.className ='drop-active'
    ev.target.parentElement.prepend(newNode);
    makeHoverElement = false;
  }
});

dropTarget.addEventListener("dragleave", function(ev){
   // really I have no idea how to make this effect
});

Results so far have not turned out as I expected. Dragover is applying to element where the task item originated from

enter image description here


Solution

  • Using jquery and jquery UI, I did something quite like this a while ago. I didn't create a "make new card" function, I began with a "launchpad" and created two droppable areas that cards could be appended to and switched between - similar to what you have. Using "intersect" as I remember was a tipping point to getting it to work as I wanted - being able to move elements up and down the list (so they don't necessarily move back to where they originated). Perhaps it could be a starting point for you? Here's the fiddle (the jquery is old.. recommend updating to newer versions)

    Hope this helps.

    EDIT: I made a couple of small tweaks to your code to add an outline and change the cursor on move. According to a comment on another question, adding a border is the most efficient way to create the visual 'outline' effect. There is a longer way to create the 'sortable' effect which is demoed in this codepen I found, and explained simply, the function is based around calculating hover position and if the dragged element is half-way over an item in the list, the effect displays and the item can be dropped in between list items.

    Hope this is clear enough!

    // Tells the other side what data is being passed (e.g. the ID is targeted)
    var dropTarget = document.querySelector(".drop-target");
    var draggables = document.querySelectorAll(".drag-task");
    
    // Tells the other side what data is being passed (e.g. the ID is targeted)
    draggables.forEach(item => {
      item.addEventListener("dragstart", function(ev) {
        ev.dataTransfer.setData("srcId", ev.target.id);
      });
    })
    // The end destination, prevent browsers default drag and drop (disabling breaks feature)
    // because it's disabled by browsers by default
    dropTarget.addEventListener('dragover', function(ev) {
      ev.preventDefault();
    });
    // End destination where item is dropped into
    dropTarget.addEventListener('drop', function(ev) {
      ev.preventDefault();
      let target = ev.target;
      let droppable = target.classList.contains('drag-box');
      let srcId = ev.dataTransfer.getData("srcId");
    
      if (droppable) {
        ev.target.appendChild(document.getElementById(srcId));
      }
    });
    .drag-box {
      background-color: lightgray;
      float: left;
      width: 120px;
      min-height: 80px; /*lengthened the height slightly*/
      padding-bottom: 30px;
      height: auto;
      margin: 30px;
      cursor: move; /*added the 'cross' cursor*/
    }
    .drag-task {
      background-color: white;
      margin: 10px;
      padding: 5px; /*added padding to make tiles bigger*/
      border:1px dashed #000000; /*set an outline*/
    }
    
    .drop-active {
      border: 1px dashed red;
      cursor: pointer; /*change the pointer back to the default cursor while moving between lists*/
    }
    <div class="drop-target">
      <div class="drag-box">
        <div class="drag-card">
          <div draggable="true" id="task1" class="drag-task">Test Card 1</div>
        </div>
        <div class="drag-card">
          <div draggable="true" id="task2" class="drag-task">Test Card 2</div>
        </div>
        <div class="drag-card">
          <div draggable="true" id="task3" class="drag-task">Test Card 3</div>
        </div>
      </div>
      
      <!-- added tiles to the 2nd list (and deleted 3rd box)-->
      <div class="drag-box">
        <div class="drag-card">
          <div draggable="true" id="orange" class="drag-task">Orange</div>
        </div>
        <div class="drag-card">
          <div draggable="true" id="apple" class="drag-task">Apple</div>
        </div>
        <div class="drag-card">
          <div draggable="true" id="pear" class="drag-task">Pear</div>
        </div>
      </div>

    $("#launchPad").height($(window).height() - 20);
    var dropSpace = $(window).width() - $("#launchPad").width();
    $("#dropZone").width(dropSpace - 70);
    $("#dropZone").height($("#launchPad").height());
    
    $(".card").draggable({
        appendTo: "#launchPad",
        cursor: "move",
        helper: 'clone',
        revert: "invalid",
    
    });
    
    $("#launchPad").droppable({
        tolerance: "intersect",
        accept: ".card",
        activeClass: "ui-state-default",
        hoverClass: "ui-state-hover",
        drop: function(event, ui) {
            $("#launchPad").append($(ui.draggable));
        }
    });
    
    $(".stackDrop").droppable({
        tolerance: "intersect",
        accept: ".card",
        activeClass: "ui-state-default",
        hoverClass: "ui-state-hover",
        drop: function(event, ui) {        
            $(this).append($(ui.draggable));
        }
    });
    body { 
        margin: 0;
        background-color: #ffffcc;
    }
    #launchPad {
        width:170px;
        float:left;
        border: 1px solid #eaeaea;
        background-color: #f5f5f5;
    }
    #dropZone {
        float:right;
        border: 1px solid #eaeaea;
        background-color: #ffffcc;
    }
    .card { 
        width: 130px; 
        padding: 5px 10px;
        margin:5px;
        border:1px solid #ccc;
        background-color: #eaeaea;
    }
    .stack {
      display:inline-block;
       vertical-align:top;
        width: 180px;
        border: 1px solid #ccc;
        background-color: #f5f5f5;
        margin: 20px;
    }
    .stackHdr {
        background-color: #eaeaea;
        border: 1px solid #fff;
        padding: 5px 
    }
    .stackDrop {
        min-height:100px;
        padding: 15px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
    <script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
    
    <div id="launchPad">    
        <div class="card draggable" >
            apple
        </div> 
        <div class="card draggable">
            orange
        </div> 
        <div class="card draggable">
            banana
        </div> 
        <div class="card draggable">
            car
        </div> 
        <div class="card draggable">
            bus
        </div> 
    </div>
    
    <div id="dropZone">
        <div class="stack">
            <div class="stackHdr">
                Drop here 
            </div>
            <div class="stackDrop droppable">
                
            </div>
        </div>
        
        <div class="stack">
            <div class="stackHdr">
                Or here
            </div>
            <div class="stackDrop droppable">
                
            </div>
        </div>
    </div>