20

I'm trying to replace all contents of an element with a document fragment:

var frag = document.createDocumentFragment()

The document fragment is being created just fine. No problems there. I add elements to it just fine, no problems there either. I can append it using element.appendChild(frag). That works just fine too.

I'm trying to create a "replace" method similar to jQuery's HTML. I'm not worried about old-browser compatibility. Is there a magical function to replace all content of an element?

I have tried element.innerHTML = frag.cloneNode(true), (as per every 'replace element content' wiki I could find), that doesn't work. It gives me <div>[object DocumentFragment]</div>.

No libraries, please, not even a jQuery solution.

For clarity, I'm looking for a "magic" solution, I know how to remove all the existing elements one at a time and then append my fragment.

E_net4
  • 27,810
  • 13
  • 101
  • 139
Randy Hall
  • 7,716
  • 16
  • 73
  • 151
  • Just curious, why the aversion to libraries? – Mikeb Nov 09 '12 at 15:23
  • 1
    Project spec beyond my control. Also, kind of fun to try to do things "old school". – Randy Hall Nov 09 '12 at 15:23
  • @pimvdb essentially, yes. But I'd like to do it in one swoop, to avoid page re-draws (I could innerHTML = "" then appendChild, but that's 2 re-draws). – Randy Hall Nov 09 '12 at 15:24
  • @Randy Hall: Hm, well, since a fragment only exists at one place at a time, you could replace each child node again and again, so that they are implicitly removed: http://jsfiddle.net/tMGFM/. But I'm not sure if that's what you want - just `element.innerHTML = "";` and then appending should not do a redraw in between. – pimvdb Nov 09 '12 at 15:28
  • @pimvdb that should be an answer, not a comment. It totally works. However, there's two issues still: 1) I'd like an answer without the loop (if possible) and 2) theoretically, the browser redraws each time there's a node change. This will perform a node change for each element I'm replacing. Right direction thought! – Randy Hall Nov 09 '12 at 15:31
  • Maybe appending the fragment to a detached element (to avoid a redraw) and then transfering the `innerHTML` is what you want? http://jsfiddle.net/tMGFM/1/ Still, I've never seen redraws when manipulating multiple times in a row. – pimvdb Nov 09 '12 at 15:36
  • pimvdb, you are way too fast, but I have the same solution :) – Thomas Jones Nov 09 '12 at 15:41

3 Answers3

25

Have you tried replaceChild

something like this

element.parentNode.replaceChild(frag, element)

source: https://developer.mozilla.org/en-US/docs/DOM/Node.replaceChild

original jsFiddle: http://jsfiddle.net/tomprogramming/RxFZA/

EDIT: ahh, I didn't see replace contents. Well, just remove them first!

element.innerHTML = "";
element.appendChild(frag);

jsfiddle: http://jsfiddle.net/tomprogramming/RxFZA/1/

note that in the jsfiddle, I only use jquery to hook up the button, the entirety of the click handler is raw javascript.

Edit2: also suggested by pimvdb, but just append the new stuff to a detached element and replace.

var newElement = element.cloneNode();
newElement.innerHTML = "";
newElement.appendChild(frag);
element.parentNode.replaceChild(newElement, element);

http://jsfiddle.net/tomprogramming/RxFZA/3/

Thomas Jones
  • 4,892
  • 26
  • 34
  • Not quite, sorry. This replaces the element with the fragment, I need to replace the element's content with the fragment. – Randy Hall Nov 09 '12 at 15:26
  • 1
    Better! Ideally, I could do it in one go, so as to avoid a browser redraw on `element.innerHTML = ""` - however, this is probably the best solution so far (also suggested by @pimvdb). Still hoping someone has something magic! – Randy Hall Nov 09 '12 at 15:33
  • See answer below, very similar to your element clone method but with ranges. – Randy Hall Nov 09 '12 at 15:47
7

2017:
Try this Magic answer from ContentEditable field and Range

var range = document.createRange(); // create range selection 
range.selectNodeContents($element); // select all content of the node
range.deleteContents() // maybe there is replace command but i'm not find it
range.insertNode(frag)
pery mimon
  • 7,713
  • 6
  • 52
  • 57
1

EDIT (cause my original answer was just plain dumb):

var rep = document.createElement("div");
rep.appendChild(frag);
element.innerHTML = rep.innerHTML;
Randy Hall
  • 7,716
  • 16
  • 73
  • 151
  • Looks like this strips all elements and only sets the textual contents. Personally, I think all "magical" solutions are a bit... odd. `removeChild`/`appendChild(frag)` should work fine. – pimvdb Nov 09 '12 at 15:48
  • In old IE this will only grab text I believe. However, I'm not worried about anything but the most recent browsers (and mostly just mobile). I'll edit to reflect this in my answer. – Randy Hall Nov 09 '12 at 15:50
  • Well, it fails for me on Chrome 23. Just so you know :) – pimvdb Nov 09 '12 at 15:52
  • Nevermind, you're completely correct. It is destroying html elements. -1 to my own answer =/ – Randy Hall Nov 09 '12 at 15:52
  • if you're going to go with ranges, I'd suggest taking a look at http://code.google.com/p/rangy/ (I know, no libraries). It normalizes alot of inconsistencies across browsers – Thomas Jones Nov 09 '12 at 15:54