Domanda

I want to select and return searched text using jQuery.

The problem is; parts of the text may be located in <span> or other inline elements, so when searching for 'waffles are tasty' in this text: 'I'm not sure about <i>cabbages</i>, but <b>waffles</b> <span>are</span> <i>tasty</i>, indeed.', you wouldn't get any matches, while the text appears uninterrupted to people.

Let's use this HTML as an example:

<div id="parent">
  <span style="font-size: 1.2em">
    I
  </span>
  like turtles 
  <span>
    quite a
  </span>
  lot, actually.

  <span>
    there's loads of
  </span>
  tortoises over there, OMG

  <div id="child">
    <span style="font-size: 1.2em">
      I
    </span>
    like turtles 
    <span>
      quite a
    </span>
    lot, actually.

    TURTLES!
  </div>
</div>

With this (or similar) JavaScript:

$('div#parent').selectText({query: ['i like', 'turtles', 'loads of tortoises'], caseinsensitive: true}).each(function () {
  $(this).css('background-color', '#ffff00');
});
//The (hypothetical) SelectText function would return an array of wrapped elements to chain .each(); on them

You would want to produce this output: (without the comments, obviously)

<div id="parent">
  <span style="font-size: 1.2em">
    <span class="selected" style="background-color: #ffff00">
      I
    </span> <!--Wrap in 2 separate selection spans so the original hierarchy is disturbed less (as opposed to wrapping 'I' and 'like' in a single selection span)-->
  </span>
  <span class="selected" style="background-color: #ffff00">
    like
  </span>
  <span class="selected" style="background-color: #ffff00"> <!--Simple match, because the search query is just the word 'turtles'-->
    turtles
  </span> 
  <span>
    quite a
  </span>
  lot, actually.

  <span>
    there's
    <span class="selected" style="background-color: #ffff00">
      loads of
    </span> <!--Selection span needs to be closed here because of HTML tag order-->
  </span>
  <span class="selected" style="background-color: #ffff00"> <!--Capture the rest of the found text with a second selection span-->
    tortoises
  </span>
  over there, OMG

  <div id="child"> <!--This element's children are not searched because it's not a span-->
    <span style="font-size: 1.2em">
      I
    </span>
    like turtles 
    <span>
      quite a
    </span>
    lot, actually.

    TURTLES!
  </div>
</div>

The (hypothetical) SelectText function would wrap all selected text in <span class="selected"> tags, regardless of whether parts of the search are located in other inline elements like <span>, '', etc. It does not search the child <div>'s contents because that's not an inline element.

Is there a jQuery plugin that does something like this? (wrap search query in span tags and return them, oblivious to whether parts of the found text may be located in other inline elements?)

If not, how would one go about creating such a function? This function's kinda what I'm looking for, but it doesn't return the array of selected spans and breaks when parts of the found text are nested in other inline elements.

Any help would be greatly appreciated!

È stato utile?

Soluzione

Piece of cake! See this.

Folded notation:

$.each(
    $(...).findText(...), 
    function (){
        ...
    }
);

In-line notation:

$(...).findText(...).each(function (){
        ...
    }
);

Altri suggerimenti

Three options:

  • Use the browser's built-in methods for this. For the finding, IE has TextRange with its findText() method; other browsers (with the exception of Opera, last time I checked, which was a long time ago) have window.find(). However, window.find() may be killed off without being replaced at some point, which is not ideal. For the highlighting, you can use document.execCommand().
  • Use my Rangy library. There's a demo here: http://rangy.googlecode.com/svn/trunk/demos/textrange.html
  • Build your own code to search text content in the DOM and style it.

The first two options are covered in more detail on this answer:

https://stackoverflow.com/a/5887719/96100

Since I just so happened to be working on a similar thing right now, in case you'd like to see the beginnings of my interpretation of "option 3", I thought I'd share this, with the main feature being that all text nodes are traversed, without altering existing tags. Not tested across any unusual browsers yet, so no warranty whatsoever with this one!

function DOMComb2 (oParent) {
    if (oParent.hasChildNodes()) {
        for (var oNode = oParent.firstChild; oNode; oNode = oNode.nextSibling) {
            if (oNode.nodeType==3 && oNode.nodeValue) { // Add regexp to search the text here
                var highlight = document.createElement('span');
                highlight.appendChild(oNode.cloneNode(true));
                highlight.className = 'selected';
                oParent.replaceChild(highlight, oNode);

                // Or, the shorter, uglier jQuery hybrid one-liner
                // oParent.replaceChild($('<span class="selected">' + oNode.nodeValue + '</span>')[0], oNode);
            }
            if (oNode.tagName != 'DIV') { // Add any other element you want to avoid
                DOMComb2(oNode);
            }
        }
    }
}

Then search through things selectively with jQuery perhaps:

$('aside').each(function(){
    DOMComb2($(this)[0]);
});

Of course, if you have asides within your asides, strange things might happen.

(DOMComb function adapted from the Mozilla dev reference site https://developer.mozilla.org/en-US/docs/Web/API/Node)

I wrote a draft as a fiddle. The main steps:

I made a plugin for jQuery

$.fn.selectText = function(params){
    var phrases = params.query, 
        ignorance = params.ignorecase;
        wrapper = $(this);
    . . .
    return(wrapper);
};

Now I can call the selection as a $(...).selectText({query:["tortoise"], ignorance: true, style: 'selection'});

I know you want to have iterator after the function call, but in your case it is impossible, because iterator have to return valid jQuery selectors. For example: word <tag>word word</tag> word is not valid selector.

After sanitizing the content of wrapper, for each search makeRegexp() makes personal regular expression.

Each searched piece of html source goes to emulateSelection() and then wrapWords()

Main idea is to wrap in <span class="selection">...</span> each single piece of phrase not separated by tags, but not analyze the whole tree of nodes.

NOTE:

It's NOT working with <b><i>... tags in html. You have to make corrections in regexp string for it. I not guarantee it will work with unicode. But who knows...

As I understood, we talking about iterators like $.each($(...).searchText("..."),function (str){...});.

Check the David Herbert Lawrence poem:

<div class="poem"><p class="part">I never saw a wild thing<br /> 
sorry for itself.<br /> 
A small bird will drop frozen dead from a bough<br />
without ever having felt sorry for itself.<br /></p></div>

Actually, after rendering, browser will understood it like this:

<div class="poem">
<p class="part">
<br>I never saw a wild thing</br> 
<br>sorry for itself.</br> 
<br>A small bird will drop frozen dead from a bough</br>
<br>without ever having felt sorry for itself.</br> 
</p>
</div>

For example, I looking for the phrase: "wild thing sorry for". Therefore, I have to highligt the exerpt:

wild thing</br><br>sorry for

I can not wrap it like this <span>wild thing</br><br>sorry for</span>, then create jQuery selector by some temporary id="search-xxxxxx", and return it back -- it's wrong html. I can wrap each piece of text like this:

<span search="search-xxxxx">wild thing</span></br><br><span search="search-xxxxx">sorry for</span>

Then I have to call some function and return jQuery array of selectors:

return($("[search=search-xxxxx]"));

Now we have two "results": a) "wild thing"; b) "sorry for". Is it really what you want?

OR

You have to write you own each() function like another plugin to jQuery:

$.fn.eachSearch = function(arr, func){
    ...
};

where arr will be not an array of selectors, but array of arrays of selectors, like:

arr = [
    {selector as whole search},
    {[{selector as first part of search]}, {[selector as second part of search]}},
    ...    
]
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top