Drag and Drop Rows in Table


(Matthew G. Saroff) #1

I’m using a table to present a page for user custom menus.

I want the user to be able to reorder rows so as to be able to set the menus in the order that they want.

The page looks like this:

The idea is that the user can edit the custom tags by selecting a row (already got that).

What I want to do is make the rows draggable so that they can reorder the menus to suit their preferences.

The body of the table is generated programatically from the object where the custom tags are stored, and each row element is defined as draggable, but the item will not drag:

<table id="customTagTable" border="2">
    <thead id="customTableHead">
        <tr>
            <th>Menu ID</th>
            <th>Menu Title</th>
            <th>Parent ID</th>
            <th>Menu Argument</th>
        </tr>
    </thead>
    <tbody id="customTagList">
        <tr id="bbcwbx.custom.001" draggable="true">
            <td>bbcwbx.custom.001</td>
            <td>First custom menu</td>
            <td>bbcwbx.custom</td>
            <td>Arg 1</td>
        </tr>
        <tr id="bbcwbx.custom.002" draggable="true">
            <td>bbcwbx.custom.002</td>
            <td>Second custom menu</td>
            <td>bbcwbx.custom</td>
            <td>Arg 2</td>
        </tr>
        <tr id="bbcwbx.custom.003" draggable="true">
            <td>bbcwbx.custom.003</td>
            <td>Third custom menu</td>
            <td>bbcwbx.custom</td>
            <td>Arg 3</td>
        </tr>
        <tr id="bbcwbx.custom.004" draggable="true">
            <td>bbcwbx.custom.004</td>
            <td>Fourth custom menu</td>
            <td>bbcwbx.custom</td>
            <td>Arg 4</td>
        </tr>
        <tr id="bbcwbx.custom.006" draggable="true">
            <td>bbcwbx.custom.006</td>
            <td>Fifth custom menu</td>
            <td>bbcwbx.custom</td>
            <td>XYZZY</td>
        </tr>
    </tbody>
</table>

But this does not make the rows draggable.

I am trying to avoid things like jQuery, since I already have a tough enough learning curve for plain old JavaScript, and I was wondering what your suggestions might be.


(Matthew G. Saroff) #2

I’ve just got the answer to my own question, you don’t, because you probably can’t, (at least I can’t) drag and drop rows in a table.

However, you can make a list (<ul> and <li>) drag and droppable, and this is exactly the same user experience, there are dozens examples of pure JavaScript and various JS libraries that give examples.

You just to add the appropriate elements to the CSS, specifically display: table; to the grid container, and display: table-cell; to the cell elements, along with borders, widths, etc.

So your list masquerading as a table looks like this:

CSS allows me to set up the “column” widths as a percentage of the screen and have text wrapping if necessary, so it’s responsive to different resolutions.

Now I just need to make the rows drag and drop amenable.


(Niklas Gollenstede) #3

Drag and drop is rather annoying. I’d suggest you use a library that takes care of all the weirdness for you, e.g. sortablejs.


(Matthew G. Saroff) #4

I’ve got drag and drop working, I use a fairly standard drag and drop.

You can see the GitHub here.

There are two things, one annoying, and one “nice to have”, that I’d like to fix.

On the annoying side, I cannot drag anything to the bottom row. I can drag the bottom row up, I just can’t drag anything to the bottom row.

On the “nice to have” side, I would like to move the rows aside as I move something down:

The pertinent snippets of the code relating to dragging below:

CSS:

ul.order {  /* Set up a list with no indent and no first character to make sortable table appearance */ 
    list-style-type: none;
    margin-left: 0; padding-left: 0;
}

.grid-container {
  display: table;
  width: 100%;
  border: none;
  cursor: move;
  font-size: 18px;
}


.grid-containerHdr {
  display: table;
  width: 100%;
  margin-bottom: -16px;
  border: none;
 font-size: 18px;
}

The list that looks like a table table is generated with the following JS:

    for (i = 0; i < Object.keys(customMenus).length; i++) {

const containerSpan = document.createElement('span');
      containerSpan.classList.add("grid-container");
const menuIdSpan = document.createElement('span');
      menuIdSpan.classList.add("baseSort", "menuIdBody");
const menuTitleSpan = document.createElement('span');
      menuTitleSpan.classList.add("baseSort", "menuTitleBody");
const parentIdSpan = document.createElement('span');
      parentIdSpan.classList.add("baseSort", "menuParentIdBody");
const menuArgSpan = document.createElement('span');
      menuArgSpan.classList.add("baseSort", "menuArgBody");
    var createLi = document.createElement("li"); 
    createLi.setAttribute('id', customMenus[i].menuId);
    createLi.setAttribute('draggable', true);
    createLi.classList.add("listItem");
    menuIdSpan.appendChild(document.createTextNode(customMenus[i].menuId));
    menuTitleSpan.appendChild(document.createTextNode(customMenus[i].menuTitle));
    parentIdSpan.appendChild(document.createTextNode(customMenus[i].parentId));
    menuArgSpan.appendChild(document.createTextNode(customMenus[i].menuArg));
//console.log(menuIdSpan,menuTitleSpan);
    containerSpan.appendChild(menuIdSpan);
    containerSpan.appendChild(menuTitleSpan);
    containerSpan.appendChild(parentIdSpan);
    containerSpan.appendChild(menuArgSpan);
createLi.appendChild(containerSpan);
  ulSort.appendChild(createLi);
}

