Comment sélectionner des nœuds de texte avec jQuery?

388

Je voudrais obtenir tous les nœuds de texte descendants d'un élément, en tant que collection jQuery. Quelle est la meilleure façon de le faire?

Christian Oudard
la source

Réponses:

261

jQuery n'a pas de fonction pratique pour cela. Vous devez combiner contents(), qui ne donnera que des nœuds enfants mais inclut des nœuds de texte, avec find(), qui donne tous les éléments descendants mais pas de nœuds de texte. Voici ce que j'ai trouvé:

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

getTextNodesIn(el);

Remarque: Si vous utilisez jQuery 1.7 ou une version antérieure, le code ci-dessus ne fonctionnera pas. Pour résoudre ce problème, remplacez addBack()par andSelf(). andSelf()est déconseillé au profit de la version addBack()1.8.

Ceci est quelque peu inefficace par rapport aux méthodes DOM pures et doit inclure une solution de contournement laide pour la surcharge de jQuery de sa contents()fonction (grâce à @rabidsnail dans les commentaires pour l'avoir souligné), voici donc une solution non jQuery utilisant une simple fonction récursive. Le includeWhitespaceNodesparamètre contrôle si les nœuds de texte d'espaces sont inclus ou non dans la sortie (dans jQuery, ils sont automatiquement filtrés).

Mise à jour: correction d'un bug lorsque includeWhitespaceNodes est faux.

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
la source
L'élément peut-il passer, être le nom d'un div?
crosenblum
@crosenblum: Vous pouvez appeler en document.getElementById()premier, si c'est ce que vous voulez dire:var div = document.getElementById("foo"); var textNodes = getTextNodesIn(div);
Tim Down
En raison d'un bogue dans jQuery si vous avez des iframes dans el, vous devrez utiliser .find (': not (iframe)') au lieu de .find ('*').
bobpoekert
@rabidsnail: Je pense que l'utilisation de .contents()toute façon implique également une recherche dans l'iframe. Je ne vois pas comment ça pourrait être un bug.
Robin Maben
bugs.jquery.com/ticket/11275 Que ce soit réellement un bug semble être à débattre, mais bug ou pas si vous appelez find ('*'). contents () sur un nœud qui contient une iframe qui n'a pas ajouté au dom, vous obtiendrez une exception à un moment indéfini.
bobpoekert
209

Jauco a posté une bonne solution dans un commentaire, donc je la copie ici:

$(elem)
  .contents()
  .filter(function() {
    return this.nodeType === 3; //Node.TEXT_NODE
  });
Christian Oudard
la source
34
en fait $ (elem) .contents () .filter (function () {return this.nodeType == Node.TEXT_NODE;}); suffit
Jauco
37
IE7 ne définit pas le nœud global, vous devez donc utiliser this.nodeType == 3, malheureusement: stackoverflow.com/questions/1423599/node-textnode-and-ie7
Christian Oudard
17
Cela ne renvoie-t-il pas seulement les nœuds de texte qui sont les enfants directs de l'élément plutôt que les descendants de l'élément comme l'OP l'a demandé?
Tim Down du
7
cela ne fonctionnera pas lorsque le nœud de texte est profondément imbriqué dans d'autres éléments, car la méthode contents () renvoie uniquement les nœuds enfants immédiats, api.jquery.com/contents
minhajul
1
@Jauco, non, pas assez! car .contents () renvoie uniquement les nœuds enfants immédiats
minhajul
17
$('body').find('*').contents().filter(function () { return this.nodeType === 3; });
He Nrik
la source
6

jQuery.contents()peut être utilisé avec jQuery.filterpour rechercher tous les nœuds de texte enfant. Avec une petite torsion, vous pouvez également trouver des nœuds de texte pour petits-enfants. Aucune récursivité requise:

$(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
la source
4

J'obtenais beaucoup de nœuds de texte vides avec la fonction de filtre acceptée. Si vous souhaitez uniquement sélectionner des nœuds de texte contenant des espaces non blancs, essayez d'ajouter un nodeValueconditionnel à votre filterfonction, comme un simple $.trim(this.nodevalue) !== '':

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

http://jsfiddle.net/ptp6m97v/

Ou pour éviter des situations étranges où le contenu ressemble à un espace, mais ne l'est pas (par exemple le &shy;caractère de trait d'union doux , les nouvelles lignes \n, les tabulations, etc.), vous pouvez essayer d'utiliser une expression régulière. Par exemple, \Scorrespondra à tous les caractères non blancs:

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

Si vous pouvez faire l'hypothèse que tous les enfants sont des nœuds d'élément ou des nœuds de texte, alors c'est une solution.

Pour obtenir tous les nœuds de texte enfant en tant que collection jquery:

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

Pour obtenir une copie de l'élément d'origine avec les enfants non textuels supprimés:

$('selector').clone().children().remove().end();
colllin
la source
1
Je viens de remarquer le commentaire de Tim Down sur une autre réponse. Cette solution n'obtient que les enfants directs, pas tous les descendants.
colllin
2

Pour une raison quelconque, cela contents()n'a pas fonctionné pour moi, donc si cela n'a pas fonctionné pour vous, voici une solution que j'ai faite, j'ai crééjQuery.fn.descendants avec l'option d'inclure ou non des nœuds de texte

Usage


Obtenez tous les descendants, y compris les nœuds de texte et les nœuds d'élément

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

Obtenir tous les descendants renvoyant uniquement les nœuds de texte

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

Récupère tous les descendants renvoyant uniquement les nœuds d'élément

jQuery('body').descendants();

Coffeescript Original :

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)

Déposer la version 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)}

Version Javascript non minimisée: http://pastebin.com/cX3jMfuD

Il s'agit d'un navigateur croisé, un petit Array.indexOfpolyfill est inclus dans le code.

iConnor
la source
1

Peut aussi se faire comme ceci:

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

Le code ci-dessus filtre les textNodes des nœuds enfants enfants directs d'un élément donné.

Monsieur green
la source
1
... mais pas tous les nœuds enfants descendants (par exemple, un nœud de texte qui est l'enfant d'un élément qui est un enfant de l'élément d'origine).
Tim Down
0

si vous souhaitez supprimer toutes les balises, essayez ceci

une fonction:

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

usage:

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

Pour moi, tout simplement vieux .contents() semble fonctionner pour renvoyer les nœuds de texte, il suffit de faire attention à vos sélecteurs pour que vous sachiez qu'ils seront des nœuds de texte.

Par exemple, cela a enveloppé tout le contenu textuel des TDs dans ma table avec pre balises et n'a eu aucun problème.

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

J'ai eu le même problème et l'ai résolu avec:

Code:

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

Usage:

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

Est similaire next()mais renvoie également les nœuds de texte.

Guillermo
la source
.nextSibling provient de la spécification Dom: developer.mozilla.org/en/Document_Object_Model_(DOM)/…
Guillermo