21

Is there an easy way to loop through all td tags and change them to th? (etc).

My current approach would be to wrap them with the th and then remove the td, but then I lose other properties etc.

Matrym
  • 16,643
  • 33
  • 95
  • 140
  • You want to maintain the attributes currently on the ``? – Nick Craver May 12 '10 at 01:56
  • Yeah. I'd want to maintain everything about them. – Matrym May 12 '10 at 01:58
  • 1
    Any reason this can't be done on the server? This isn't trivially done on the client, at least not to handle all possible cases, the an element's tag type is immutable once added to the DOM. – Nick Craver May 12 '10 at 02:10
  • You may want to consider an alternative default DOM structure to achieve whatever effect you aim to accomplish. Your operation can be done, but as Nick said, it's no simple task, especially if your number of td tags can be a variable amount. – ground5hark May 12 '10 at 02:25

7 Answers7

25

jQuery.replaceTagName

The following is a jQuery plugin to replace the tag name of DOM elements.

Source

(function($) {
    $.fn.replaceTagName = function(replaceWith) {
        var tags = [],
            i    = this.length;
        while (i--) {
            var newElement = document.createElement(replaceWith),
                thisi      = this[i],
                thisia     = thisi.attributes;
            for (var a = thisia.length - 1; a >= 0; a--) {
                var attrib = thisia[a];
                newElement.setAttribute(attrib.name, attrib.value);
            };
            newElement.innerHTML = thisi.innerHTML;
            $(thisi).after(newElement).remove();
            tags[i] = newElement;
        }
        return $(tags);
    };
})(window.jQuery);

Minified Source

(function(e){e.fn.replaceTagName=function(t){var n=[],r=this.length;while(r--){var i=document.createElement(t),s=this[r],o=s.attributes;for(var u=o.length-1;u>=0;u--){var a=o[u];i.setAttribute(a.name,a.value)}i.innerHTML=s.innerHTML;e(s).after(i).remove();n[r]=i}return e(n)}})(window.jQuery);

Usage

Include the above minified source in your javascript after jQuery.

Then you can use the plugin like this:

$('div').replaceTagName('span'); // replace all divs with spans

Or in your case this:

$('td').replaceTagName('th');

jQuery selectors work as expected

$('.replace_us').replaceTagName('span'); // replace all elements with "replace_us" class with spans
$('#replace_me').replaceTagName('div'); // replace the element with the id "replace_me"

More resources

jsFiddle with Qunit tests

Will
  • 7,225
  • 2
  • 23
  • 14
16

Completely untested, but giving this a whirl:

$("td").each(function(index) {
  var thisTD = this;
  var newElement = $("<th></th>");
  $.each(this.attributes, function(index) {
    $(newElement).attr(thisTD.attributes[index].name, thisTD.attributes[index].value);
  });
  $(this).after(newElement).remove();
});

I'm looking and looking at it, and I can't think of a reason why it wouldn't work!

1) loop through each td element
2) create a new th element
3) for each of those td's, loop over each of its attributes
4) add that attribute and value to the new th element
5) once all attributes are in place, add the element to the DOM right after the td, and remove the td

Edit: works fine: http://jsbin.com/uqofu3/edit

GlenCrawford
  • 3,359
  • 3
  • 26
  • 34
  • 3
    Works great! And if you want to copy the element inner html you can add this $(newElement).html($(this).html()); before the last line. – idFlood Aug 30 '13 at 07:29
5
$("td").each(function() {
  var tmp = $('<div/>').append($(this).clone(true)).html().replace(/td/i,'th');
  $(this).after(tmp).remove();
});

or pure DOM

function replaceElm(oldTagName, newTagName, targetElm) {
  var target = targetElm || window.document;
  var allFound = target.getElementsByTagName(oldTagName);
  for (var i=0; i<allFound.length; i++) {
    var tmp = document.createElement(newTagName);
    for (var k=0; k<allFound[i].attributes.length; k++) {
      var name = allFound[i].attributes[k].name;
      var val = allFound[i].attributes[k].value;
      tmp.setAttribute(name,val);
    }
    tmp.innerHTML = allFound[i].innerHTML;
    allFound[i].parentNode.insertBefore(tmp, allFound[i]);
    allFound[i].parentNode.removeChild(allFound[i]);
  }
}

replaceElm('td','th',document.getElementsByTagName('table')[0]);

DOM is always faster: http://jsperf.com/replace-tag-names

user824951
  • 71
  • 1
  • 3
3

This might work, but I haven't tested it extensively:

var tds = document.getElementsByTagName("td");
while(tds[0]){
    var t = document.createElement("th");
    var a = tds[0].attributes;
    for(var i=0;i<a.length;i++) t.setAttribute(a[i].nodeName,a[i].nodeValue);
    t.innerHTML = tds[0].innerHTML;
    tds[0].parentNode.insertBefore(t,tds[0]);
    tds[0].parentNode.removeChild(tds[0]);
}

I hope it helps in some way.

tau
  • 6,499
  • 10
  • 37
  • 60
0

Well this question is pretty old but this could help anyway: the only jQuery plugin that actually works as expected (you can't reuse the returned object in the other one, to add attributes for example):

jQuery.fn.extend({
    replaceTagName: function(replaceWith) {
        var tags=[];
        this.each(function(i,oldTag) {
            var $oldTag=$(oldTag);
            var $newTag=$($("<div />").append($oldTag.clone(true)).html().replace(new RegExp("^<"+$oldTag.prop("tagName"),"i"),"<"+replaceWith));
            $oldTag.after($newTag).remove();
            tags.push($newTag.get(0));
        });

        return $(tags);
    }
});

Besides the basic $("td").replaceTagName("th"); you can also chain calls like $("td").replaceTagName("th").attr("title","test");

Minified version:

jQuery.fn.extend({replaceTagName:function(a){var b=[];this.each(function(d,c){var e=$(c);var f=$($("<div />").append(e.clone(true)).html().replace(new RegExp("^<"+e.prop("tagName"),"i"),"<"+a));e.after(f).remove();b.push(f.get(0))});return $(b)}});
Vincent
  • 938
  • 13
  • 20
0

This is a bit cleaner than @GlenCrawford's answer and additionally copies the children of the replaced element.

$('td').each(function(){
    var newElem = $('<th></th>', {html: $(this).html()});
    $.each(this.attributes, function() {
        newElem.attr(this.name, this.value);
    });
    $(this).replaceWith(newElem);
});
jstice4all
  • 1,878
  • 4
  • 22
  • 33
0

Slight addition to @GlenCrawford answer, to also preserve inner text with the line:

newElement.text($(value).text());

All together now:

$("td").each(function(index) {
  var thisTD = this;
  var newElement = $("<th></th>");
  newElement.text($(value).text());
  $.each(this.attributes, function(index) {
    $(newElement).attr(thisTD.attributes[index].name, thisTD.attributes[index].value);
  });
  $(this).after(newElement).remove();
});
slipsec
  • 3,004
  • 3
  • 34
  • 46