The sort code is:

var listItems = document.querySelectorAll('.listItem');

var dragSrcEl = null;

function handleDragStart(e) {
  this.className += " dragStartClass";
  dragSrcEl = this;

  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/html', this.innerHTML);
  e.dataTransfer.setDragClass("dataTransferClass");
//just added
  e.target.style.border = "5px solid #ffffff";

}

function handleDragOver(e) {
  // if (e.preventDefault) { not needed according to my question and anwers on : http://stackoverflow.com/questions/36920665/why-if-statement-with-e-preventdefault-drag-and-drop-javascript
  e.preventDefault();
  // }
  e.dataTransfer.dropEffect = 'move'; // sets cursor
  return false;

}

function handleDragEnter(e) {
  // this / e.target is the current hover target.
  this.classList.add('over');
}

function handleDragLeave(e) {
  this.classList.remove('over'); // this / e.target is previous target element.
}

function handleDrop(e) {

  var listItems = document.querySelectorAll('.listItem');
  e.stopPropagation(); // stops the browser from redirecting.
  dragSrcOrderId = parseInt(dragSrcEl.getAttribute("order-id"));
  dragTargetOrderId = parseInt(this.getAttribute("order-id"));
  var tempThis = this;


  // Don't do anything if dropping the same column we're dragging.
  // and
  // check if only one difference and then do not execute
  // && ((Math.abs(dragSrcOrderId - dragTargetOrderId)) != 1)
  if (dragSrcEl != this) {
    // Set the source column's HTML to the HTML of the column we dropped on.
    var tempThis = this;

    function makeNewOrderIds(tempThis) {
      // check if up or down movement

      dragSrcEl.setAttribute("order-id", dragTargetOrderId);
      tempThis.setAttribute("order-id", dragTargetOrderId);

      //  find divs between old and new location and set new ids - different in up or down movement (if else)
      if (dragSrcOrderId < dragTargetOrderId) {
        for (i = dragSrcOrderId + 1; i < dragTargetOrderId; i++) {
          listItems[i].setAttribute("order-id", i - 1);
          // set new id src
          dragSrcEl.setAttribute("order-id", dragTargetOrderId - 1);
        }
      } else {
        for (i = dragTargetOrderId; i < dragSrcOrderId; i++) {
          listItems[i].setAttribute("order-id", i + 1);
          // set new id src
          dragSrcEl.setAttribute("order-id", dragTargetOrderId);

        }
      }

    };
    makeNewOrderIds(tempThis);


    dragSrcEl.classList.remove("dragStartClass");

    reOrder(listItems);




  } else {

    dragSrcEl.classList.remove("dragStartClass");
    return false;

  }

};

function handleDragEnd(e) {

  for (i = 0; i < listItems.length; i++) {
    listItem = listItems[i];
    listItem.classList.remove('over');
  }
  dragSrcEl.classList.remove("dragStartClass");
  e.target.style.border = "none";


}



for (i = 0; i < listItems.length; i++) {
  listItem = listItems[i];


  listItem.setAttribute("order-id", i);



  listItem.addEventListener('dragstart', handleDragStart, false)
  listItem.addEventListener('dragenter', handleDragEnter, false)
  listItem.addEventListener('dragover', handleDragOver, false)
  listItem.addEventListener('dragleave', handleDragLeave, false)
  listItem.addEventListener('drop', handleDrop, false)
  listItem.addEventListener('dragend', handleDragEnd, false)
}

function reOrder(listItems) {


  var tempListItems = listItems;
  tempListItems = Array.prototype.slice.call(tempListItems, 0);

  tempListItems.sort(function(a, b) {
    return a.getAttribute("order-id") - b.getAttribute("order-id");
  });



  var parent = document.getElementById('listOrder');
  parent.innerHTML = "";

  for (var i = 0, l = tempListItems.length; i < l; i++) {
    parent.appendChild(tempListItems[i]);
  }
};

(Niklas Gollenstede) #5

You mean like this: http://sortablejs.github.io/Sortable/ (Seriously, check out that library)

A padding at the end of the container or a margin on the last item (together with the right boxing) should help with placing items at the end of the list.


(Baptiste Thémine) #6

Constructing every Nodes and inserting CSS classes in JavaScript for each element is also annoying. You could really improve your code with HTML Templates.


(Matthew G. Saroff) #7

Do HTML templates work on lists?

What looks like a table is actually a list styled to appear as a table, because drag and drop does not (as far as I have been able to determine) work on tables.


(Baptiste Thémine) #8

Yes, templates work with everything.


(jscher2000) #9

I just learned about HTML Templates recently. I’m using a couple of them to inject forms, which works around the innerHTML / insertAdjacentHTML warnings.

Obviously this is amateur coding, but hopefully it makes some sense.