3

I have the following HTML, where I want to remove everything starting from the first "a" to the table. Since there is some text that isn't inside a container, I can't figure out how to simply go from one point to another

$('.MyDiv a').nextUntil('.MyDiv table').remove();
$('.MyDiv a').nextUntil('table').remove();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="MyDiv">
  <div>div1</div>
  <div>div2</div>
  <div>div3</div>

  <!- Remove all below ->
  <a>a1</a>
  <a>a2</a>
  <a>a3</a>
  <ul><li>ul1</li></ul>
  Text with no wrap more text with no wrap
  <div>div4</div>
  Text with no wrap
  <!- Remove all above ->

  <table><tr><td>table</td></tr></table>
</div>

New HTML should look like this

<div class="MyDiv">
  <div>div1</div>
  <div>div2</div>
  <div>div3</div>
  <table><tr><td>table</td></tr></table>
</div>
mplungjan
  • 169,008
  • 28
  • 173
  • 236
MShack
  • 642
  • 1
  • 14
  • 33
  • The text nodes are the [tricky part](https://stackoverflow.com/questions/298750/how-do-i-select-text-nodes-with-jquery) here. Are there text nodes directly inside myDiv that you need to preserve? Or is everything you want to keep contained in a child element of .MyDiv? – Daniel Beck May 14 '18 at 16:24
  • yes , tried that also , added that to question as well , thanks – MShack May 14 '18 at 16:26
  • Daniel , text nodes are within the MyDiv , they are inside that div , along with other items i need to keep – MShack May 14 '18 at 16:28
  • @MShack - meaning the text nodes before the table should be removed? – Anthony May 14 '18 at 16:31
  • correct anthony – MShack May 14 '18 at 16:34

3 Answers3

5

Not as simple as I thought. I am using contents and a test

Possibly addBack could be used but this is easy to read and works

var found = false;
$('.MyDiv').contents().each(function() { 

  // debugging 
  var whitespace=this.nodeType==3, comment=this.nodeType==8, tag=this.nodeType==1;
  if (!whitespace) console.log(tag?this.tagName:"comment",this.textContent);
  // end debugging

  if (this.tagName=="A") found=true;
  if (found) { 
    // stop at first table - to stop at comment, test nodeType==8 instead
    if (this.tagName =="TABLE") {
      console.log("stopped");
      return false; 
    }
    else $(this).get(0).remove(); // dom / textnodes 
  }  
});
.as-console-wrapper { max-height: 3.5em !important; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="MyDiv">
  <div>d1</div>
  <div>d2</div>
  <div>d3</div>

  <!- Remove all below ->
  <a>first a</a>
  <a>second a</a>
  <a>third a</a>
  <ul><li>an LI</li></ul>
  Text with no wrap more text with no wrap
  <div>d4</div>
  Text with no wrap
  <!- Remove all above ->
  <table><tr><td>Table <a href="">An embedded anchor</a></td></tr></table>
  After table
</div>
mplungjan
  • 169,008
  • 28
  • 173
  • 236
  • But in order for this to stop at the first table, doesn't it need some sort of `break` when it reaches the table? – Anthony May 14 '18 at 16:33
  • 1
    Yes: `if (this.tagName =="TABLE") return false; ` https://stackoverflow.com/questions/1784780/how-to-break-out-of-jquery-each-loop – mplungjan May 14 '18 at 16:33
  • Tested and confirmed. But I guess it doesn't seem intuitive since I would expect it to just be returning `false` for that iteration. In my mind, it is running the function on each member in `.contents` not running the loop inside the function (if that makes sense). But again, tested and works. Awesome job. Wondering if it could be abstracted to work with actual jquery selectors. Something like `$('a').removeUntil('table')` but where any selector could be plugged in instead of relying on tag names. – Anthony May 14 '18 at 16:40
  • Sure - $(this).is(selector) instead of the dom test this.tagName – mplungjan May 14 '18 at 16:41
  • How do you access the two selectors in that scenario? Would the one passed to `removeUntil()` be the `this`? and the one it was chained from be a `parent` or some such? Now I'm just curious. – Anthony May 14 '18 at 16:46
  • The second: `if ($(this).is("table")) return false` and the first could be `$("a").first()` – mplungjan May 14 '18 at 16:48
  • I made something. Feel free to critique or improve. – Anthony May 14 '18 at 18:09
  • after implementing this , there is an issue. If you have an "a" tag inside the table , it will also remove it as well , even though its supposed to stop at the table – MShack May 18 '18 at 22:58
  • I have updated the code. I do not see this happening. Are you sure you return false when the tag you want to stop at is reached? – mplungjan May 19 '18 at 06:16
1

One way is to traverse each element between the two comment nodes and remove those one by one. This solution assumes that the comments' contents are fixed, and solely depends on those.

var comments = $(".MyDiv").contents()
  .filter(function() {
    return this.nodeType == 8 && this.textContent.trim() === 'Remove all below' || this.textContent.trim() === 'Remove all above';
  }).get();

var elem = comments[0].nextSibling;

while (elem !== comments[1]) {
  var next = elem.nextSibling;
  elem.remove();
  elem = next;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="MyDiv">
  <div></div>
  <div></div>
  <div></div>
  <p>before comment</p>
  <!-- Remove all below -->
  <a></a>
  <a></a>
  <a></a>
  <ul></ul>
  Text with no wrap more text with no wrap
  <div></div>
  Text with no wrap
  <!-- Remove all above -->
  <p>after comment</p>

  <table></table>
</div>
31piy
  • 23,323
  • 6
  • 47
  • 67
1

Just for fun, I took @mplungjan's answer and turned it into a function removeUntil that could be used like:

$('.MyDiv a').removeUntil('table');

But since any a elements after the table would also match $('.MyDiv a'), you would want to actually use:

$('.MyDiv a:first-of-type').removeUntil('table');

And since you probably don't want to remove content that is deeper than the first level of .MyDiv (like any as found inside the table), you would actually want to use:

$('.MyDiv > a:first-of-type').removeUntil('table');

Here is the removeUntil function code:

jQuery.fn.extend({
  removeUntil: function(untilSelector) {
    this.each(function() {
      var theStart = $(this);
      var theParent = theStart.parent();
      var found = false;
      theParent.contents().each(function() {
        if ($(this).is(theStart)) {
          found = true;
        }
        if (found) {
          if ($(this).is(untilSelector)) {
            return false;
          } else {
            $(this).get(0).remove(); // dom / textnodes 
          }
        }
      });
    });
  }
});

A simple demo based on your original HTML, but with two .MyDiv sections: https://jsfiddle.net/b7fkm8g3/

Anthony
  • 36,459
  • 25
  • 97
  • 163
  • @MShack - Well technically, it works great. It just removes the contents of that parent `a` as expected. Which is why this would be a pretty fierce function to actually put in place. If the user of said function only wanted it to remove the contents at the `.MyDiv` top child level, they should use `$('.MyDiv>a:first-of-type').removeUntil('table');` But solid catch on how this function wouldn't (and shouldn't) assume the selector meant something other than what it normally means. Demo with better selector and `a` in table. https://jsfiddle.net/o0y1nw1q/1/ – Anthony May 18 '18 at 23:08
  • Also note that if you used the original `$('.MyDiv a:first-of-type').removeUntil('table');` on the demo I just linked, it doesn't wipe out the entire table, it just wipes out the contents of the parent `td` (as designed). https://jsfiddle.net/o0y1nw1q/2/ – Anthony May 18 '18 at 23:09
  • @MShack - Updated to reflect that for the OP's specific use-case, that the better selector would be `.MyDiv > a:first-of-type`. Thanks again. – Anthony May 18 '18 at 23:22