Javascript 拖放;删除API和表行(仅适用于vanilla JS)

Javascript 拖放;删除API和表行(仅适用于vanilla JS),javascript,html-table,drag-and-drop,Javascript,Html Table,Drag And Drop,在一列n行的my HTML表格中,例如: <table id="my-table"> <tr> <td>How</td> </tr> <tr> <td>Are</td> </tr> <tr> <td>You</td> </tr> </table> 当我运行此命令并将一行拖到另一行时

在一列n行的my HTML表格中,例如:

<table id="my-table">
  <tr>
    <td>How</td>
  </tr>
  <tr>
    <td>Are</td>
  </tr>
  <tr>
    <td>You</td>
  </tr>
</table>

当我运行此命令并将一行拖到另一行时,控制台会为我提供一个关于Dragenter、Dragleave和Dragent处理程序函数的
ev.target.closest
的错误,总是说
ev.target.closest不是函数。虽然我在dragstart处理程序函数中使用了完全相同的语法,但在控制台中该行没有报告任何错误。完成拖动后,拖放行的内容都变为
确定解决方案,最重要的事实是:

  • 如上所述,我在HTML标记中使用了
    addEventListener
    而不是HTML内联事件监听器,原因有几个(这里不再详细说明)

  • 我遇到的最困难的问题是,当我将拖动的元素拖到一个元素的子元素上时,dragenter&dragleave侦听器绑定到该元素上,我的dragleave事件函数被触发。我在这个论坛上读了无数关于这个问题的文章和帖子,没有一个建议的解决方案能够轻松或简单地处理我非常基本的HTML表行和单元格,并且很快就使用了函数来停止事件传播等,这实际上是不必要的,正如您在我的代码中看到的那样。此外,我在我的解决方案中完全使用了
    dragleave
    事件侦听器,因此这可能是实现我想要的内容的一种更简单的方法,这就是为什么我想在这里将它分享给其他可能想做类似事情的人

与上述内容不同的是,一切都能正常工作:

HTML:我只在
td
标记中包含了
draggable=“true”class=“table cell”

JavaScript:现在完全不同了,但工作非常完美:

/*##############################################################################
##    1. Define Function which adds all the required event listeners          ##
##############################################################################*/

function addDragEvents(element) {
  element.addEventListener("dragstart",dragCell);
  element.addEventListener("dragover",allowDrop);
  element.addEventListener("drop",dropCell);
  element.addEventListener("dragenter",handleDragEnter);
  element.addEventListener("dragend",handleDragEnd);
}

/*##############################################################################
##    2. Define Function which resets the drag state for the concerned table  ##
##############################################################################*/

function resetDragState(e) {
  let data = e.dataTransfer.getData("text/plain");
  data = JSON.parse(data);
  let rows = document.getElementById(data.id).rows;
  for (let row of rows) {
    if (row.classList.contains('ready-for-drop')) {
      row.classList.remove('ready-for-drop');
    }
    let cells = row.cells;
    for (let cell of cells) {
      cell.style.cursor="pointer";
    }
  }
}

/*##############################################################################
##    3. Add Required Event listeners to all table cells                      ##
##############################################################################*/

let cells = document.getElementsByClassName('table-cell');
for (let cell of cells) {
  addDragEvents(cell);
}

/*##############################################################################
##                            4. Dragstart Handler                            ##
##############################################################################*/

// This function is created to define the action of a drag (which data will be
// taken for transfer to the HTML element into which the dragged element's
// content will be dropped)

function dragCell(e) {
  // Change the cursor to a grabbing hand on dragstart
  let cells = document.getElementsByClassName('table-cell');
  for (let cell of cells) {
    cell.style.cursor="grabbing";
  }
  // Three pieces of information must be transferred for the drag & drop to work
  // properly: the table id of the table having the row being dragged (to assure
  // that D&D only works among rows of the same table), the row index of the row
  // being dragged (to know which row needs to be replaced via the drop
  // function), and finally the content of the row being dragged
  let draggedRow = e.target.closest("tr");
  // Get the row index of that row
  let rowNumber = draggedRow.rowIndex;
  // Get the id name of the table having that row
  let tableId = draggedRow.parentNode.parentNode.getAttribute("id");
  // Initiate JSON object which will be transferred to the drop row
  let data = {"id":tableId,"rowNumber":rowNumber};
  // Append all the cells as second element onto this same object
  let cellsdragged = draggedRow.children;
  let amountOfCells = cellsdragged.length;
  let dataToTransfer = [];
  for (let i = 0; i < amountOfCells; i++) {
    let currentCell = cellsdragged[i];
    dataToTransfer.push(currentCell.outerHTML);
  }
  data["cellContents"] = dataToTransfer;
  data = JSON.stringify(data);
  e.dataTransfer.setData("text/plain",data);
}

