[jquery] Use jQuery to change an HTML tag?

Is this possible?

example:

$('a.change').click(function(){
//code to change p tag to h5 tag
});


<p>Hello!</p>
<a id="change">change</a>

So clicking the change anchor should cause the <p>Hello!</p> section to change to (as an example) an h5 tag so you'd end up with <h5>Hello!</h5> after the click. I realize you can delete the p tag and replace it with an h5, but is there anyway to actually modify an HTML tag?

This question is related to jquery

The answer is


I noticed that the first answer wasn't quite what I needed, so I made a couple of modifications and figured I'd post it back here.

Improved replaceTag(<tagName>)

replaceTag(<tagName>, [withDataAndEvents], [withDataAndEvents])

Arguments:

  • tagName: String
    • The tag name e.g. "div", "span", etc.
  • withDataAndEvents: Boolean
    • "A Boolean indicating whether event handlers should be copied along with the elements. As of jQuery 1.4, element data will be copied as well." info
  • deepWithDataAndEvents: Boolean,
    • A Boolean indicating whether event handlers and data for all children of the cloned element should be copied. By default its value matches the first argument's value (which defaults to false)." info

Returns:

A newly created jQuery element

Okay, I know there are a few answers here now, but I took it upon myself to write this again.

Here we can replace the tag in the same way we use cloning. We are following the same syntax as .clone() with the withDataAndEvents and deepWithDataAndEvents which copy the child nodes' data and events if used.

Example:

$tableRow.find("td").each(function() {
  $(this).clone().replaceTag("li").appendTo("ul#table-row-as-list");
});

Source:

$.extend({
    replaceTag: function (element, tagName, withDataAndEvents, deepWithDataAndEvents) {
        var newTag = $("<" + tagName + ">")[0];
        // From [Stackoverflow: Copy all Attributes](http://stackoverflow.com/a/6753486/2096729)
        $.each(element.attributes, function() {
            newTag.setAttribute(this.name, this.value);
        });
        $(element).children().clone(withDataAndEvents, deepWithDataAndEvents).appendTo(newTag);
        return newTag;
    }
})
$.fn.extend({
    replaceTag: function (tagName, withDataAndEvents, deepWithDataAndEvents) {
        // Use map to reconstruct the selector with newly created elements
        return this.map(function() {
            return jQuery.replaceTag(this, tagName, withDataAndEvents, deepWithDataAndEvents);
        })
    }
})

Note that this does not replace the selected element, it returns the newly created one.


Here's an extension that will do it all, on as many elements in as many ways...

Example usage:

keep existing class and attributes:

$('div#change').replaceTag('<span>', true);

or

Discard existing class and attributes:

$('div#change').replaceTag('<span class=newclass>', false);

or even

replace all divs with spans, copy classes and attributes, add extra class name

$('div').replaceTag($('<span>').addClass('wasDiv'), true);

Plugin Source:

$.extend({
    replaceTag: function (currentElem, newTagObj, keepProps) {
        var $currentElem = $(currentElem);
        var i, $newTag = $(newTagObj).clone();
        if (keepProps) {//{{{
            newTag = $newTag[0];
            newTag.className = currentElem.className;
            $.extend(newTag.classList, currentElem.classList);
            $.extend(newTag.attributes, currentElem.attributes);
        }//}}}
        $currentElem.wrapAll($newTag);
        $currentElem.contents().unwrap();
        // return node; (Error spotted by Frank van Luijn)
        return this; // Suggested by ColeLawrence
    }
});

$.fn.extend({
    replaceTag: function (newTagObj, keepProps) {
        // "return" suggested by ColeLawrence
        return this.each(function() {
            jQuery.replaceTag(this, newTagObj, keepProps);
        });
    }
});

You can achieve by data-* attribute like data-replace="replaceTarget,replaceBy" so with help of jQuery to get replaceTarget & replaceBy value by .split() method after getting values then use .replaceWith() method.
This data-* attribute technique to easily manage any tag replacement without changing below (common code for all tag replacement).

I hope below snippet will help you lot.

