Henrik Petterson
Henrik Petterson

Reputation: 7094

Highlight results when searching divs

I have a div setup like this:

<input id="search">

<div class="entry">
  <div class="title">hello world test 123</div>
  <div class="description">lorem ipsum test test1 testing</div>
</div>

<div class="entry">
  <div class="title">attack on titan</div>
  <div class="description">fullmetal alchemist</div>
</div>

And I allow the user to search the divs with:

jQuery(document).ready(function() {
    jQuery("#search").on("keyup click input", function () {
        var val = jQuery(this).val();
        if (val.length) {
            jQuery(".entry").hide().filter(function () {
                return jQuery('.title, .description',this).text().toLowerCase().indexOf(val.toLowerCase()) != -1;
            }).show();
        }
        else {
            jQuery(".entry").show();
        }
    });
});

Works great, try jsFiddle.

My question is, how can I highlight the search terms? For example, if the user searches for test, I want to wrap the text test into <span> tags.

EDIT: Note that I know how to search/replace text, but I can't seem to make it work properly with my search function.

Upvotes: 3

Views: 381

Answers (3)

cнŝdk
cнŝdk

Reputation: 32145

Optimised solution

After all the issues discussed in comments and trying to optimise the solution so it won't have any lack for eventual bugs, I refactored the code and optimised it:

$(document).ready(function() {
  $("#search").on("keyup click input", function() {
    var val = jQuery(this).val();
    var regExp = new RegExp(val, 'ig');
    var reg = new RegExp('<span class="highlight">(.+)<\/span>', 'ig');
    if (val.length) {
      $(".entry").hide().filter(function() {
        var found = $('.title, .description', this).text().toLowerCase().indexOf(val.toLowerCase()) != -1;
        if (val.length > 3) {
          $('.title, .description', this).each(function(k, v) {
            if ($(v).text().match(regExp)) {
              $(v).html($(v).text().replace(regExp, '<span class="highlight">$&</span>'));
            } else {
              $(v).html($(v).text().replace(reg, '$&'));
            }
          });
        } else {
          $('.title, .description', this).each(function(k, v) {
            $(v).html($(v).text().replace(reg, '$&'));
          });
        }
        return found;
      }).show();
    } else {
      $('.title, .description').each(function(k, v) {
        $(v).html($(v).text().replace(reg, '$&'));
      });
      $(".entry").show();
    }
  });
});
.highlight {
  background-color: blue
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="search">

<div class="entry">
  <div class="title">hello world test 123</div>
  <div class="description">lorem ipsum test test1 testing</div>
</div>

<div class="entry">
  <div class="title">attack on titan</div>
  <div class="description">fullmetal alchemist</div>
</div>

It loops over the elements and use a RegExp with a matching group and if the iterated element content matches the Regex replace the matched text with the same content wrapped in a span, otherwise just set the content to its original form.


Original Answer

This is how you should do it:

    var val = jQuery(this).val();
    if (val.length) {
      $(".entry").hide().filter(function() {
        var found = $('.title, .description', this).text().toLowerCase().indexOf(val.toLowerCase()) != -1;
        var regExp = new RegExp(val, 'ig');
        $('.title, .description', this).each(function(k, v) {
          if ($(v).text().toLowerCase().indexOf(val.toLowerCase()) != -1) {
            var newHTML = $(v).text().replace(regExp, '<span class="highlight">$&</span>');
            $(v).html(newHTML);
          }
        });
        return found;
      }).show();
    } else {
      $(".entry").show();
    }

You need to loop over the elements and use a RegExp with a matching group and if this element content matches your Regex replace the matched text with the same content wrapped in a span.

Demo:

This is a working Demo:

$(document).ready(function() {
  $("#search").on("keyup click input", function() {
      var val = jQuery(this).val();
      if (val.length) {
        $(".entry").hide().filter(function() {
            var found = $('.title, .description', this).text().toLowerCase().indexOf(val.toLowerCase()) != -1;
            var regExp = new RegExp(val, 'ig');
              $('.title, .description', this).each(function(k, v) {
                if ($(v).text().toLowerCase().indexOf(val.toLowerCase()) != -1) {
                  var newHTML = $(v).text().replace(regExp, '<span class="highlight">$&</span>');
                  $(v).html(newHTML);
                }
              });
          return found;
        }).show();
    } else {
      $('.title, .description').each(function(k, v) {
        var reg = new RegExp('<span class="highlight">(.+)<\/span>', 'ig');
        var newHTML = $(v).text().replace(reg, '$&');
        $(v).html(newHTML);
      });
      $(".entry").show();
    }
  });
});
.highlight {
  background-color: blue
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="search">

<div class="entry">
  <div class="title">hello world test 123</div>
  <div class="description">lorem ipsum test test1 testing</div>
</div>

<div class="entry">
  <div class="title">attack on titan</div>
  <div class="description">fullmetal alchemist</div>
</div>

Edit:

This is a Demo that highlights sentences only if more than 2 letters are typed:

$(document).ready(function() {
  $("#search").on("keyup click input", function() {
    var val = jQuery(this).val();
    if (val.length) {
      $(".entry").hide().filter(function() {
        var found = $('.title, .description', this).text().toLowerCase().indexOf(val.toLowerCase()) != -1;
        var regExp = new RegExp(val, 'ig');
        if (val.length > 2) {
          $('.title, .description', this).each(function(k, v) {
            if ($(v).text().toLowerCase().indexOf(val.toLowerCase()) != -1) {
              var newHTML = $(v).text().replace(regExp, '<span class="highlight">$&</span>');
              $(v).html(newHTML);
            }
          });
        } else {
          $('.title, .description').each(function(k, v) {
            var reg = new RegExp('<span class="highlight">(.+)<\/span>', 'ig');
            var newHTML = $(v).text().replace(reg, '$&');
            $(v).html(newHTML);
          });
        }
        return found;
      }).show();
    } else {
      $('.title, .description').each(function(k, v) {
        var reg = new RegExp('<span class="highlight">(.+)<\/span>', 'ig');
        var newHTML = $(v).text().replace(reg, '$&');
        $(v).html(newHTML);
      });
      $(".entry").show();
    }
  });
});
.highlight {
  background-color: blue
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="search">

<div class="entry">
  <div class="title">hello world test 123</div>
  <div class="description">lorem ipsum test test1 testing</div>
</div>

<div class="entry">
  <div class="title">attack on titan</div>
  <div class="description">fullmetal alchemist</div>
</div>

Upvotes: 2

Ethan
Ethan

Reputation: 4375

Try this:

document.getElementById('search').onkeyup = userInput;
document.getElementById('search').onclick = userInput;
document.getElementById('search').oninput = userInput;
var allEntries = document.querySelectorAll('.entry');

function userInput () {
    var val = this.value;
    for (var i = 0; i < allEntries.length; i++) {
        var entryElement    = allEntries[i];
        var title           = entryElement.querySelector('.title');
        var description     = entryElement.querySelector('.description');
        var noHtmlSearchStr = '';
        if (title)       noHtmlSearchStr +=       title.innerText;
        if (description) noHtmlSearchStr += description.innerText;
        if (noHtmlSearchStr.length > 0) {
            if (noHtmlSearchStr.toLowerCase().indexOf(val.toLowerCase()) != -1) {
                // Remove existing <b> tags.
                var regexp1 = new RegExp('(<b>|<\/b>)', 'gi');
                if (title)             title.innerHTML =       title.innerHTML.replace(regexp1, '');
                if (description) description.innerHTML = description.innerHTML.replace(regexp1, '');

                if (val.length > 3) {
                    var regexp2 = new RegExp('(' + val + ')(?!>)', 'gi');
                    if (title)             title.innerHTML =       title.innerHTML.replace(regexp2, '<b>$1</b>');
                    if (description) description.innerHTML = description.innerHTML.replace(regexp2, '<b>$1</b>');
                }
                entryElement.style.display = 'block';
            } else {
                entryElement.style.display = 'none';
            }
        }
    }
}
.entry {
    background: #fff;
    margin-bottom: 10px;
    width: 300px;
}
<input id="search">

<div class="entry">
    <div class="title">hello world test 123</div>
    <div class="description">div lorem <span>ipsum</span> test <div>test1</div> testing span</div>
</div>

<div class="entry">
    <div class="title">attack on titan</div>
    <div class="description">fullmetal alchemist</div>
</div>

<div class="entry"></div>

<div class="entry">
    <div class="title">attack on titan</div>
</div>

<div class="entry">
    <div class="description">Let's not go to Camelot, 'tis a silly place</div>
</div>

Explanation of JS code

  1. Bind all events to the userInput() function.
  2. Get all elements with the .entry class and store them in allEntries.
  3. Get the user's input and store in val.
  4. Iterate through allEntries.
  5. Get the text to search on from title and description and store in noHtmlSearchStr.
  6. If val matches some part of noHtmlSearchStr then show the entryElement, otherwise hide it.
  7. Remove <b> tags from title and description.
  8. If the length of the user's search (val) is longer than three characters, highlight the matches on the text, otherwise, don't highlight anything.

Upvotes: 2

prasanth
prasanth

Reputation: 22490

Try with contains(text) instead of filter() .initially hide the All div .Then Show only the text contains div .And apply the span element to matching letter in the children using new RegExp()

For ignore case sensitive match ig in regex and also added code for case insensitive for contains

Updated Fix with .title, .description on children

jQuery(document).ready(function() {
  jQuery("#search").on("input", function() {
    var val = jQuery(this).val()
    jQuery(".entry").hide()
    jQuery(".entry:contains(" + val + ")").show()
    jQuery(".entry").each(function() {
      if ($(this).find(".title, .description:contains(" + val + ")")) {
        $(this).find(".title, .description:contains(" + val + ")").html(function() {
          return $(this).text().replace(new RegExp('('+val+')', 'ig'), '<span>$1</span>')
        })
      }
    })
  });

})
jQuery.expr[':'].contains = function(a, i, m) {
  return jQuery(a).text().toUpperCase()
      .indexOf(m[3].toUpperCase()) >= 0;
};
.entry {
  background: #fff;
  margin-bottom: 10px;
  width: 300px;
}

span {
  color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="search">

<div class="entry">
  <div class="title">hello world test 123</div>
  <div class="description">lorem ipsum test test1 testing</div>
</div>

<div class="entry">
  <div class="title">Attack on titan</div>
  <div class="description">fullmetal alchemist</div>
</div>
<div class="entry">
<div>
  <div class="title">For nested element on titan</div>
  <div>
  <div class="description">fullmetal alchemist nested</div>
  </div>
  </div>
</div>

Upvotes: 2

Related Questions