/*##############################################################################
##                             5. Dragover Handler                            ##
##############################################################################*/

// This function is used to allow for drops into the corresponding HTML elements
// (the default behavior doesn't allow this)
function allowDrop(e) {
  e.preventDefault();
}

/*##############################################################################
##                               6. Drop Handler                              ##
##############################################################################*/

function dropCell(e) {
  // First, prevent default behavior once again
  e.preventDefault();
  // Second, access data coming from dragged element (which is the index of the
  // row from which data is being dragged)
  let data = e.dataTransfer.getData("text/plain");
  data = JSON.parse(data);
  // Next, get the index of the row into which content shall be dropped
  let rowToDrop = e.target.closest('tr');
  let targetIndex = rowToDrop.rowIndex;
  // Next, get the id of the table of that retrieved row
  let targetId = rowToDrop.parentNode.parentNode.getAttribute("id");
  // Next, only proceed if the dragged row comes from the same table as the
  // target row, and if the dragged and the target rows are two different rows
  if (data.id == targetId && data.rowNumber != targetIndex) {
    // Store the contents of the target row in the same array structure as the
    // one coming from the dragged row
    let targetContents = [];
    // Exchange the contents of the two rows
    let cellsForDrop = rowToDrop.children;
    let amountOfCells = cellsForDrop.length;
    for (let i = 0; i < amountOfCells; i++) {
      targetContents
        .push(cellsForDrop[i].outerHTML);
    }
    // Exchange the contents of the two rows
    let draggedRow = document.getElementById(data.id).rows[data.rowNumber];
    let cellsOfDrag = draggedRow.children;
    for (let i = 0; i < amountOfCells; i++) {
      // Replace the content of the row into which the drag is being dropped
      // with the content of the dragged row
      cellsForDrop[i].outerHTML = data.cellContents[i];
      // Replacement of the outerHTML deletes all bound event listeners, so:
      addDragEvents(cellsForDrop[i]);
      // And now, replace the content of the dragged row with the content of the
      // target row. Then, do the same for the value.
      cellsOfDrag[i].outerHTML = targetContents[i];
      addDragEvents(cellsOfDrag[i]);
    }
    resetDragState(e);
  }
}

/*##############################################################################
##                             7. Dragenter Handler                           ##
##############################################################################*/

// When dragging over the text node of a table cell (the text in a table cell),
// while previously being over the table cell element, the dragleave event gets
// fired, which stops the highlighting of the currently dragged cell. To avoid
// this problem and any coding around to fight it, everything has been
// programmed with the dragenter event handler only; no more dragleave needed

// For the dragenter event, e.target corresponds to the element into which the
// drag enters. This fact has been used to program the code as follows:

var previousRow = null;

function handleDragEnter(e) {
  // Assure that dragenter code is only executed when entering an element (and
  // for example not when entering a text node)
  if (e.target.nodeType === 1) {
    // Get the currently entered row
    let currentRow = this.closest('tr');
    // Check if the currently entered row is different from the row entered via
    // the last drag
    if (previousRow !== null) {
      if (currentRow !== previousRow) {
        // If so, remove the class responsible for highlighting it via CSS from
        // it
        previousRow.className = "";
      }
    }
    // Each time an HTML element is entered, add the class responsible for
    // highlighting it via CSS onto its containing row (or onto itself, if row)
    currentRow.className = "ready-for-drop";
    // To know which row has been the last one entered when this function will
    // be called again, assign the previousRow variable of the global scope onto
    // the currentRow from this function run
    previousRow = currentRow;
  }
}

/*##############################################################################
##                              8. Dragend Handler                            ##
##############################################################################*/

// This function is required for cases where the dragged has been dropped on a
// non-valid drop target.

