3

I'm building an e-mail constructor and when the user saves the template I just send the HTML to the server. But I need to remove the drap & drop element to send it to the server.

I'm not very good with DOM manipulation so I don't know where to start from.

This is my HTML:

<table>
  <tbody>
    <tr>
      <th>
        <div class="components-drop-area">
          <p>aa</p>
          <p>bb</p>
        </div>
      </th>
    </tr>
    <tr>
      <th>
        <div class="components-drop-area">
          <p>cc</p>
          <p>dd</p>
        </div>
      </th>
    </tr>
  </tbody>
</table>

I need to remove all the .components-drop-area divs. Something like that:

<table>
  <tbody>
    <tr>
      <th>
        <p>aa</p>
        <p>bb</p>
      </th>
    </tr>
    <tr>
      <th>
        <p>cc</p>
        <p>dd</p>
      </th>
    </tr>
  </tbody>
</table>

I stopped my code here:

var table = document.querySelector('table').cloneNode(true)

let dropAreas = table.querySelectorAll('.components-drop-area')

console.log(table, dropAreas)

How can I loop and remove desired elements while retaining their content?

sao
  • 1,835
  • 6
  • 21
  • 40
Caio Kawasaki
  • 2,894
  • 6
  • 33
  • 69
  • See [“Cut and Paste” - moving nodes in the DOM with Javascript](https://stackoverflow.com/q/324303/1366033) – KyleMit Nov 21 '19 at 22:16

4 Answers4

4

One way would simply be to replace the parentNode's innerHTMLs with the .components-drop-area innerHTMLs:

let dropAreas = document.querySelectorAll('.components-drop-area');
for (let i = 0; i < dropAreas.length; i++) {
  dropAreas[i].parentNode.innerHTML = dropAreas[i].innerHTML;
}

// The <div> contents have now been extracted, and the <div> elements removed
console.log(document.querySelector('table').innerHTML);
<table>
  <tbody>
    <tr>
      <th>
        <div class="components-drop-area">
          <p>aa</p>
          <p>bb</p>
        </div>
      </th>
    </tr>
    <tr>
      <th>
        <div class="components-drop-area">
          <p>cc</p>
          <p>dd</p>
        </div>
      </th>
    </tr>
  </tbody>
</table>
Obsidian Age
  • 41,205
  • 10
  • 48
  • 71
  • The only cautionary tale here would be that you'll destroy any state in the DOM that isn't reflected by the string representation of these elements. Which may not be a big deal in this example, but in another real world example, it may be material. – KyleMit Nov 21 '19 at 22:43
1

If you want to use vanilla DOM operations, you would have to take each child from selected div elements and insert them into that node's parent element using the selected div itself as a reference before removing it. Every DOM node has a reference to its parent and its children, so you can do everything relative to each selected node like so:

  for (const node of document.querySelectorAll("table .components-drop-area")) {
    const parent = node.parentNode;
    const children = Array.from(node.children);
    for (const child of children) {
      node.removeChild(child);
      parent.insertBefore(child, node);
    }
    parent.removeChild(node);
  }
James O'Doherty
  • 2,186
  • 13
  • 14
  • A+ for vanilla DOM operations - although the output here isn't quite what I think you're intending. The culprit - the for loop on node.children is affected by modifying the children while you're iterating over it - so you only move some of the nodes – KyleMit Nov 21 '19 at 22:40
  • 1
    Good catch. The children HTMLCollection property is indeed updated live with DOM changes. I made a slight edit to my answer to account for this. – James O'Doherty Nov 22 '19 at 16:12
1

Here's an adaption of James's vanilla JS implementation that should work

for (const node of document.querySelectorAll("table .components-drop-area")) {
  const parent = node.parentNode;
  while (node.children.length>0) {
    let child = node.children[0];
    node.removeChild(child);
    parent.insertBefore(child, node);
  }
  parent.removeChild(node);
}

Looping over the elements is tricky since we're modifying the collection during the iteration

for (const node of document.querySelectorAll("table .components-drop-area")) {
  const parent = node.parentNode;
  while (node.children.length>0) {
    let child = node.children[0];
    node.removeChild(child);
    parent.insertBefore(child, node);
  }
  parent.removeChild(node);
}

// The <div> contents have now been extracted, and the <div> elements removed
console.log(document.querySelector('table').innerHTML);
<table>
  <tbody>
    <tr>
      <th>
        <div class="components-drop-area">
          <p>aa</p>
          <p>bb</p>
        </div>
      </th>
    </tr>
    <tr>
      <th>
        <div class="components-drop-area">
          <p>cc</p>
          <p>dd</p>
        </div>
      </th>
    </tr>
  </tbody>
</table>
KyleMit
  • 30,350
  • 66
  • 462
  • 664
0

An elegant solution using jQuery would be:

$('.components-drop-area').contents().unwrap();
console.log(document.querySelector('table').innerHTML);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table>
  <tbody>
    <tr>
      <th>
        <div class="components-drop-area">
          <p>aa</p>
          <p>bb</p>
        </div>
      </th>
    </tr>
    <tr>
      <th>
        <div class="components-drop-area">
          <p>cc</p>
          <p>dd</p>
        </div>
      </th>
    </tr>
  </tbody>
</table>

Notes:

Pedro Lobito
  • 94,083
  • 31
  • 258
  • 268
  • 1
    The term **unwrap** is what you're looking for. jQuery implements this nicely, and it turns out trying to roll your own isn't so trivial. If you [look at the source code for `unwrap`](https://github.com/jquery/jquery/blob/d0ce00/src/wrap.js#L65-L69), the code just says "have the parent node replace itself with hits children," but then if you [look at the `replaceWith` source](https://github.com/jquery/jquery/blob/d0ce00cd/src/manipulation.js#L411-L426), and then [the `domManip` function](https://github.com/jquery/jquery/blob/d0ce00/src/manipulation.js#L96) you see the complexity spiral. – romellem Nov 21 '19 at 22:45