139

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?

Christopher Cooper
  • 1,910
  • 4
  • 20
  • 25

11 Answers11

231

Once a dom element is created, the tag is immutable, I believe. You'd have to do something like this:

$(this).replaceWith($('<h5>' + this.innerHTML + '</h5>'));
Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
mishac
  • 3,288
  • 1
  • 21
  • 19
  • 2
    Please see my comment below...changing document structure to apply style is not the best approach. – jrista May 28 '09 at 01:38
  • 44
    Wouldn't this clobber any attributes that might be on the element you replaced? Could lead to some unexpected behavior due to deleted style attributes, data attributes, etc... – Xavi Dec 24 '11 at 14:18
  • 9
    `"<" + el.outerHTML.replace(/(^<\w+|\w+>$)/g, "H5") + ">";` Or pluggable jQuery function: [link](http://jsfiddle.net/emtdkwv6/1/) – basil Feb 11 '15 at 16:36
70

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);
        });
    }
});
Orwellophile
  • 13,235
  • 3
  • 69
  • 45
  • 2
    Yup. And for the record, there are actually a lot of very valid reasons you might want to change a tag. e.g. if you had DIV tags within a SPAN, which is hella non-standard. I have gotten a lot of use out of this function while working with the strict standards of princexml for pdb publishing. – Orwellophile Dec 18 '13 at 08:08
  • 1
    Looks really nice, though unfortunatelly it loses all events of the replaced element. Perhaps that could be handled too — would be awesome! – NPC Dec 24 '13 at 14:18
  • @NPC that's life in the big city. if you are going to replace elements, there are going to be casualties. i'm sure there is someone out there who knows the relevant jquery to clone the events :) – Orwellophile Dec 27 '13 at 23:08
  • @NPC - Okay, I wrote a new version.. this will keep the structure of all the sub-elements intact. If there is an event on the actual tag your changing, tough. – Orwellophile Jan 15 '14 at 07:42
  • Your line 'return node' in your plugin throws an error, as node is undefined. Just a headsup for people using this great plugin! – Frank van Luijn Jun 18 '14 at 11:52
  • @FrankvanLuijn - Thanks, since I can see I'm not using the return value, I just commented it out. Should work now. – Orwellophile Jun 24 '14 at 14:17
  • 1
    @FrankvanLuijn @orwellophile the `return node;` should actually be `return this;` as shown in the "old version" of the plugin. This is essentiall for chaining events together like `$("tr:first").find("td").clone().replaceTag("li").appendTo("ul#list")` – Cole Lawrence Jun 24 '14 at 18:40
  • Also return the each statement: ``` return this.each(function() { jQuery.replaceTag(this, newTagObj, keepProps); }); ``` – Cole Lawrence Jun 24 '14 at 18:50
  • @ColeLawrence I would agree with both statements, but regards "return this", I can't imagine being brave enough to chain onto the end of such a recontructive function, but maybe someone will be braver than me. (Editing source now). I did say I was waiting for a real jquery expert, didn't I? :) – Orwellophile Oct 15 '14 at 13:27
  • It seems this script will not convert self closed elements (input) to normal ones (div). – certainlyakey Dec 06 '14 at 19:01
  • maybe copy the node id as well along with the props. – Edi Dec 10 '14 at 16:23
  • @certainlyakey please provide an example in jsfiddle – Orwellophile Dec 13 '14 at 05:18
  • @edi the id should be copied anyway, in the line: $.extend(newTag.attributes, currentElem.attributes); – Orwellophile Dec 13 '14 at 05:19
  • It seems not working with IE9 since you use classList – Freddy Boucher Jun 26 '15 at 11:41
  • @FreddyBoucher – Sorry, IE9 does not work on my Mac. – Orwellophile Jul 27 '15 at 04:36
  • It seems to break when I do `$('*').replaceTag('marquee')` – TimE Sep 02 '16 at 21:48
  • @TimE so you're effectively trying to replace your `` tag with a marquee, and you're wondering why it's not working? FTR I use it like this: `$('div.class').replaceTag` or `$('#section > div').replaceTag`. Why don't you try something like `$('div, p, span')` or `$('body > div')` – Orwellophile Sep 03 '16 at 01:04
  • 1
    I don't get it, why do you need 2 functions? Do you need both? Sorry, I'm lost – João Pimentel Ferreira Feb 26 '20 at 21:49
  • This is how jQuery extensions are/were written, or is/was one of the ways. Check elsewhere for an explanation. @JoãoPimentelFerreira – Orwellophile Mar 11 '20 at 05:22
  • @Orwellophile I know this is quite an old post, but should this still work? My browser is throwing an error on the extend functions saying "Cannot set property length of [object DOMTokenList] which has only a getter" – adaliabooks Jan 21 '21 at 18:09
  • 1
    @adaliabooks It should, I'm use a slightly modified version of it right now in my GreaseMonkey SO context formatter -- https://gist.github.com/sfinktah/f7f8f290e49735742cb20b140dbe0e70 (Google Chrome). And yes -- I just checked the code posted in this article against this page, and it works. `$('div[itemprop="upvoteCount"]').replaceTag($('').addClass('wasDiv'), true);` – Orwellophile Mar 10 '21 at 11:21
