2

I'm wondering is there a way to convert this:

<body>
    Foo
    Bar
    <div>Abc</div>
    <div>Xyz</div>
</body>

To this:

<body>
    <div id="new">
        Foo
        Bar
    </div>
    <div>Abc</div>
    <div>Xyz</div>
</body>

As a novice, I know basic things about innerHTML and jQuery wrap, but code like this one:

var foo = document.body.innerHTML;
console.log(foo);

gives me the full body, include Abc and Xyz divs. So, it doesn't resolve the problem.

Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
user90726
  • 939
  • 1
  • 8
  • 28
  • Should newline characters between `"Foo"` and `"Bar"` be preserved? Or should only `.textContent` of `#text` nodes be set as `.innerHTML` of dynamically created `
    ` element?
    – guest271314 Jan 06 '17 at 17:25
  • I'm working on Markdown extension for Chrome, so I think, the newline characters should be preserved... – user90726 Jan 06 '17 at 17:32
  • @guest271314 Sorry, maybe I forgot to mention you in previous comment – user90726 Jan 06 '17 at 17:40

3 Answers3

4

You can achieve this by using contents().filter() to retrieve the text nodes and then wrapAll() to place them inside a new div element. Try this:

$('body').contents().filter(function() {
  return this.nodeType == 3 && this.textContent != '';
}).wrapAll('<div id="new" />');
/* only to show the new div exists... */
#new { border: 1px solid #C00; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Foo
Bar
<div>Abc</div>
<div>Xyz</div>
Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
  • Thank you very much! But as I understand, it doent's preserve newline character between Foo and Bar. Is there a way to fix it? – user90726 Jan 06 '17 at 17:42
  • Do you need that newline? Multiple whitespace is irrelevant in html – Rory McCrossan Jan 06 '17 at 18:11
  • Not want to bother you, but may be you could also show how to include `Abc` div into filtering, but without `Xyz` div? In any case, I accepted the answer. – user90726 Jan 06 '17 at 18:36
  • Sure - you can use `filter()` to do that too: `$('div').filter(function() { return $(this).text() == 'Abc'; });` although it would be much better if you could put a class or id on the div and select it by that instead – Rory McCrossan Jan 06 '17 at 19:00
3

A non-jQuery solution:

You can iterate over all child nodes of the body and move every text node to a new element until you reach the first element node. Example:

var div = document.createElement('div');
div.id = 'new';
var childNodes = document.body.childNodes;
while (childNodes[0].nodeType !== Node.ELEMENT_NODE) {
  div.appendChild(childNodes[0]);
}
document.body.insertBefore(div, childNodes[0]);
#new {
  color: red;
}
Foo
Bar
<div>Abc</div>
<div>Xyz</div>
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Thank you very much! But as I understand, it doent's preserve newline character between Foo and Bar. Is there a way to fix it? – user90726 Jan 06 '17 at 17:42
  • It does preserve them. But browsers treat line breaks as ordinary white space. If you want to *render* newlines use a `pre` element instead of a `div`. – Felix Kling Jan 06 '17 at 17:43
1

You can use .contents(), .filter(), .prependTo(). To preserve new line characters, set #new css white-space property to pre.

var nodes = $("body").contents().filter(function() {
  return this.nodeType === 3 && /Foo|Bar/.test(this.textContent) 
});

$("<div></div>", {
  id:"new",
  html: nodes,
  css: {whiteSpace: "pre"}
}).prependTo("body")
#new {
  color: sienna;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>

Foo

Bar

<div>Abc</div>

<div>Xyz</div>

</body>
guest271314
  • 1
  • 15
  • 104
  • 177