2
votes

I have a relatively simple web app that I'm creating as a bookshelf and I want the user to be able to drag and drop books between the bookshelves. It works exactly as I want when I drop a book to the "right" side of the existing books in one of the bookshelves. The book that has been dragged and dropped is added to the shelf.

Where it isn't working is when I drop a book on top of an existing book. When I do that, the book is removed from the existing shelf -- but it disappears totally instead of being added to the targeted bookshelf as I desire. Its unclear to me why this is.

See here: https://mainstringargs.github.io/bookshelf-data/test.html

(I'm using Firefox, if that is relevant)

Relevant source code is here (raw html): https://github.com/mainstringargs/bookshelf-data/blob/master/test.html

Note: This was originally generated via jekyll in github, but since this problem is not jekyll centric, I'm not including that source code detail.

I largely followed this guide for how to implement drag and drop:

https://www.w3schools.com/html/html5_draganddrop.asp

The relevant javascript code is below. The 'drop' function is intended to drop the relevant list item to the new list.

     <script>
        
        function allowDrop(ev) {
           ev.preventDefault();
        }
        
        function drag(ev) {
           ev.dataTransfer.setData("text", ev.target.id);
        }
        
        function drop(ev) {
          ev.preventDefault();
          var data = ev.dataTransfer.getData("text");
          ev.target.appendChild(document.getElementById(data).parentNode);
        }
        
     </script>

Each bookshelf is itself wrapped in a div containing a single unordered list (ul) with list items (li) representing each book. Here is a snippet from the first bookshelf:

     <div ondrop="drop(event)" ondragover="allowDrop(event)"  class="shelf read">
        <ul  class="books clearfix">
           <li id="The_Clean_Coder" class="book">
              <img id="img_The_Clean_Coder" draggable="true" ondragstart="drag(event)"  class="front" src="/bookshelf-data/assets/img/the_clean_coder.jpg" alt="The Clean Coder">
              <div class="back" onclick="" id="read_The_Clean_Coder">
                 <div class="p0">
                    <span class="score"><span id="rating_The_Clean_Coder"></span></span>
                    <script>
                       var span = document.getElementById("rating_The_Clean_Coder");
                       var rating = 4.3;
                       var starWidth = (span.offsetWidth - paddingLeft - (5*gapSize))/5.0;
                       var gaps = Math.floor(rating);
                       var width = (rating * starWidth) + (gaps * gapSize);
                       span.style.width = `${width}px`;
                       span.title=4.3 +" out of 5";
                    </script>
                    <dl class="p10">
                       <dt>Title:</dt>
                       <dd>The Clean Coder</dd>
                       <dt>Author:</dt>
                       <dd>Robert C. Martin</dd>
                       <dt>Released:</dt>
                       <dd>May 23, 2011</dd>
                    </dl>
                    <p class="description">
                       Programmers who endure and succeed amidst swirling uncertainty and nonstop pressure share a common attribute - They care deeply about...
                    </p>
                    <a href="https://www.amazon.com/Clean-Coder-Conduct-Professional-Programmers/dp/0137081073/ref=as_li_ss_tl?crid=9YJ9X71LWCXD&keywords=clean+architecture&qid=1564690296&s=gateway&sprefix=clean+arh,aps,155&sr=8-4&linkCode=ll1&tag=mainstringarg-20&linkId=cd98bbe94682a01daca2dcb8852cf52e&language=en_US" target="_blank">Amazon Link</a>
                 </div>
              </div>
           </li>

As you can see, I've added these calls to the bookshelf div: ondrop="drop(event)" ondragover="allowDrop(event)" and on each image, I've included draggable="true" ondragstart="drag(event)". This follows the pattern from the w3schools link, but there are cases where it does not seem to append the item to the unordered list.

3
Just use sortablejs.github.io/Sortable - it handles all of those scenariosDai
I think you're confusing the HTML5 drag-and-drop API (which is intended for dragging-in external content into a browser window, like files and text-selections) with in-page dragging-and-dropping.Dai

3 Answers

2
votes

The problem is that when you drop the element at the end, it's considering the parent of that element that is being moved the ul :

enter image description here

But when you drop your image on top of another image, it's putting that image inside the dropped one, considering it it's parent :

enter image description here

You then get an image inside an image, so it wouldn't show and wouldn't work.

To fix this I would suggest using a 'closest' method, in order to append to the closest book shelf :

function closest(elem) {
  if (elem.className.indexOf(1)) {
    return elem;
  }
  var parent = elem.parentNode;
  if (parent.className.indexOf(1) != -1) {
    return parent;
  }
  return closest(parent);
}
function drop(ev) {
  ...
  ev.target.closest('ul').appendChild(document.getElementById(data).parentNode);
}

You'd have a result like the following :

<link rel="stylesheet" href="https://mainstringargs.github.io/myshelf/assets/css/main.css">
<script>
  const paddingLeft = 15;
  const gapSize = 10;

  function allowDrop(ev) {
    ev.preventDefault();
  }

  function drag(ev) {
    ev.dataTransfer.setData("text", ev.target.id);
  }

  function closest(elem) {
    if (elem.className.indexOf(1)) {
      return elem;
    }
    var parent = elem.parentNode;
    if (parent.className.indexOf(1) != -1) {
      return parent;
    }
    return closest(parent);
  }

  function drop(ev) {
    ev.preventDefault();
    var data = ev.dataTransfer.getData("text");
    console.log(ev.target)
    //console.log(data);
    //console.log(document.getElementById(data));
    //console.log(document.getElementById(data).parentNode);
    //var parent = document.getElementById(data).parentNode
    ev.target.closest('ul').appendChild(document.getElementById(data).parentNode);
  }
</script>
<section id="bookshelf">
  <div class="user-details">
    <h1> My Shelf </h1>
  </div>
  <div class="shelf ondeck" ondrop="drop(event)" ondragover="allowDrop(event)">
    <ul class="books clearfix">
      <li id="Clean_Architecture" class="book">
        <img id="img_Clean_Architecture" draggable="true" ondragstart="drag(event)" class="front" src="https://mainstringargs.github.io/bookshelf-data/assets/img/clean_architecture.jpg" alt="Clean Architecture">
        <div class="back" onclick="" id="ondeck_Clean_Architecture">
          <div class="p0">
            <span class="score"><span id="rating_Clean_Architecture"></span></span>
            <script>
              var span = document.getElementById("rating_Clean_Architecture");
              var rating = 4.3;
              var starWidth = (span.offsetWidth - paddingLeft - (5 * gapSize)) / 5.0;
              var gaps = Math.floor(rating);
              var width = (rating * starWidth) + (gaps * gapSize);
              span.style.width = `${width}px`;
              span.title = 4.3 + " out of 5";
            </script>
            <dl class="p10">
              <dt>Title:</dt>
              <dd>Clean Architecture</dd>
              <dt>Author:</dt>
              <dd>Robert C. Martin</dd>
              <dt>Released:</dt>
              <dd>September 20, 2017</dd>
            </dl>
            <p class="description">
              By applying universal rules of software architecture, you can dramatically improve developer productivity throughout the life of any software system....
            </p>
            <a href="https://www.amazon.com/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164/ref=as_li_ss_tl?crid=9YJ9X71LWCXD&keywords=clean+architecture&qid=1564690296&s=gateway&sprefix=clean+arh,aps,155&sr=8-1&linkCode=ll1&tag=mainstringarg-20&linkId=d660a50d6cae9d31db54a46b1367c79e&language=en_US"
              target="_blank">Amazon Link</a>
          </div>
        </div>
      </li>
      <li id="Designing_Data-Intensive_Applications" class="book">
        <img id="img_Designing_Data-Intensive_Applications" draggable="true" ondragstart="drag(event)" class="front" src="https://mainstringargs.github.io/bookshelf-data/assets/img/designing_data_intensive_applications.jpg" alt="Designing Data-Intensive Applications">
        <div class="back" onclick="" id="ondeck_Designing_Data-Intensive_Applications">
          <div class="p0">
            <span class="score"><span id="rating_Designing_Data-Intensive_Applications"></span></span>
            <script>
              var span = document.getElementById("rating_Designing_Data-Intensive_Applications");
              var rating = 4.7;
              var starWidth = (span.offsetWidth - paddingLeft - (5 * gapSize)) / 5.0;
              var gaps = Math.floor(rating);
              var width = (rating * starWidth) + (gaps * gapSize);
              span.style.width = `${width}px`;
              span.title = 4.7 + " out of 5";
            </script>
            <dl class="p10">
              <dt>Title:</dt>
              <dd>Designing Data-Intensive Applications</dd>
              <dt>Author:</dt>
              <dd>Martin Kleppmann</dd>
              <dt>Released:</dt>
              <dd>April 2, 2017</dd>
            </dl>
            <p class="description">
              Data is at the center of many challenges in system design today. Difficult issues need to be figured out, such...
            </p>
            <a href="https://www.amazon.com/Designing-Data-Intensive-Applications-Reliable-Maintainable/dp/1449373321/ref=as_li_ss_tl?crid=2J0BUT3XUW5BX&keywords=streaming+systems&qid=1561728107&s=books&sprefix=Streaming+Systems,stripbooks,156&sr=1-2&linkCode=ll1&tag=mainstringarg-20&linkId=f07f59f79a517afbf479cd3642e61c3a&language=en_US"
              target="_blank">Amazon Link</a>
          </div>
        </div>
      </li>
      <li id="Streaming_Systems" class="book">
        <img id="img_Streaming_Systems" draggable="true" ondragstart="drag(event)" class="front" src="https://mainstringargs.github.io/bookshelf-data/assets/img/streaming_systems.jpg" alt="Streaming Systems">
        <div class="back" onclick="" id="ondeck_Streaming_Systems">
          <div class="p0">
            <span class="score"><span id="rating_Streaming_Systems"></span></span>
            <script>
              var span = document.getElementById("rating_Streaming_Systems");
              var rating = 4.5;
              var starWidth = (span.offsetWidth - paddingLeft - (5 * gapSize)) / 5.0;
              var gaps = Math.floor(rating);
              var width = (rating * starWidth) + (gaps * gapSize);
              span.style.width = `${width}px`;
              span.title = 4.5 + " out of 5";
            </script>
            <dl class="p10">
              <dt>Title:</dt>
              <dd>Streaming Systems</dd>
              <dt>Author:</dt>
              <dd>Tyler Akidau, Slava Chernyak & Reuven Lax</dd>
              <dt>Released:</dt>
              <dd>August 2, 2018</dd>
            </dl>
            <p class="description">
              Streaming data is a big deal in big data these days. As more and more businesses seek to tame the...
            </p>
            <a href="https://www.amazon.com/Streaming-Systems-Where-Large-Scale-Processing/dp/1491983876/ref=as_li_ss_tl?keywords=Streaming+Systems&qid=1564691718&s=gateway&sr=8-2&linkCode=ll1&tag=mainstringarg-20&linkId=3976bbc20ed7fc47509f68ea58a53b19&language=en_US"
              target="_blank">Amazon Link</a>
          </div>
        </div>
      </li>
      <li id="Clean_Code" class="book">
        <img id="img_Clean_Code" draggable="true" ondragstart="drag(event)" class="front" src="https://mainstringargs.github.io/bookshelf-data/assets/img/clean_code.jpg" alt="Clean Code">
        <div class="back" onclick="" id="ondeck_Clean_Code">
          <div class="p0">
            <span class="score"><span id="rating_Clean_Code"></span></span>
            <script>
              var span = document.getElementById("rating_Clean_Code");
              var rating = 4.3;
              var starWidth = (span.offsetWidth - paddingLeft - (5 * gapSize)) / 5.0;
              var gaps = Math.floor(rating);
              var width = (rating * starWidth) + (gaps * gapSize);
              span.style.width = `${width}px`;
              span.title = 4.3 + " out of 5";
            </script>
            <dl class="p10">
              <dt>Title:</dt>
              <dd>Clean Code</dd>
              <dt>Author:</dt>
              <dd>Robert C. Martin</dd>
              <dt>Released:</dt>
              <dd>August 11, 2008</dd>
            </dl>
            <p class="description">
              Best agile practices of cleaning code “on the fly” that will instill within you the values of a software craftsman...
            </p>
            <a href="https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882/ref=as_li_ss_tl?_encoding=UTF8&pd_rd_i=0132350882&pd_rd_r=779f34be-10b6-4da1-bee1-315e02822e8a&pd_rd_w=cbXpV&pd_rd_wg=7e0si&pf_rd_p=1c11b7ff-9ffb-4ba6-8036-be1b0afa79bb&pf_rd_r=5DNWQZ8VE6S37W6B11GZ&psc=1&refRID=5DNWQZ8VE6S37W6B11GZ&linkCode=ll1&tag=mainstringarg-20&linkId=c0c7bb9dde941f0956bbee5628952f98&language=en_US"
              target="_blank">Amazon Link</a>
          </div>
        </div>
      </li>
      <li id="Weapons_of_Math_Destruction" class="book">
        <img id="img_Weapons_of_Math_Destruction" draggable="true" ondragstart="drag(event)" class="front" src="https://mainstringargs.github.io/bookshelf-data/assets/img/weapons_of_math_destruction.jpg" alt="Weapons of Math Destruction">
        <div class="back" onclick="" id="ondeck_Weapons_of_Math_Destruction">
          <div class="p0">
            <span class="score"><span id="rating_Weapons_of_Math_Destruction"></span></span>
            <script>
              var span = document.getElementById("rating_Weapons_of_Math_Destruction");
              var rating = 4.1;
              var starWidth = (span.offsetWidth - paddingLeft - (5 * gapSize)) / 5.0;
              var gaps = Math.floor(rating);
              var width = (rating * starWidth) + (gaps * gapSize);
              span.style.width = `${width}px`;
              span.title = 4.1 + " out of 5";
            </script>
            <dl class="p10">
              <dt>Title:</dt>
              <dd>Weapons of Math Destruction</dd>
              <dt>Author:</dt>
              <dd>Cathy O'Neil</dd>
              <dt>Released:</dt>
              <dd>September 6, 2016</dd>
            </dl>
            <p class="description">
              We live in the age of the algorithm. Increasingly, the decisions that affect our lives—where we go to school, whether...
            </p>
            <a href="https://www.amazon.com/Weapons-Math-Destruction-Increases-Inequality/dp/0553418815/ref=as_li_ss_tl?ie=UTF8&linkCode=ll1&tag=mainstringarg-20&linkId=e04f1e235dca487c6ea2eaf6050243d1&language=en_US" target="_blank">Amazon Link</a>
          </div>
        </div>
      </li>
      <li id="Microservices_in_Action" class="book">
        <img id="img_Microservices_in_Action" draggable="true" ondragstart="drag(event)" class="front" src="https://mainstringargs.github.io/bookshelf-data/assets/img/microservices_in_action.jpg" alt="Microservices in Action">
        <div class="back" onclick="" id="ondeck_Microservices_in_Action">
          <div class="p0">
            <span class="score"><span id="rating_Microservices_in_Action"></span></span>
            <script>
              var span = document.getElementById("rating_Microservices_in_Action");
              var rating = 4.4;
              var starWidth = (span.offsetWidth - paddingLeft - (5 * gapSize)) / 5.0;
              var gaps = Math.floor(rating);
              var width = (rating * starWidth) + (gaps * gapSize);
              span.style.width = `${width}px`;
              span.title = 4.4 + " out of 5";
            </script>
            <dl class="p10">
              <dt>Title:</dt>
              <dd>Microservices in Action</dd>
              <dt>Author:</dt>
              <dd>Morgan Bruce & Paulo A. Pereira</dd>
              <dt>Released:</dt>
              <dd>November 5, 2018</dd>
            </dl>
            <p class="description">
              Microservices in Action is a practical book about building and deploying microservice-based applications. Written for developers and architects with a...
            </p>
            <a href="https://www.amazon.com/Microservices-Action-Morgan-Bruce/dp/1617294454/ref=as_li_ss_tl?ie=UTF8&linkCode=ll1&tag=mainstringarg-20&linkId=74fa14a366397e614b1f8916f01d2464&language=en_US" target="_blank">Amazon Link</a>
          </div>
        </div>
      </li>
    </ul>
  </div>
</section>

Disclaimer, the closest function I got for my algorithm is from here.

2
votes

The solution using closest("ul") from @islam is a good solution.

To build on that, as I’m not sure his function closest(elem) will ever get called (I think you need to use Element.prototype.closest = function (selector) {…}, to be able to call the method on the target element, and closest should do what you need), you can use a polyfill to provide the function on browsers that don’t have the closest method (i.e. Internet Explorer).

MDN’s page on closest includes a polyfill that enables IE to have the functionality.

You can add the ployfill in the head section of your page:

<script>
    if (!Element.prototype.matches) {
        Element.prototype.matches =
        Element.prototype.msMatchesSelector ||
        Element.prototype.webkitMatchesSelector;
    }

    if (!Element.prototype.closest) {
        Element.prototype.closest = function(s) {
            var el = this;

            do {
                if (Element.prototype.matches.call(el, s)) return el;
                el = el.parentElement || el.parentNode;
            } while (el !== null && el.nodeType === 1);
            return null;
        };
    }
</script>

And then use the drop function from Islam with closest("ul"):

function drop(ev) {
    ev.preventDefault();
    var data = ev.dataTransfer.getData("text");
 
    ev.target.closest("ul").appendChild(document.getElementById(data).parentNode);
}
0
votes

The issue is that when you are dropping on top of an existing book, the target of the event is the img element of the existing book. When you drop on the right side of a list, the target of the event is the ul element which works correctly.

This answer will solve your issue: https://stackoverflow.com/a/28203782/9262488

Please see this fiddile: https://jsfiddle.net/4gsrpv31/

Or you could check the target of the event and append to its parent node if it is an img element as below:

function drop(ev) {
    ev.preventDefault();
    var data = ev.dataTransfer.getData("text");
    var appendNode = ev.target;
    if(appendNode.nodeName == "IMG")
        appendNode = appendNode.parentNode.parentNode;
    appendNode.appendChild(document.getElementById(data).parentNode);
}

Example: https://jsfiddle.net/n8t7Lory/