_x000D_
_x000D_
$(document).on('click', '[data-replace]', function(){_x000D_
  var replaceTarget = $(this).attr('data-replace').split(',')[0];_x000D_
  var replaceBy = $(this).attr('data-replace').split(',')[1];_x000D_
  $(replaceTarget).replaceWith($(replaceBy).html($(replaceTarget).html()));_x000D_
});
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>_x000D_
_x000D_
<p id="abc">Hello World #1</p>_x000D_
<a href="#" data-replace="#abc,<h1/>">P change with H1 tag</a>_x000D_
<hr>_x000D_
<h2 id="xyz">Hello World #2</h2>_x000D_
<a href="#" data-replace="#xyz,<p/>">H1 change with P tag</a>_x000D_
<hr>_x000D_
<b id="bold">Hello World #2</b><br>_x000D_
<a href="#" data-replace="#bold,<i/>">B change with I tag</a>_x000D_
<hr>_x000D_
<i id="italic">Hello World #2</i><br>_x000D_
<a href="#" data-replace="#italic,<b/>">I change with B tag</a>
_x000D_
_x000D_
_x000D_


Is there a specific reason that you need to change the tag? If you just want to make the text bigger, changing the p tag's CSS class would be a better way to go about that.

Something like this:

$('#change').click(function(){
  $('p').addClass('emphasis');
});

Rather than change the type of tag, you should be changing the style of the tag (or rather, the tag with a specific id.) Its not a good practice to be changing the elements of your document to apply stylistic changes. Try this:

$('a.change').click(function() {
    $('p#changed').css("font-weight", "bold");
});

<p id="changed">Hello!</p>
<a id="change">change</a>

This is my solution. It allows to toggle between tags.

_x000D_
_x000D_
<!DOCTYPE html>_x000D_
<html>_x000D_
<head>_x000D_
 <title></title>_x000D_
_x000D_
<script src="https://code.jquery.com/jquery-1.11.3.js"></script>_x000D_
<script type="text/javascript">_x000D_
_x000D_
function wrapClass(klass){_x000D_
 return 'to-' + klass;_x000D_
}_x000D_
_x000D_
function replaceTag(fromTag, toTag){_x000D_
 _x000D_
 /** Create selector for all elements you want to change._x000D_
   * These should be in form: <fromTag class="to-toTag"></fromTag>_x000D_
   */_x000D_
 var currentSelector = fromTag + '.' + wrapClass(toTag);_x000D_
_x000D_
 /** Select all elements */_x000D_
 var $selected = $(currentSelector);_x000D_
_x000D_
 /** If you found something then do the magic. */_x000D_
 if($selected.size() > 0){_x000D_
_x000D_
  /** Replace all selected elements */_x000D_
  $selected.each(function(){_x000D_
_x000D_
   /** jQuery current element. */_x000D_
   var $this = $(this);_x000D_
_x000D_
   /** Remove class "to-toTag". It is no longer needed. */_x000D_
   $this.removeClass(wrapClass(toTag));_x000D_
_x000D_
   /** Create elements that will be places instead of current one. */_x000D_
   var $newElem = $('<' + toTag + '>');_x000D_
_x000D_
   /** Copy all attributes from old element to new one. */_x000D_
   var attributes = $this.prop("attributes");_x000D_
   $.each(attributes, function(){_x000D_
    $newElem.attr(this.name, this.value);_x000D_
   });_x000D_
_x000D_
   /** Add class "to-fromTag" so you can remember it. */_x000D_
   $newElem.addClass(wrapClass(fromTag));_x000D_
_x000D_
   /** Place content of current element to new element. */_x000D_
   $newElem.html($this.html());_x000D_
_x000D_
   /** Replace old with new. */_x000D_
   $this.replaceWith($newElem);_x000D_
  });_x000D_
_x000D_
  /** It is possible that current element has desired elements inside._x000D_
    * If so you need to look again for them._x000D_
    */_x000D_
  replaceTag(fromTag, toTag);_x000D_
 }_x000D_
}_x000D_
_x000D_
_x000D_
</script>_x000D_
_x000D_
<style type="text/css">_x000D_
 _x000D_
 section {_x000D_
  background-color: yellow;_x000D_
 }_x000D_
_x000D_
 div {_x000D_
  background-color: red;_x000D_
 }_x000D_
