最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Using jQuery to gather all text nodes from a wrapped set, separated by spaces - Stack Overflow

programmeradmin11浏览0评论

I'm looking for a way to gather all of the text in a jQuery wrapped set, but I need to create spaces between sibling nodes that have no text nodes between them.

For example, consider this HTML:

<div>
  <ul>
    <li>List item #1.</li><li>List item #2.</li><li>List item #3.</li>
  </ul>
</div>

If I simply use jQuery's text() method to gather the text content of the <div>, like such:

var $div = $('div'), text = $div.text().trim();

alert(text);

that produces the following text:

List item #1.List item #2.List item #3.

because there is no whitespace between each <li> element. What I'm actually looking for is this (note the single space between each sentence):

List item #1. List item #3. List item #3.

This suggest to me that I need to traverse the DOM nodes in the wrapped set, appending the text for each to a string, followed by a space. I tried the following code:

var $div = $('div'), text = '';

$div.find('*').each(function() {
  text += $(this).text().trim() + ' ';
});

alert(text);

but this produced the following text:

This is list item #1.This is list item #2.This is list item #3. This is list item #1. This is list item #2. This is list item #3.

I assume this is because I'm iterating through every descendant of <div> and appending the text, so I'm getting the text nodes within both <ul> and each of its <li> children, leading to duplicated text.

I think I could probably find/write a plain JavaScript function to recursively walk the DOM of the wrapped set, gathering and appending text nodes - but is there a simpler way to do this using jQuery? Cross-browser consistency is very important.

Thanks for any help!

I'm looking for a way to gather all of the text in a jQuery wrapped set, but I need to create spaces between sibling nodes that have no text nodes between them.

For example, consider this HTML:

<div>
  <ul>
    <li>List item #1.</li><li>List item #2.</li><li>List item #3.</li>
  </ul>
</div>

If I simply use jQuery's text() method to gather the text content of the <div>, like such:

var $div = $('div'), text = $div.text().trim();

alert(text);

that produces the following text:

List item #1.List item #2.List item #3.

because there is no whitespace between each <li> element. What I'm actually looking for is this (note the single space between each sentence):

List item #1. List item #3. List item #3.

This suggest to me that I need to traverse the DOM nodes in the wrapped set, appending the text for each to a string, followed by a space. I tried the following code:

var $div = $('div'), text = '';

$div.find('*').each(function() {
  text += $(this).text().trim() + ' ';
});

alert(text);

but this produced the following text:

This is list item #1.This is list item #2.This is list item #3. This is list item #1. This is list item #2. This is list item #3.

I assume this is because I'm iterating through every descendant of <div> and appending the text, so I'm getting the text nodes within both <ul> and each of its <li> children, leading to duplicated text.

I think I could probably find/write a plain JavaScript function to recursively walk the DOM of the wrapped set, gathering and appending text nodes - but is there a simpler way to do this using jQuery? Cross-browser consistency is very important.

Thanks for any help!

Share Improve this question asked May 14, 2010 at 17:40 BungleBungle 19.7k25 gold badges81 silver badges108 bronze badges
Add a comment  | 

4 Answers 4

Reset to default 9

jQuery deals mostly with elements, its text-node powers are relatively weak. You can get a list of all children with contents(), but you'd still have to walk it checking types, so that's really no different from just using plain DOM childNodes. There is no method to recursively get text nodes so you would have to write something yourself, eg. something like:

function collectTextNodes(element, texts) {
    for (var child= element.firstChild; child!==null; child= child.nextSibling) {
        if (child.nodeType===3)
            texts.push(child);
        else if (child.nodeType===1)
            collectTextNodes(child, texts);
    }
}
function getTextWithSpaces(element) {
    var texts= [];
    collectTextNodes(element, texts);
    for (var i= texts.length; i-->0;)
        texts[i]= texts[i].data;
    return texts.join(' ');
}

This is the simplest solution I could think of:

$("body").find("*").contents().filter(function(){return this.nodeType!==1;});

You can use the jQuery contents() method to get all nodes (including text nodes), then filter down your set to only the text nodes.

$("body").find("*").contents().filter(function(){return this.nodeType!==1;});

From there you can create whatever structure you need.

I built on @bobince's terrific answer to make search tool that would search all columns of a table and filter the rows to show only those that matched (case-insensitively) all of a user's search terms (provided in any order).

Here is a screenshot example:

And here is my javascript/jQuery code:

$(function orderFilter() {
  // recursively collect all text from child elements (returns void)
  function collectTextNodes(element, texts) {
    for (
      let child = element.firstChild;
      child !== null;
      child = child.nextSibling
    ) {
      if (child.nodeType === Node.TEXT_NODE) {
        texts.push(child);
      } else if (child.nodeType === Node.ELEMENT_NODE) {
        collectTextNodes(child, texts);
      }
    }
  }

  // separate all text from all children with single space
  function getAllText(element) {
    const texts = [];
    collectTextNodes(element, texts);

    for (let i = texts.length; i-- > 0; ) texts[i] = texts[i].data;

    return texts.join(' ').replace(/\s\s+/g, ' ');
  }

  // check to see if the search value appears anywhere in child text nodes
  function textMatchesFilter(tbody, searchVal) {
    const tbodyText = getAllText(tbody).toLowerCase();
    const terms = searchVal.toLowerCase().replace(/\s\s+/g, ' ').split(' ');

    return terms.every(searchTerm => tbodyText.includes(searchTerm));
  }

  // filter orders to only show those matching certain fields
  $(document).on('keyup search', 'input.js-filter-orders', evt => {
    const searchVal = $(evt.target).val();
    const $ordersTable = $('table.js-filterable-table');
    $ordersTable.find('tbody[hidden]').removeAttr('hidden');

    if (searchVal.length <= 1) return;

    // Auto-click the "Show more orders" button and reveal any collapsed rows
    $ordersTable
      .find('tfoot a.show-hide-link.collapsed, tbody.rotate-chevron.collapsed')
      .each((_idx, clickToShowMore) => {
        clickToShowMore.click();
      });

    // Set all tbodies to be hidden, then unhide those that match
    $ordersTable
      .find('tbody')
      .attr('hidden', '')
      .filter((_idx, tbody) => textMatchesFilter(tbody, searchVal))
      .removeAttr('hidden');
  });
});

For our purposes, it works perfectly! Hope this helps others!

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论