28

Can I replace one HTML element with another?

I want to change an <a> to a <div>, but I don't want to make the content blank.

From:

<a data-text="text">content</a>

to:

<div data-text="text">content</div>

Any idea?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Michael Antonius
  • 942
  • 2
  • 11
  • 20

9 Answers9

26

Use the ChildNode.replaceWith() method to create a new node and replace the old node with the new one. As exemplified in MDN docs:

var parent = document.createElement("div");
var child = document.createElement("p");
parent.appendChild(child);
var span = document.createElement("span");

child.replaceWith(span);

console.log(parent.outerHTML);
// "<div><span></span></div>"

More information is available in this answer.

TylerH
  • 20,799
  • 66
  • 75
  • 101
Santiago M. Quintero
  • 1,237
  • 15
  • 22
  • 1
    This is missing the attributes transfer. Check my solution for a more complete one - https://stackoverflow.com/a/65090521/13817884 – dreamLo May 24 '21 at 16:11
18

No. You cannot change the tagName (nodeName) of a DOM node.

You can only create a new node of the desired type, copy all attributes (and maybe properties) and move the child nodes. However, this way would still lose inaccessible things like any attached event listeners. This technique is used, for example, when you want to change the type of an input in IE.

However, there is absolutely no reason to change an a into a div; they have completely different semantics (also behaviour and layout).

TylerH
  • 20,799
  • 66
  • 75
  • 101
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
13

Basically you create a new element, transfer the attributes to it, then transfer the children and at the end you replace the element with the new one.

I have also provided an example for case sensitive elements like <svg>. For case sensitive purposes, you shouldn't use innerHTML. Instead use the while loop with appendChild.

function changeTag (node, tag) {
  const clone = createElement(tag)
  for (const attr of node.attributes) {
    clone.setAttributeNS(null, attr.name, attr.value)
  }
  while (node.firstChild) {
    clone.appendChild(node.firstChild)
  }
  node.replaceWith(clone)
  return clone
}

function createElement (tag) {
  if (tag === 'svg') {
    return document.createElementNS('http://www.w3.org/2000/svg', 'svg')
  } else {
    return document.createElementNS('http://www.w3.org/1999/xhtml', tag)
  }
}

const node1 = document.getElementById('node1')
console.log(node1.outerHTML)
console.log(changeTag(node1, 'a').outerHTML)

const node2 = document.getElementById('node2')
console.log(node2.outerHTML)
console.log(changeTag(node2, 'svg-custom').outerHTML)
<p id="node1" class="x" rel="follow">
  Some <b class="a1">bold</b> text
</p>

 <svg id="node2" height="150" width="400">
  <defs>
    <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
      <stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
      <stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
    </linearGradient>
  </defs>
  <ellipse cx="200" cy="70" rx="85" ry="55" fill="url(#grad1)" />
</svg>
TylerH
  • 20,799
  • 66
  • 75
  • 101
dreamLo
  • 1,612
  • 12
  • 17
5

sure, but why?

var a = ​document.getElementsByTagName('a');
var src, el, attrs;
for(var i=0,l=a.length;i<l;i++) {
    src = a[i];
    el = document.createElement('div');
    attrs = src.attributes;
    for(var j=0,k=attrs.length;j<k;j++) {
        el.setAttribute(attrs[j].name, attrs[j].value);
    }
    el.innerHTML = src.innerHTML;
    src.parentNode.replaceChild(el, src);
}
Tetaxa
  • 4,375
  • 1
  • 19
  • 25
2

It would be invalid HTML (div elements do not have a href attribute) and not act like a link anymore.

However, you could emulate the behaviour using JavaScript:

$('div').on('click', function() {
    location.href = $(this).attr('href');
});

But please do not do this. It breaks middle-mouse-clicks (for a new tab) for example.

ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
2

Here's a simple way to make the div look and act like an a tag without having to change the tag type:

Click <div style="display:inline; color:blue; text-decoration:underline; cursor:pointer;"
           onclick="window.location='http://example.com/';">here</div>!
Pluto
  • 2,900
  • 27
  • 38
1

Use

var href=$("#aId").attr('href');

$("#aId").replaceWith($("<div"+href+">" + $("#aId").innerHTML + "</div>"));
vusan
  • 5,221
  • 4
  • 46
  • 81
0

Here's a way to change the tag of an element:

// Just provide the element and the new tag

function changeTag(el, newTag) {
    var outerHTML = el.outerHTML // Get the outerHTML of the element
    var tag = el.tagName // Get the tag of the outerHTML
    var r = new RegExp(tag, 'i') // Prepare a RegExp to replace the old tag with the new tag
    outerHTML = outerHTML.replace(r, newTag) // Replace it
    el.outerHTML = outerHTML // Set the outerHTML of the element
}
<span>Span 1</span> -- <span>Span 2</span>
<br>
<button onclick="changeTag(document.querySelector('span'), 'div')">
Change tag
</button>
TylerH
  • 20,799
  • 66
  • 75
  • 101
shreyasm-dev
  • 2,711
  • 5
  • 16
  • 34
0

The short modern vanilla way

const atts = Array.prototype.slice.call(elem.attributes)
elem.outerHTML = `<div ${atts.map(
  attr => (attr.name + '="' + attr.value + '"')
).join(' ')}>${elem.innerHTML}</div>`

This solution will convert your tag name and preserve all attributes without the necessity to remove and add elements from and to the DOM, by yourself. The browser will handle that for you, so you have to reload the element from the DOM to access it after conversion.

Example

let elem = document.getElementById("test")
const tagName = 'div'

const atts = Array.prototype.slice.call(elem.attributes)
elem.outerHTML = `<${tagName} ${atts.map(
  attr => (attr.name + '="' + attr.value + '"')
).join(' ')}>${elem.innerHTML}</${tagName}>`

elem = document.getElementById("test") // reload required
console.log(elem.outerHTML)
<span id="test" data-test="123">I want to be a div.</span>

Don't need to preserve attributes?

elem.outerHTML = `<div id="test">${elem.innerHTML}</div>`

This one-liner will do it, but remember that elem will point to the old element, just like in the above solution.

Example

let elem = document.getElementById("test")
const tagName = 'div'

elem.outerHTML = `<${tagName} id="test">${elem.innerHTML}</${tagName}>`

elem = document.getElementById("test") // reload required
console.log(elem.outerHTML)
<span id="test">I want to be a div.</span>

With these methods, you always have to re-load the object from the DOM to continue to work with it, since the browser will create a new DOM element and place it for you under the hood.

Martin Braun
  • 10,906
  • 9
  • 64
  • 105