10

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>
jrista
  • 32,447
  • 15
  • 90
  • 130
  • The reason I'm asking to change the element/tag is because I am attempting to change a tag (an
    , though the type is irrelevant) to an when an "edit" button is clicked elsewhere on the page.
    – Christopher Cooper May 28 '09 at 01:46
  • 3
    Even in this case, you shouldn't be modifying your document structure. If you need to display an input in response to an edit butting being clicked, then put the input in and stick display:none or visibility:hidden on it. Hide the
    and show the in response to the button click. If you are constantly modifying your document structure...your just asking for a bucketfull of style and layout issues down the road.
    – jrista May 28 '09 at 02:24
  • You really think its better to have a whole mess of hidden edit functions that are intended for site admins only rather than modifying the document structure? – Christopher Cooper May 28 '09 at 02:32
  • 1
    Absolutely. Your javascript is embedded or linked, so if your concern is security, then your approach to security is somewhat flawed. You should render the content of your document according to the role of the user...blending content for roles isn't a secure way to approach the problem. If someone is logged into your system as an admin, render the content for an admin. If they are logged into your system as a reader, render the content for a reader. This completely eliminates content that shouldn't be accessed. Once the content is rendered, use CSS to style your document and show/hide stuff. – jrista May 28 '09 at 04:09
  • 2
    I agree with you and have taken your recommendations into the project. I'll be using toggle() to show admin elements that are only rendered when an admin is logged in. Although unrelated (directly) to the original question, this is probably a better solution than the direction I was originally going. Cheers! – Christopher Cooper May 28 '09 at 05:16
  • 1
    Woo! Bagged another blop of bad security! A win, and rounds for all! (insert beer icon here) – jrista May 28 '09 at 05:24
  • 1
    Relying on client-side javascript for security is actually *WORSE* than no security at all. Why? Because you _think_ you have security, when in fact you do not. – BryanH Jul 27 '11 at 14:42
  • @BryanH: Certainly don't disagree...as noted in my comment above about rendering on server-side the necessary content according to role. The original question did not include anything about security, hence my answer does not either. – jrista Jun 26 '12 at 20:25
8

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.

Cole Lawrence
  • 615
  • 6
  • 13
8

Idea is to wrap the element & unwrap the contents:

function renameElement($element,newElement){

    $element.wrap("<"+newElement+">");
    var $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

Shub
  • 2,686
  • 17
  • 26
0

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);
kevinweber
  • 606
  • 9
  • 11
0

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

<!DOCTYPE html>
<html>
<head>
 <title></title>

<script src="https://code.jquery.com/jquery-1.11.3.js"></script>
<script type="text/javascript">

function wrapClass(klass){
 return 'to-' + klass;
}

function replaceTag(fromTag, toTag){
 
 /** Create selector for all elements you want to change.
   * These should be in form: <fromTag class="to-toTag"></fromTag>
   */
 var currentSelector = fromTag + '.' + wrapClass(toTag);

 /** Select all elements */
 var $selected = $(currentSelector);

 /** If you found something then do the magic. */
 if($selected.size() > 0){

  /** Replace all selected elements */
  $selected.each(function(){

   /** jQuery current element. */
   var $this = $(this);

   /** Remove class "to-toTag". It is no longer needed. */
   $this.removeClass(wrapClass(toTag));

   /** Create elements that will be places instead of current one. */
   var $newElem = $('<' + toTag + '>');

   /** Copy all attributes from old element to new one. */
   var attributes = $this.prop("attributes");
   $.each(attributes, function(){
    $newElem.attr(this.name, this.value);
   });

   /** Add class "to-fromTag" so you can remember it. */
   $newElem.addClass(wrapClass(fromTag));

   /** Place content of current element to new element. */
   $newElem.html($this.html());

   /** Replace old with new. */
   $this.replaceWith($newElem);
  });

  /** It is possible that current element has desired elements inside.
    * If so you need to look again for them.
    */
  replaceTag(fromTag, toTag);
 }
}


</script>

<style type="text/css">
 
 section {
  background-color: yellow;
 }

 div {
  background-color: red;
 }

 .big {
  font-size: 40px;
 }

</style>
</head>
<body>

<button onclick="replaceTag('div', 'section');">Section -> Div</button>
<button onclick="replaceTag('section', 'div');">Div -> Section</button>

<div class="to-section">
 <p>Matrix has you!</p>
 <div class="to-section big">
  <p>Matrix has you inside!</p>
 </div>
</div>

<div class="to-section big">
 <p>Matrix has me too!</p>
</div>

</body>
</html>
zie1ony
  • 1,190
  • 2
  • 14
  • 33
0

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>" );
   });
Mahafuz
  • 71
  • 1
  • 4
0

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.

$(document).on('click', '[data-replace]', function(){
  var replaceTarget = $(this).attr('data-replace').split(',')[0];
  var replaceBy = $(this).attr('data-replace').split(',')[1];
  $(replaceTarget).replaceWith($(replaceBy).html($(replaceTarget).html()));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<p id="abc">Hello World #1</p>
<a href="#" data-replace="#abc,<h1/>">P change with H1 tag</a>
<hr>
<h2 id="xyz">Hello World #2</h2>
<a href="#" data-replace="#xyz,<p/>">H1 change with P tag</a>
<hr>
<b id="bold">Hello World #2</b><br>
<a href="#" data-replace="#bold,<i/>">B change with I tag</a>
<hr>
<i id="italic">Hello World #2</i><br>
<a href="#" data-replace="#italic,<b/>">I change with B tag</a>
Raeesh Alam
  • 3,380
  • 1
  • 10
  • 9
0

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

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())
    })
  }
}

changeTag("div", "p")

console.log($("body").html())
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="A" style="font-size:1em">
  <div class="B" style="font-size:1.1em">A</div>
</div>
<div class="C" style="font-size:1.2em">
  B
</div>
</body>
João Pimentel Ferreira
  • 14,289
  • 10
  • 80
  • 109
-2

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');
});
Dave Ward
  • 59,815
  • 13
  • 117
  • 134
  • The reason I'm asking to change the element/tag is because I am attempting to change a tag (an
    , though the type is irrelevant) to an when an "edit" button is clicked elsewhere on the page.
    – Christopher Cooper May 28 '09 at 01:46