0

I've got an in page text search using JS, which is here:

$.fn.eoTextSearch = function(pat) {
    var out = []
    var textNodes = function(n) {
        if (!window['Node']) {
            window.Node = new Object();
            Node.ELEMENT_NODE = 1;
            Node.ATTRIBUTE_NODE = 2;
            Node.TEXT_NODE = 3;
            Node.CDATA_SECTION_NODE = 4;
            Node.ENTITY_REFERENCE_NODE = 5;
            Node.ENTITY_NODE = 6;
            Node.PROCESSING_INSTRUCTION_NODE = 7;
            Node.COMMENT_NODE = 8;
            Node.DOCUMENT_NODE = 9;
            Node.DOCUMENT_TYPE_NODE = 10;
            Node.DOCUMENT_FRAGMENT_NODE = 11;
            Node.NOTATION_NODE = 12;
        }
        if (n.nodeType == Node.TEXT_NODE) {
            var t = typeof pat == 'string' ?
            n.nodeValue.indexOf(pat) != -1 :
            pat.test(n.nodeValue);
            if (t) {
                out.push(n.parentNode)
            }
        }
        else {
            $.each(n.childNodes, function(a, b) {
                textNodes(b)
            })
        }
    }
    this.each(function() {
        textNodes(this)
    })
    return out
};

And I've got the ability to hide columns and rows in a table. When I submit a search and get the highlighted results, there would be in this case, the array length of the text nodes found would be 6, but there would only be 3 highlighted on the page. When you output the array to the console you get this:

So you get the 3 tags which I was expecting, but you see that the array is actually consisting of a [span,undefined,span,undefined,undefined,span]. Thus giving me the length of 6.

<span>
<span>
<span>
[span, undefined, span, undefined, undefined, span]

I don't know why it's not stripping out all of the undefined text nodes when I do the check for them. Here's what I've got for the function.

performTextSearch = function(currentObj){
    if($.trim(currentObj.val()).length > 0){
        var n = $("body").eoTextSearch($.trim(currentObj.val())),
            recordTitle = "matches",
            arrayRecheck = new Array(),
            genericElemArray = new Array()
        if(n.length == 1){
            recordTitle = "match"
        }

        //check to see if we need to do a recount on the array length. 
        //if it's more than 0, then they're doing a compare and we need to strip out all of the text nodes that don't have a visible parent.
        if($(".rows:checked").length > 0){
            $.each(n,function(i,currElem){
                if($(currElem).length != 0 && typeof currElem != 'undefined'){
                    if($(currElem).closest("tr").is(":visible") || $(currElem).is(":visible")){
                        //remove the element from the array
                        console.log(currElem)
                        arrayRecheck[i] = currElem
                    }
                }
            })
        }
        if(arrayRecheck.length > 0){
            genericElemArray.push(arrayRecheck)
            console.log(arrayRecheck)
        }
        else{
            genericElemArray.push(n)    
        }
        genericElemArray = genericElemArray[0]
        $("#recordCount").text(genericElemArray.length + " " +recordTitle)
        $(".searchResults").show()
        for(var i = 0; i < genericElemArray.length; ++i){ 
            void($(genericElemArray[i]).addClass("yellowBkgd").addClass("highLighted"))
        }   
    }
    else{
        $(".highLighted").css("background","none")  
    }           
}

If you look at the code below "//check to see if we need to do a recount on the array length. ", you'll see where I'm stripping out the text nodes based off of the display and whether or not the object is defined. I'm checking the length instead of undefined because the typeof == undefined wasn't working at all for some reason. Apparently, things are still slipping by though.

Any idea why I'm still getting undefined objects in the array?

My apologies for such a big post!

Thanks in advance

Michael Stone
  • 950
  • 2
  • 14
  • 30
  • What do you mean by *"recount the array length"*? When you have removed invisible items from the array and process the array afterwards, just ignore `undefined` elements - wouldn't that be all that's necessary? – Tomalak Jul 25 '11 at 08:29
  • I've been trying to just remove them if they're undefined, but each attempt has failed in some way. If you look at if($(currElem).length != 0 && typeof currElem != 'undefined'){ You'll see I'm trying to keep the array clean from any unwanted elements and then checking to see if the parent is visible or not. – Michael Stone Jul 25 '11 at 13:20

3 Answers3

1

I've modified your eoTextSearch() function to remove dependencies on global variables in exchange for closures:

$.fn.extend({
  // helper function
  // recurses into a DOM object and calls a custom function for every descendant
  eachDescendant: function (callback) {
    for (var i=0, j=this.length; i<j; i++) {
      callback.call(this[i]);
      $.fn.eachDescendant.call(this[i].childNodes, callback);
    }
    return this;
  },
  // your text search function, revised
  eoTextSearch: function () {
    var text = document.createTextNode("test").textContent 
               ? "textContent" : "innerText";
    // the "matches" function uses an out param instead of a return value
    var matches = function (pat, outArray) {
      var isRe = typeof pat.test == "function";
      return function() {
        if (this.nodeType != 3) return; // ...text nodes only
        if (isRe && pat.test(this[text]) || this[text].indexOf(pat) > -1) {
          outArray.push(this.parentNode);
        }
      }
    };
    // this is the function that will *actually* become eoTextSearch()
    return function (stringOrPattern) {
      var result = $(); // start with an empty jQuery object
      this.eachDescendant( matches(stringOrPattern, result) );
      return result;
    }
  }()  // <- instant calling is important here
});

And then you can do something like this:

$("body").eoTextSearch("foo").filter(function () {
  return $(this).closest("tr").is(":visible");
});

To remove unwanted elements from the search result. No "recounting the array length" necessary. Or you use each() directly and decide within what to do.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • I can't use the filter() method because it's only valid in IE9 and it's not exactly the "best solution" to just use a different method if it's IE8 or lower. Also, unless you're doing something I'm not aware of, this wouldn't work in IE. The DOM Node Object needs to be defined when it's not there. – Michael Stone Jul 25 '11 at 12:55
  • Clarifying, in regards to Internet Explorer, it's only valid in IE9. – Michael Stone Jul 25 '11 at 13:58
  • @Michael: Of course it is valid in *any* browser. It's a standard jQuery function, it works anywhere where jQuery works. – Tomalak Jul 25 '11 at 14:14
  • Ah. Thought you were using the JavaScript 1.6 filter(). – Michael Stone Jul 25 '11 at 14:20
  • @Michael: No, my version of `eoTextSearch()` returns jQuery. – Tomalak Jul 25 '11 at 14:23
0

I cannot entirely get my head around your code, but the most likely issue is that you are removing items from the array, but not shrinking the array afterwards. Simply removing items will return you "undefined", and will not collapse the array.

I would suggest that you do one of the following:

  1. Copy the array to a new array, but only copying those items that are not undefined

  2. Only use those array items that are not undefined.

I hope this is something of a help.

Schroedingers Cat
  • 3,099
  • 1
  • 15
  • 33
0

Found the answer in another post.

Remove empty elements from an array in Javascript

Ended up using the answer's second option and it worked alright.

Community
  • 1
  • 1
Michael Stone
  • 950
  • 2
  • 14
  • 30