_x000D_
 .big {_x000D_
  font-size: 40px;_x000D_
 }_x000D_
_x000D_
</style>_x000D_
</head>_x000D_
<body>_x000D_
_x000D_
<button onclick="replaceTag('div', 'section');">Section -> Div</button>_x000D_
<button onclick="replaceTag('section', 'div');">Div -> Section</button>_x000D_
_x000D_
<div class="to-section">_x000D_
 <p>Matrix has you!</p>_x000D_
 <div class="to-section big">_x000D_
  <p>Matrix has you inside!</p>_x000D_
 </div>_x000D_
</div>_x000D_
_x000D_
<div class="to-section big">_x000D_
 <p>Matrix has me too!</p>_x000D_
</div>_x000D_
_x000D_
</body>_x000D_
</html>
_x000D_
_x000D_
_x000D_


Idea is to wrap the element & unwrap the contents:

function renameElement($element,newElement){

    $element.wrap("<"+newElement+">");
    $newElement = $element.parent();

    //Copying Attributes
    $.each($element.prop('attributes'), function() {
        $newElement.attr(this.name,this.value);
    });

    $element.contents().unwrap();       

    return $newElement;
}

Sample usage:

renameElement($('p'),'h5');

Demo


This the quick way to change HTML tags inside your DOM using jQuery. I find this replaceWith() function is very useful.

   var text= $('p').text();
   $('#change').on('click', function() {
     target.replaceWith( "<h5>"+text+"</h5>" );
   });

I came up with an approach where you use a string representation of your jQuery object and replace the tag name using regular expressions and basic JavaScript. You will not loose any content and don't have to loop over each attribute/property.

/*
 * replaceTag
 * @return {$object} a new object with replaced opening and closing tag
 */
function replaceTag($element, newTagName) {

  // Identify opening and closing tag
  var oldTagName = $element[0].nodeName,
    elementString = $element[0].outerHTML,
    openingRegex = new RegExp("^(<" + oldTagName + " )", "i"),
    openingTag = elementString.match(openingRegex),
    closingRegex = new RegExp("(<\/" + oldTagName + ">)$", "i"),
    closingTag = elementString.match(closingRegex);

  if (openingTag && closingTag && newTagName) {
    // Remove opening tag
    elementString = elementString.slice(openingTag[0].length);
    // Remove closing tag
    elementString = elementString.slice(0, -(closingTag[0].length));
    // Add new tags
    elementString = "<" + newTagName + " " + elementString + "</" + newTagName + ">";
  }

  return $(elementString);
}

Finally, you can replace the existing object/node as follows:

var $newElement = replaceTag($rankingSubmit, 'a');
$('#not-an-a-element').replaceWith($newElement);

The following function does the trick and keeps all the attributes. You use it for example like this: changeTag("div", "p")

function changeTag(originTag, destTag) {
  while($(originTag).length) {
    $(originTag).replaceWith (function () {
      var attributes = $(this).prop("attributes");
      var $newEl = $(`<${destTag}>`)
      $.each(attributes, function() {
        $newEl.attr(this.name, this.value);
      });  
      return $newEl.html($(this).html())
    })
  }
}

To be sure that it works, check the following example

_x000D_
_x000D_
function changeTag(originTag, destTag) {_x000D_
  while($(originTag).length) {_x000D_
    $(originTag).replaceWith (function () {_x000D_
      var attributes = $(this).prop("attributes");_x000D_
      var $newEl = $(`<${destTag}>`)_x000D_
      $.each(attributes, function() {_x000D_
        $newEl.attr(this.name, this.value);_x000D_
      });  _x000D_
      return $newEl.html($(this).html())_x000D_
    })_x000D_
  }_x000D_
}_x000D_
_x000D_
changeTag("div", "p")_x000D_
_x000D_
console.log($("body").html())
_x000D_
<body>_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>_x000D_
_x000D_
<div class="A" style="font-size:1em">_x000D_
  <div class="B" style="font-size:1.1em">A</div>_x000D_
</div>_x000D_
<div class="C" style="font-size:1.2em">_x000D_
  B_x000D_
</div>_x000D_
</body>
_x000D_
_x000D_
_x000D_