Wie wähle ich Textknoten mit jQuery aus?

388

Ich möchte alle untergeordneten Textknoten eines Elements als jQuery-Auflistung erhalten. Was ist der beste Weg das zu tun?

Christian Oudard
quelle

Antworten:

261

jQuery hat hierfür keine praktische Funktion. Sie müssen kombinieren contents(), was nur untergeordnete Knoten find()ergibt, aber Textknoten enthält, mit , was alle untergeordneten Elemente, aber keine Textknoten ergibt. Folgendes habe ich mir ausgedacht:

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

getTextNodesIn(el);

Hinweis: Wenn Sie jQuery 1.7 oder früher verwenden, funktioniert der obige Code nicht. Um dies zu beheben, ersetzen Sie addBack()durch andSelf(). andSelf()wird zugunsten von addBack()ab 1,8 abgelehnt .

Dies ist im Vergleich zu reinen DOM-Methoden etwas ineffizient und muss eine hässliche Problemumgehung für die Überladung seiner contents()Funktion durch jQuery enthalten (dank @rabidsnail in den Kommentaren, um darauf hinzuweisen). Daher handelt es sich hier um eine Nicht-jQuery-Lösung, die eine einfache rekursive Funktion verwendet. Der includeWhitespaceNodesParameter steuert, ob Whitespace-Textknoten in der Ausgabe enthalten sind oder nicht (in jQuery werden sie automatisch herausgefiltert).

Update: Fehler behoben, wenn includeWhitespaceNodes falsch ist.

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
quelle
Kann das übergebene Element der Name eines Div sein?
Crosenblum
@ Crosenblum: Sie könnten document.getElementById()zuerst anrufen , wenn Sie das meinen:var div = document.getElementById("foo"); var textNodes = getTextNodesIn(div);
Tim Down
Aufgrund eines Fehlers in jQuery müssen Sie, wenn Sie iframes in el haben, .find (': not (iframe)') anstelle von .find ('*') verwenden.
Bobpoekert
@ Rabidsnail: Ich denke, die Verwendung von .contents()sowieso impliziert, dass es auch den Iframe durchsuchen wird. Ich sehe nicht ein, wie es ein Fehler sein könnte.
Robin Maben
bugs.jquery.com/ticket/11275 Ob dies tatsächlich ein Fehler ist, scheint zur Debatte zu stehen, aber Fehler oder nicht, wenn Sie find ('*'). content () auf einem Knoten aufrufen, der einen Iframe enthält, der keinen hat Wurde dem Dom hinzugefügt, wird an einer undefinierten Stelle eine Ausnahme angezeigt.
Bobpoekert
209

Jauco hat in einem Kommentar eine gute Lösung gepostet, daher kopiere ich sie hier:

$(elem)
  .contents()
  .filter(function() {
    return this.nodeType === 3; //Node.TEXT_NODE
  });
Christian Oudard
quelle
34
tatsächlich $ (elem) .contents () .filter (function () {return this.nodeType == Node.TEXT_NODE;}); ist genug
Jauco
37
IE7 definiert den Knoten nicht global, daher müssen Sie this.nodeType == 3 leider verwenden: stackoverflow.com/questions/1423599/node-textnode-and-ie7
Christian Oudard
17
Gibt dies nicht nur die Textknoten zurück, die die direkten untergeordneten Elemente des Elements sind, sondern auch die Nachkommen des Elements, wie vom OP angefordert?
Tim Down
7
Dies funktioniert nicht, wenn der Textknoten
minhajul
1
@ Jauco, nein, nicht genug! als .contents () gibt nur die unmittelbaren
untergeordneten
17
$('body').find('*').contents().filter(function () { return this.nodeType === 3; });
Er Nrik
quelle
6

jQuery.contents()kann verwendet werden jQuery.filter, um alle untergeordneten Textknoten zu finden. Mit einer kleinen Drehung können Sie auch Textknoten für Enkelkinder finden. Keine Rekursion erforderlich:

$(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.
quelle
4

Ich habe viele leere Textknoten mit der akzeptierten Filterfunktion erhalten. Wenn Sie nur daran interessiert sind Textknoten in der Auswahl , die kein Leerzeichen enthalten, versuchen , eine Zugabe nodeValuebedingte zu Ihrer filterFunktion, wie eine einfache $.trim(this.nodevalue) !== '':

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

http://jsfiddle.net/ptp6m97v/

Um seltsame Situationen zu vermeiden, in denen der Inhalt wie ein Leerzeichen aussieht, dies jedoch nicht ist (z. B. das weiche Bindestrichzeichen &shy;, Zeilenumbrüche \n, Tabulatoren usw.), können Sie versuchen, einen regulären Ausdruck zu verwenden. Entspricht beispielsweise \Sallen Nicht-Leerzeichen:

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

Wenn Sie davon ausgehen können, dass alle untergeordneten Elemente entweder Elementknoten oder Textknoten sind, ist dies eine Lösung.

So rufen Sie alle untergeordneten Textknoten als JQuery-Sammlung ab:

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

So erhalten Sie eine Kopie des Originalelements, bei der nicht untergeordnete Textelemente entfernt wurden:

$('selector').clone().children().remove().end();
Colllin
quelle
1
Ich habe gerade Tim Downs Kommentar zu einer anderen Antwort bemerkt. Diese Lösung erhält nur die direkten Kinder, nicht alle Nachkommen.
Colllin
2

Aus irgendeinem Grund contents()hat es bei mir nicht funktioniert. Wenn es bei Ihnen nicht funktioniert hat, habe ich eine Lösung erstellt, die ich jQuery.fn.descendantsmit der Option erstellt habe, Textknoten einzuschließen oder nicht

Verwendungszweck


Holen Sie sich alle Nachkommen einschließlich Textknoten und Elementknoten

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

Lassen Sie alle Nachkommen nur Textknoten zurückgeben

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

Lassen Sie alle Nachkommen nur Elementknoten zurückgeben

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)

Drop In Javascript Version

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

Nicht minimierte Javascript-Version: http://pastebin.com/cX3jMfuD

Dies ist browserübergreifend, eine kleine Array.indexOfPolyfüllung ist im Code enthalten.

iConnor
quelle
1

Kann auch so gemacht werden:

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

Der obige Code filtert die textNodes von direkten untergeordneten untergeordneten Knoten eines bestimmten Elements.

Herr Green
quelle
1
... aber nicht alle untergeordneten untergeordneten Knoten (z. B. ein Textknoten, der das untergeordnete Element eines Elements ist, das dem untergeordneten Element untergeordnet ist).
Tim Down
0

Wenn Sie alle Tags entfernen möchten, versuchen Sie dies

Funktion:

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

Verwendungszweck:

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

Für mich .contents()schien einfach alt zu funktionieren, um die Textknoten zurückzugeben. Sie müssen nur vorsichtig mit Ihren Selektoren sein, damit Sie wissen, dass es sich um Textknoten handelt.

Dies hat beispielsweise den gesamten Textinhalt der TDs in meiner Tabelle mit preTags versehen und hatte keine Probleme.

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

Ich hatte das gleiche Problem und löste es mit:

Code:

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

Verwendungszweck:

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

Ist wie, gibt next()aber auch die Textknoten zurück.

Guillermo
quelle
.nextSibling ist aus der Dom-Spezifikation: developer.mozilla.org/en/Document_Object_Model_(DOM)/…
Guillermo