como mudar um tipo de elemento usando jquery

104

Eu tenho o seguinte código

<b class="xyzxterms" style="cursor: default; ">bryant keil bio</b>

Como eu substituiria a btag por uma h1tag, mas manteria todos os outros atributos e informações?

bammab
fonte
@beanland: Isso não mantém os atributos.
Felix Kling

Respostas:

137

Aqui está uma maneira de fazer isso com jQuery:

var attrs = { };

$.each($("b")[0].attributes, function(idx, attr) {
    attrs[attr.nodeName] = attr.nodeValue;
});


$("b").replaceWith(function () {
    return $("<h1 />", attrs).append($(this).contents());
});

Exemplo: http://jsfiddle.net/yapHk/

Update , aqui está um plugin:

(function($) {
    $.fn.changeElementType = function(newType) {
        var attrs = {};

        $.each(this[0].attributes, function(idx, attr) {
            attrs[attr.nodeName] = attr.nodeValue;
        });

        this.replaceWith(function() {
            return $("<" + newType + "/>", attrs).append($(this).contents());
        });
    };
})(jQuery);

Exemplo: http://jsfiddle.net/mmNNJ/

Andrew Whitaker
fonte
2
@FelixKling: Obrigado, childrennão funcionou, mas contentsfuncionou.
Andrew Whitaker
1
@Andrew Whitaker WOW !!! voce é bom! Então, só para ter certeza de que uso b.class ou b.xyzxterms (xyzxterms é o nome da classe)
bammab
5
@AndrewWhitaker: Se eu não estiver errado, no seu plugin, os atributos do primeiro elemento correspondente serão aplicados a todos os elementos correspondentes. Não é necessariamente o que queremos. Além disso, um erro é gerado quando não há nenhum elemento correspondente no conjunto. Esta é uma versão modificada de seu plug-in que mantém os próprios atributos para cada elemento correspondido e não aciona um erro no conjunto vazio: gist.github.com/2934516
Etienne
2
Isso funciona como um encanto! Exceto que quando o seletor falha em encontrar qualquer elemento correspondente, ele lança uma mensagem de erro para o console porque este [0] é indefinido acessando quebra de atributos. Adicionar uma condição corrige: if (this.length! = 0) {...
ciuncan
1
@ciuncan: Obrigado pelo feedback! Realmente deveria estar embrulhado em um .eachbloco como mostra a resposta abaixo também.
Andrew Whitaker
14

Não tenho certeza sobre jQuery. Com JavaScript simples, você pode fazer:

var new_element = document.createElement('h1'),
    old_attributes = element.attributes,
    new_attributes = new_element.attributes;

// copy attributes
for(var i = 0, len = old_attributes.length; i < len; i++) {
    new_attributes.setNamedItem(old_attributes.item(i).cloneNode());
}

// copy child nodes
do {
    new_element.appendChild(element.firstChild);
} 
while(element.firstChild);

// replace element
element.parentNode.replaceChild(new_element, element);

DEMO

Não tenho certeza de quão compatível com vários navegadores isso é.

Uma variação pode ser:

for(var i = 0, len = old_attributes.length; i < len; i++) {
    new_element.setAttribute(old_attributes[i].name, old_attributes[i].value);
}

Para obter mais informações, consulte Node.attributes [MDN] .

Felix Kling
fonte
O desempenho do seu código é melhor do que o "jQuery puro" (ex. Código de Andrew), mas tem um pequeno problema com as tags internas, veja o itálico neste exemplo com o seu código e o exemplo de referência .
Peter Krauss
Se você corrigir, um "plugin jquery ideal" pode ser definido, chamando sua função pelo jquery-plugin-template.
Peter Krauss
Fixo. O problema era que depois de copiar o primeiro filho, ele não tinha mais um irmão seguinte, então while(child = child.nextSibling)falhou. Obrigado!
Felix Kling
9

@jakov e @Andrew Whitaker

Aqui está uma melhoria adicional para que ele possa lidar com vários elementos de uma vez.

$.fn.changeElementType = function(newType) {
    var newElements = [];

    $(this).each(function() {
        var attrs = {};

        $.each(this.attributes, function(idx, attr) {
            attrs[attr.nodeName] = attr.nodeValue;
        });

        var newElement = $("<" + newType + "/>", attrs).append($(this).contents());

        $(this).replaceWith(newElement);

        newElements.push(newElement);
    });

    return $(newElements);
};
Jazzbo
fonte
3

A resposta de @Jazzbo retornou um objeto jQuery contendo um array de objetos jQuery, que não podiam ser encadeados. Eu mudei para que ele retorne um objeto mais semelhante ao que $ .each teria retornado:

    $.fn.changeElementType = function (newType) {
        var newElements,
            attrs,
            newElement;

        this.each(function () {
            attrs = {};

            $.each(this.attributes, function () {
                attrs[this.nodeName] = this.nodeValue;
            });

            newElement = $("<" + newType + "/>", attrs).append($(this).contents());

            $(this).replaceWith(newElement);

            if (!newElements) {
                newElements = newElement;
            } else {
                $.merge(newElements, newElement);
            }
        });

        return $(newElements);
    };

(Também fiz uma limpeza de código para que ele passe jslint.)

Fiskhandlarn
fonte
Esta parece ser a melhor opção. A única coisa que não entendi é por que você moveu a declaração var para attrs de this.each (). Funciona bem com ele deixado lá: jsfiddle.net/9c0k82sr/1
Jacob C. diz Restabelecer Monica de
Agrupei os vars por causa do jslint: "(Também fiz alguma limpeza de código para que ele passe jslint.)". A ideia por trás disso é tornar o código mais rápido, eu acho (não ter que redeclarar vars dentro de cada eachloop).
fiskhandlarn
2

Só consigo pensar em copiar tudo manualmente: exemplo jsfiddle

HTML

<b class="xyzxterms" style="cursor: default; ">bryant keil bio</b>

Jquery / Javascript

$(document).ready(function() {
    var me = $("b");
    var newMe = $("<h1>");
    for(var i=0; i<me[0].attributes.length; i++) {
        var myAttr = me[0].attributes[i].nodeName;
        var myAttrVal = me[0].attributes[i].nodeValue;
        newMe.attr(myAttr, myAttrVal);
    }
    newMe.html(me.html());
    me.replaceWith(newMe);
});
Kasdega
fonte
2

@Andrew Whitaker: Proponho esta mudança:

$.fn.changeElementType = function(newType) {
    var attrs = {};

    $.each(this[0].attributes, function(idx, attr) {
        attrs[attr.nodeName] = attr.nodeValue;
    });

    var newelement = $("<" + newType + "/>", attrs).append($(this).contents());
    this.replaceWith(newelement);
    return newelement;
};

Então você pode fazer coisas como: $('<div>blah</div>').changeElementType('pre').addClass('myclass');

Jakov
fonte
2

Gosto da ideia de @AndrewWhitaker e outros, de usar um plugin jQuery - para adicionar o changeElementType()método. Mas um plugin é como uma caixa preta, não importa o código, se é pequeno e funciona bem ... Então, desempenho é necessário e é mais importante do que código.

"Pure javascript" tem melhor desempenho que jQuery: Eu acho que o código de @FelixKling tem melhor desempenho que @AndrewWhitaker e outros.


Aqui, um código "Javavascript puro" (e "DOM puro"), encapsulado em um plugin jQuery :

 (function($) {  // @FelixKling's code
    $.fn.changeElementType = function(newType) {
      for (var k=0;k<this.length; k++) {
       var e = this[k];
       var new_element = document.createElement(newType),
        old_attributes = e.attributes,
        new_attributes = new_element.attributes,
        child = e.firstChild;
       for(var i = 0, len = old_attributes.length; i < len; i++) {
        new_attributes.setNamedItem(old_attributes.item(i).cloneNode());
       }
       do {
        new_element.appendChild(e.firstChild);
       }
       while(e.firstChild);
       e.parentNode.replaceChild(new_element, e);
      }
      return this; // for chain... $(this)?  not working with multiple 
    }
 })(jQuery);
Peter Krauss
fonte
2

Aqui está um método que uso para substituir as tags html no jquery:

// Iterate over each element and replace the tag while maintaining attributes
$('b.xyzxterms').each(function() {

  // Create a new element and assign it attributes from the current element
  var NewElement = $("<h1 />");
  $.each(this.attributes, function(i, attrib){
    $(NewElement).attr(attrib.name, attrib.value);
  });

  // Replace the current element with the new one and carry over the contents
  $(this).replaceWith(function () {
    return $(NewElement).append($(this).contents());
  });

});
Seth McCauley
fonte
2

Com jQuery sem iteração de atributos:

O replaceElemmétodo abaixo aceita old Tag, new Tage contexte executa a substituição com sucesso:


replaceElem('h2', 'h1', '#test');

function replaceElem(oldElem, newElem, ctx) {
  oldElems = $(oldElem, ctx);
  //
  $.each(oldElems, function(idx, el) {
    var outerHTML, newOuterHTML, regexOpeningTag, regexClosingTag, tagName;
    // create RegExp dynamically for opening and closing tags
    tagName = $(el).get(0).tagName;
    regexOpeningTag = new RegExp('^<' + tagName, 'i'); 
    regexClosingTag = new RegExp(tagName + '>$', 'i');
    // fetch the outer elem with vanilla JS,
    outerHTML = el.outerHTML;
    // start replacing opening tag
    newOuterHTML = outerHTML.replace(regexOpeningTag, '<' + newElem);
    // continue replacing closing tag
    newOuterHTML = newOuterHTML.replace(regexClosingTag, newElem + '>');
    // replace the old elem with the new elem-string
    $(el).replaceWith(newOuterHTML);
  });

}
h1 {
  color: white;
  background-color: blue;
  position: relative;
}

h1:before {
  content: 'this is h1';
  position: absolute;
  top: 0;
  left: 50%;
  font-size: 5px;
  background-color: black;
  color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


<div id="test">
  <h2>Foo</h2>
  <h2>Bar</h2>
</div>

Boa sorte...

Akash
fonte
1
Gosto da sua resposta! Por quê? Porque TODAS as outras respostas falharão na tentativa de fazer algo simples, como converter uma âncora em um rótulo. Dito isso, considere as seguintes correções / revisões em sua resposta: A). Seu código não funcionará com seletores. B) Seu código precisa executar regex que não diferencia maiúsculas de minúsculas. Dito isso, aqui estão minhas sugestões de correções: regexOpeningTag = new RegExp ('^ <' + $ (el) .get (0) .tagName, 'i'); regexClosingTag = new RegExp ($ (el) .get (0) .tagName + '> $', 'i');
zax
Substituir HTML simples como esse também fará você perder todos os ouvintes de evento anexados aos objetos.
José Yánez
1

Solução Javascript

Copie os atributos do elemento antigo para o novo elemento

const $oldElem = document.querySelector('.old')
const $newElem = document.createElement('div')

Array.from($oldElem.attributes).map(a => {
  $newElem.setAttribute(a.name, a.value)
})

Substitua o elemento antigo pelo novo

$oldElem.parentNode.replaceChild($newElem, $oldElem)
svnm
fonte
mapcria uma nova matriz não utilizada, ela pode ser substituída por forEach.
Orkhan Alikhanov
1

Aqui está minha versão. É basicamente a versão de @fiskhandlarn, mas em vez de construir um novo objeto jQuery, ele simplesmente sobrescreve os elementos antigos com os recém-criados, portanto, nenhuma fusão é necessária.
Demo: http://jsfiddle.net/0qa7wL1b/

$.fn.changeElementType = function( newType ){
  var $this = this;

  this.each( function( index ){

    var atts = {};
    $.each( this.attributes, function(){
      atts[ this.name ] = this.value;
    });

    var $old = $(this);
    var $new = $('<'+ newType +'/>', atts ).append( $old.contents() );
    $old.replaceWith( $new );

    $this[ index ] = $new[0];
  });

  return this;
};
biziclop
fonte