Como seleciono nós de texto com o jQuery?

388

Gostaria de obter todos os nós de texto descendentes de um elemento, como uma coleção jQuery. Qual o melhor jeito pra fazer isso?

Christian Oudard
fonte

Respostas:

261

O jQuery não possui uma função conveniente para isso. Você precisa combinar contents(), que fornecerá apenas nós filhos, mas incluirá nós de texto, com find(), que fornecerá todos os elementos descendentes, mas nenhum nó de texto. Aqui está o que eu vim com:

var getTextNodesIn = function(el) {
    return $(el).find(":not(iframe)").addBack().contents().filter(function() {
        return this.nodeType == 3;
    });
};

getTextNodesIn(el);

Nota: Se você estiver usando o jQuery 1.7 ou anterior, o código acima não funcionará. Para corrigir isso, substitua addBack()por andSelf(). andSelf()é descontinuado a favor de addBack()1,8 em diante.

Isso é um tanto ineficiente comparado aos métodos DOM puros e deve incluir uma solução alternativa feia para a sobrecarga de sua contents()função pelo jQuery (graças a @rabidsnail nos comentários por apontar isso), então aqui está a solução não-jQuery usando uma função recursiva simples. O includeWhitespaceNodesparâmetro controla se os nós de texto em espaço em branco são ou não incluídos na saída (no jQuery, eles são filtrados automaticamente).

Atualização: Corrigido o erro quando includeWhitespaceNodes é falso.

function getTextNodesIn(node, includeWhitespaceNodes) {
    var textNodes = [], nonWhitespaceMatcher = /\S/;

    function getTextNodes(node) {
        if (node.nodeType == 3) {
            if (includeWhitespaceNodes || nonWhitespaceMatcher.test(node.nodeValue)) {
                textNodes.push(node);
            }
        } else {
            for (var i = 0, len = node.childNodes.length; i < len; ++i) {
                getTextNodes(node.childNodes[i]);
            }
        }
    }

    getTextNodes(node);
    return textNodes;
}

getTextNodesIn(el);
Tim Down
fonte
O elemento passado pode ser o nome de uma div?
10131 crosenblum
@crosenblum: Você pode ligar document.getElementById()primeiro, se é isso que você quer dizer:var div = document.getElementById("foo"); var textNodes = getTextNodesIn(div);
Tim Down
Por causa de um erro no jQuery, se você tiver iframes no el, precisará usar .find (': not (iframe)') em vez de .find ('*').
bobpoekert
@rabidsnail: Eu acho que o uso de .contents()qualquer maneira implica que ele irá pesquisar no iframe também. Não vejo como isso poderia ser um bug.
Robin Maben
bugs.jquery.com/ticket/11275 Se este é realmente um bug parece estar em debate, mas bug ou não se você chamar find ('*'). contents () em um nó que contém um iframe que não possui adicionado ao domínio, você receberá uma exceção em um ponto indefinido.
22412 Bobpoekert
209

Jauco postou uma boa solução em um comentário, por isso estou copiando aqui:

$(elem)
  .contents()
  .filter(function() {
    return this.nodeType === 3; //Node.TEXT_NODE
  });
Christian Oudard
fonte
34
na verdade $ (elem) .contents () .filter (function () {retorna this.nodeType == Node.TEXT_NODE;}); é suficiente
Jauco
37
O IE7 não define o mundial Node, então você tem que usar this.nodeType == 3, infelizmente: stackoverflow.com/questions/1423599/node-textnode-and-ie7
Christian Oudard
17
Isso não apenas retorna os nós de texto que são filhos diretos do elemento, em vez de descendentes do elemento, conforme solicitado pelo OP?
Tim Down
7
isso não vai funcionar quando o nó de texto é profundamente aninhada dentro de outros elementos, porque o conteúdo () método retorna apenas os nós imediatos crianças, api.jquery.com/contents
minhajul
11
@ Jauco, não, não é o suficiente! como .contents () retorna apenas os nós filhos imediatos
minhajul 16/10/2015
17
$('body').find('*').contents().filter(function () { return this.nodeType === 3; });
He Nrik
fonte
6

jQuery.contents()pode ser usado com jQuery.filterpara encontrar todos os nós de texto filho. Com uma pequena reviravolta, você também pode encontrar nós de texto dos netos. Não é necessária recursão:

