Як вибрати текстові вузли за допомогою jQuery?


388

Я хотів би отримати всі нащадкові текстові вузли елемента, як колекцію jQuery. Який найкращий спосіб зробити це?

Відповіді:


261

jQuery не має зручної для цього функції. Вам потрібно поєднати contents(), що дасть лише дочірні вузли, але включає текстові вузли, з find(), що дає всі нащадкові елементи, але не текстові вузли. Ось що я придумав:

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

getTextNodesIn(el);

Примітка. Якщо ви використовуєте jQuery 1.7 або новішу версію, код вище не працює. Щоб виправити це, замініть addBack()на andSelf(). andSelf()застаріла на користь addBack()від 1,8 і більше.

Це дещо неефективно в порівнянні з чистими методами DOM і повинно включати некрасиве вирішення проблеми перевантаження його contents()функцією jQuery (завдяки @rabidsnail в коментарях, які вказують на це), тому тут не-jQuery рішення, що використовує просту рекурсивну функцію. У includeWhitespaceNodesпараметр управляє чи не пробільні текстові вузли , що входять в висновок (в JQuery вони автоматично відфільтровані).

Оновлення: виправлена ​​помилка, коли includeWhitespaceNodes неправдивий.

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

Чи може елемент, який передається, бути ім'ям div?
crosenblum

@crosenblum: Ви можете зателефонувати document.getElementById()спочатку, якщо це саме ви маєте на увазі:var div = document.getElementById("foo"); var textNodes = getTextNodesIn(div);
Tim Down

Через помилку в jQuery, якщо у вас є якісь iframe в el, вам потрібно буде використовувати .find (': not (iframe)') замість .find ('*').
bobpoekert

@rabidsnail: Я думаю, що використання .contents()будь-якого сенсу означає, що воно буде шукати і через iframe. Я не бачу, як це могло бути помилка.
Робін Мабен

bugs.jquery.com/ticket/11275 Чи є це насправді помилка, здається, обговорюється, але помилка чи ні, якщо ви викликаєте find ('*'). content () на вузлі, що містить iframe, який не має додано до дому, ви отримаєте виняток у невизначеному пункті.
bobpoekert

209

Jauco розмістив гарне рішення у коментарі, тому я його копіюю тут:

$(elem)
  .contents()
  .filter(function() {
    return this.nodeType === 3; //Node.TEXT_NODE
  });

34
фактично $ (elem) .contents () .filter (function () {return this.nodeType == Node.TEXT_NODE;}); достатньо
Jauco

37
IE7 не визначає вузол Глобальні, так що ви повинні використовувати this.nodeType == 3, на жаль: stackoverflow.com/questions/1423599/node-textnode-and-ie7
Christian Oudard

17
Чи це не лише повертає текстові вузли, які є прямими дочірніми елементами, а не нащадки елемента, як це вимагало ОП?
Тім Даун

7
це не буде працювати, коли текстовий вузол глибоко вкладений всередині інших елементів, оскільки метод content
minhajul

1
@Jauco, nope, недостатньо! як .contents () повертає лише найближчі
дочірні


6

jQuery.contents()можна використовувати jQuery.filterдля пошуку всіх дочірніх текстових вузлів. Трохи закрутившись, ви також можете знайти текстові вузли онуків. Рекурсія не потрібна:

$(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


4

Я отримував багато порожніх текстових вузлів із прийнятою функцією фільтра. Якщо ви зацікавлені лише у виборі текстових вузлів, які містять пробіли, не додайте пробілів, спробуйте додати nodeValueумовну filterфункцію, наприклад, просту $.trim(this.nodevalue) !== '':

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

http://jsfiddle.net/ptp6m97v/

Або щоб уникнути дивних ситуацій, коли вміст виглядає як пробіл, але його немає (наприклад, м'який &shy;символ дефісу , нові рядки \n, вкладки тощо), можна спробувати скористатися регулярним виразом. Наприклад, \Sвідповідатиме будь-яким символам, що не містять пробілів:

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

3

Якщо ви можете зробити припущення, що всі діти є або вузлами елементів, або текстовими вузлами, то це одне рішення.

Щоб отримати всі дочірні текстові вузли як колекцію jquery:

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

Щоб отримати копію оригінального елемента з видаленими нетекстовими дітьми:

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

1
Щойно помітив коментар Тіма Дауна щодо іншої відповіді. Це рішення отримують лише прямі діти, а не всі нащадки.
коллін

2

Чомусь contents()у мене не вийшло, тож якщо для вас це не спрацювало, ось я зробив рішення, яке я створив jQuery.fn.descendantsз можливістю включати текстові вузли чи ні

Використання


Отримати всіх нащадків, включаючи текстові та вузлові елементи

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

Отримати всіх нащадків, повертаючи лише текстові вузли

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

Отримати всіх нащадків, які повертають лише вузли елементів

jQuery('body').descendants();

Оригінал coffeescript :

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)

Перехід у версію 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)}

Немініфікована версія Javascript: http://pastebin.com/cX3jMfuD

Це крос-браузер, в Array.indexOfкод входить невелика поліфіл.


1

Також можна зробити так:

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

Вищевказаний код фільтрує textNodes з прямих дочірніх вузлів даного елемента.


1
... але не всі нащадки дочірніх вузлів (наприклад , текстовий вузол , який є дочірнім елементом , який є нащадком вихідного елемента).
Тим Даун

0

якщо ви хочете зняти всі теги, спробуйте це

функція:

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

використання:

var newText=$('selector').html().stripTags();

0

Для мене, звичайно, старий .contents()працював над поверненням текстових вузлів, просто будьте обережні з вашими селекторами, щоб ви знали, що вони будуть текстовими вузлами.

Наприклад, це загорнуло весь текстовий вміст ТД у моїй таблиці preтегами, і не виникло проблем.

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

0

У мене була та сама проблема, і я вирішив її:

Код:

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

Використання:

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

Це як, next()але також повертає текстові вузли.


.nextSibling - із специфікації Dom: developer.mozilla.org/uk/Document_Object_Model_(DOM)/…
Guillermo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.