function handleDragEnd(e) {
  resetDragState(e);
}
/*##############################################################################
##    1. 定义添加所有必需事件侦听器的函数##
##############################################################################*/
函数AddDrageEvents(元素){
元素。addEventListener(“dragstart”,dragCell);
元素。添加事件列表器(“dragover”,allowDrop);
元素。addEventListener(“drop”,dropCell);
元素。添加事件列表器(“dragenter”,handleDragEnter);
元素。addEventListener(“dragend”,handleDragEnd);
}
/*##############################################################################
##    2. 定义用于重置相关表的拖动状态的函数##
##############################################################################*/
函数重置DragState(e){
让data=e.dataTransfer.getData(“text/plain”);
data=JSON.parse(数据);
让rows=document.getElementById(data.id).rows;
for(让一行中的一行){
if(row.classList.contains('ready-for-drop')){
row.classList.remove('ready-for-drop');
}
设cells=row.cells;
for(单元格中的单元格){
cell.style.cursor=“指针”;
}
}
}
/*##############################################################################
##    3. 向所有表格单元格添加所需的事件侦听器##
##############################################################################*/
让cells=document.getElementsByClassName('table-cell');
for(单元格中的单元格){
添加DragEvents(单元);
}
/*##############################################################################
##                            4. Dragstart处理程序##
##############################################################################*/
//创建此函数是为了定义拖动操作(将显示哪些数据)
//用于传输到被拖动元素的
//内容(将被删除)
函数dragCell(e){
//将光标更改为dragstart上的抓取手
让cells=document.getElementsByClassName('table-cell');
for(单元格中的单元格){
cell.style.cursor=“抓取”;
}
//拖放操作必须传输三条信息才能正常工作
//正确:具有被拖动行的表的表id(以确保
//该D&D仅在同一表的行之间起作用),即该行的行索引
//正在拖动(以了解需要通过拖放替换的行
//函数),最后是要拖动的行的内容
设draggedRow=e.target.nexist(“tr”);
//获取该行的行索引
设rowNumber=draggedRow.rowIndex;
//获取具有该行的表的id名称
让tableId=draggedRow.parentNode.parentNode.getAttribute(“id”);
//启动JSON对象,该对象将被传输到drop行
let data={“id”:tableId,“rowNumber”:rowNumber};
//将所有单元格作为第二个元素附加到此同一对象上
让cellsdragged=draggedRow.children;
设amountOfCells=cellsdragged.length;
让dataToTransfer=[];
for(设i=0;i/*##############################################################################
##    1. Define Function which adds all the required event listeners          ##
##############################################################################*/

function addDragEvents(element) {
  element.addEventListener("dragstart",dragCell);
  element.addEventListener("dragover",allowDrop);
  element.addEventListener("drop",dropCell);
  element.addEventListener("dragenter",handleDragEnter);
  element.addEventListener("dragend",handleDragEnd);
}

/*##############################################################################
##    2. Define Function which resets the drag state for the concerned table  ##
##############################################################################*/

function resetDragState(e) {
  let data = e.dataTransfer.getData("text/plain");
  data = JSON.parse(data);
  let rows = document.getElementById(data.id).rows;
  for (let row of rows) {
    if (row.classList.contains('ready-for-drop')) {
      row.classList.remove('ready-for-drop');
    }
    let cells = row.cells;
    for (let cell of cells) {
      cell.style.cursor="pointer";
    }
  }
}

/*##############################################################################
##    3. Add Required Event listeners to all table cells                      ##
##############################################################################*/

let cells = document.getElementsByClassName('table-cell');
for (let cell of cells) {
  addDragEvents(cell);
}

/*##############################################################################
##                            4. Dragstart Handler                            ##
##############################################################################*/

// This function is created to define the action of a drag (which data will be
// taken for transfer to the HTML element into which the dragged element's
// content will be dropped)

function dragCell(e) {
  // Change the cursor to a grabbing hand on dragstart
  let cells = document.getElementsByClassName('table-cell');
  for (let cell of cells) {
    cell.style.cursor="grabbing";
  }
  // Three pieces of information must be transferred for the drag & drop to work
  // properly: the table id of the table having the row being dragged (to assure
  // that D&D only works among rows of the same table), the row index of the row
  // being dragged (to know which row needs to be replaced via the drop
  // function), and finally the content of the row being dragged
  let draggedRow = e.target.closest("tr");
  // Get the row index of that row
  let rowNumber = draggedRow.rowIndex;
  // Get the id name of the table having that row
  let tableId = draggedRow.parentNode.parentNode.getAttribute("id");
  // Initiate JSON object which will be transferred to the drop row
  let data = {"id":tableId,"rowNumber":rowNumber};
  // Append all the cells as second element onto this same object
  let cellsdragged = draggedRow.children;
  let amountOfCells = cellsdragged.length;
  let dataToTransfer = [];
  for (let i = 0; i < amountOfCells; i++) {
    let currentCell = cellsdragged[i];
    dataToTransfer.push(currentCell.outerHTML);
  }
  data["cellContents"] = dataToTransfer;
  data = JSON.stringify(data);
  e.dataTransfer.setData("text/plain",data);
}

/*##############################################################################
##                             5. Dragover Handler                            ##
##############################################################################*/

// This function is used to allow for drops into the corresponding HTML elements
// (the default behavior doesn't allow this)
function allowDrop(e) {
  e.preventDefault();
}

/*##############################################################################
##                               6. Drop Handler                              ##
##############################################################################*/

function dropCell(e) {
  // First, prevent default behavior once again
  e.preventDefault();
  // Second, access data coming from dragged element (which is the index of the
  // row from which data is being dragged)
  let data = e.dataTransfer.getData("text/plain");
  data = JSON.parse(data);
  // Next, get the index of the row into which content shall be dropped
  let rowToDrop = e.target.closest('tr');
  let targetIndex = rowToDrop.rowIndex;
  // Next, get the id of the table of that retrieved row
  let targetId = rowToDrop.parentNode.parentNode.getAttribute("id");
  // Next, only proceed if the dragged row comes from the same table as the
  // target row, and if the dragged and the target rows are two different rows
  if (data.id == targetId && data.rowNumber != targetIndex) {
    // Store the contents of the target row in the same array structure as the
    // one coming from the dragged row
    let targetContents = [];
    // Exchange the contents of the two rows
    let cellsForDrop = rowToDrop.children;
    let amountOfCells = cellsForDrop.length;
    for (let i = 0; i < amountOfCells; i++) {
      targetContents
        .push(cellsForDrop[i].outerHTML);
    }
    // Exchange the contents of the two rows
    let draggedRow = document.getElementById(data.id).rows[data.rowNumber];
    let cellsOfDrag = draggedRow.children;
    for (let i = 0; i < amountOfCells; i++) {
      // Replace the content of the row into which the drag is being dropped
      // with the content of the dragged row
      cellsForDrop[i].outerHTML = data.cellContents[i];
      // Replacement of the outerHTML deletes all bound event listeners, so:
      addDragEvents(cellsForDrop[i]);
      // And now, replace the content of the dragged row with the content of the
      // target row. Then, do the same for the value.
      cellsOfDrag[i].outerHTML = targetContents[i];
      addDragEvents(cellsOfDrag[i]);
    }
    resetDragState(e);
  }
}

/*##############################################################################
##                             7. Dragenter Handler                           ##
##############################################################################*/

// When dragging over the text node of a table cell (the text in a table cell),
// while previously being over the table cell element, the dragleave event gets
// fired, which stops the highlighting of the currently dragged cell. To avoid
// this problem and any coding around to fight it, everything has been
// programmed with the dragenter event handler only; no more dragleave needed

// For the dragenter event, e.target corresponds to the element into which the
// drag enters. This fact has been used to program the code as follows:

var previousRow = null;

function handleDragEnter(e) {
  // Assure that dragenter code is only executed when entering an element (and
  // for example not when entering a text node)
  if (e.target.nodeType === 1) {
    // Get the currently entered row
    let currentRow = this.closest('tr');
    // Check if the currently entered row is different from the row entered via
    // the last drag
    if (previousRow !== null) {
      if (currentRow !== previousRow) {
        // If so, remove the class responsible for highlighting it via CSS from
        // it
        previousRow.className = "";
      }
    }
    // Each time an HTML element is entered, add the class responsible for
    // highlighting it via CSS onto its containing row (or onto itself, if row)
    currentRow.className = "ready-for-drop";
    // To know which row has been the last one entered when this function will
    // be called again, assign the previousRow variable of the global scope onto
    // the currentRow from this function run
    previousRow = currentRow;
  }
}

/*##############################################################################
##                              8. Dragend Handler                            ##
##############################################################################*/

// This function is required for cases where the dragged has been dropped on a
// non-valid drop target.

function handleDragEnd(e) {
  resetDragState(e);
}