$(function() {
  var $textNodes = $("#test, #test *").contents().filter(function() {
    return this.nodeType === Node.TEXT_NODE;
  });
  /*
   * for testing
   */
  $textNodes.each(function() {
    console.log(this);
  });
});
div { margin-left: 1em; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<div id="test">
  child text 1<br>
  child text 2
  <div>
    grandchild text 1
    <div>grand-grandchild text 1</div>
    grandchild text 2
  </div>
  child text 3<br>
  child text 4
</div>

jsFiddle

Salman A
fonte
4

Eu estava recebendo muitos nós de texto vazios com a função de filtro aceita. Se você estiver interessado apenas em selecionar nós de texto que não contenham espaços em branco, tente adicionar um nodeValuecondicional à sua filterfunção, como um simples $.trim(this.nodevalue) !== '':

$('element')
    .contents()
    .filter(function(){
        return this.nodeType === 3 && $.trim(this.nodeValue) !== '';
    });

http://jsfiddle.net/ptp6m97v/

Ou, para evitar situações estranhas em que o conteúdo se parece com espaços em branco, mas não é (por exemplo, o &shy;caractere de hífen suave , novas linhas \n, guias, etc.), você pode tentar usar uma Expressão regular. Por exemplo, \Scorresponderá a caracteres que não sejam espaços em branco:

$('element')
        .contents()
        .filter(function(){
            return this.nodeType === 3 && /\S/.test(this.nodeValue);
        });
Alex W
fonte
3

Se você puder supor que todos os filhos sejam nós de elemento ou nós de texto, essa é uma solução.

Para obter todos os nós de texto filho como uma coleção de jquery:

$('selector').clone().children().remove().end().contents();

Para obter uma cópia do elemento original com os filhos que não são de texto removidos:

$('selector').clone().children().remove().end();
colllin
fonte
11
Só notei o comentário de Tim Down em outra resposta. Essa solução recebe apenas os filhos diretos, nem todos os descendentes.
colllin
2

Por alguma razão contents(), não funcionou para mim; portanto, se não funcionou para você, aqui está uma solução que eu criei, criei jQuery.fn.descendantscom a opção de incluir nós de texto ou não

Uso


Obter todos os descendentes, incluindo nós de texto e nós de elemento

jQuery('body').descendants('all');

Obter todos os descendentes retornando apenas nós de texto

jQuery('body').descendants(true);

Obter todos os descendentes retornando apenas nós de elemento

jQuery('body').descendants();

Original do café :

jQuery.fn.descendants = ( textNodes ) ->

    # if textNodes is 'all' then textNodes and elementNodes are allowed
    # if textNodes if true then only textNodes will be returned
    # if textNodes is not provided as an argument then only element nodes
    # will be returned

    allowedTypes = if textNodes is 'all' then [1,3] else if textNodes then [3] else [1]

    # nodes we find
    nodes = []


    dig = (node) ->

        # loop through children
        for child in node.childNodes

            # push child to collection if has allowed type
            nodes.push(child) if child.nodeType in allowedTypes

            # dig through child if has children
            dig child if child.childNodes.length


    # loop and dig through nodes in the current
    # jQuery object
    dig node for node in this


    # wrap with jQuery
    return jQuery(nodes)

Soltar na versão Javascript

var __indexOf=[].indexOf||function(e){for(var t=0,n=this.length;t<n;t++){if(t in this&&this[t]===e)return t}return-1}; /* indexOf polyfill ends here*/ jQuery.fn.descendants=function(e){var t,n,r,i,s,o;t=e==="all"?[1,3]:e?[3]:[1];i=[];n=function(e){var r,s,o,u,a,f;u=e.childNodes;f=[];for(s=0,o=u.length;s<o;s++){r=u[s];if(a=r.nodeType,__indexOf.call(t,a)>=0){i.push(r)}if(r.childNodes.length){f.push(n(r))}else{f.push(void 0)}}return f};for(s=0,o=this.length;s<o;s++){r=this[s];n(r)}return jQuery(i)}

Versão Javascript não compactada: http://pastebin.com/cX3jMfuD

Este é um navegador cruzado, um pequeno Array.indexOfpolyfill está incluído no código.

iConnor
fonte
1

Também pode ser feito assim:

var textContents = $(document.getElementById("ElementId").childNodes).filter(function(){
        return this.nodeType == 3;
});

O código acima filtra os textNodes dos nós filhos diretos filhos de um determinado elemento.

Senhor Verde
fonte
11
... mas nem todos os nós filhos descendentes (por exemplo, um nó de texto que é filho de um elemento que é filho do elemento original).
Tim Baixo
0

se você deseja remover todas as tags, tente isso

função:

String.prototype.stripTags=function(){
var rtag=/<.*?[^>]>/g;
return this.replace(rtag,'');
}

uso:

var newText=$('selector').html().stripTags();
Rahen Rangan
fonte
0

Para mim, o velho simples .contents()parecia funcionar para retornar os nós de texto, apenas tenha cuidado com os seletores para que você saiba que eles serão nós de texto.

Por exemplo, isso envolveu todo o conteúdo de texto dos TDs na minha tabela com pretags e não teve problemas.

jQuery("#resultTable td").content().wrap("<pre/>")
davenpcj
fonte
0

Eu tive o mesmo problema e resolvi-o com:

Código:

$.fn.nextNode = function(){
  var contents = $(this).parent().contents();
  return contents.get(contents.index(this)+1);
}

Uso:

$('#my_id').nextNode();

É como, next()mas também retorna os nós de texto.

Guillermo
fonte
.nextSibling é da especificação do Dom: developer.mozilla.org/en/Document_Object_Model_(DOM)/…
Guillermo