Search code examples
javascriptjquerysearchforeachfiltering

add [and] + [or ] to search filter for a foreach generated list


So I already have a search filter in place but now I want it to have the ability to combine search phrases. Below is the code that generates a list on page.

<div class="sortable2">
  <ul class="connectedSortable links loadfiles" id="loadfiles">
    <?php
      foreach ($result as $value) {
        list($classname, $origin, $name) = explode('_', $value);
        $classname = trim($classname, '[]');
        $origin    = trim($origin, '[]');
        $name      = pathinfo($name, PATHINFO_FILENAME);
        echo "<li class='audiofile " . $name . " " . $classname . "' id='" . $value . "'>".
                "<a class='btn_clone fa fa-clone' aria-hidden='true' id='' onclick='repeat(event)' title='Clone'>&nbsp;</a>".
                "<a class='btn_addto fa fa-arrow-up' aria-hidden='true' id='' onclick='addto(event)' title='Add to playlist'>&nbsp;</a>".
                "<a class='btn_removefrom fa fa-trash' aria-hidden='true' id='' onclick='removefrom(event)' title='Remove element'>&nbsp;</a>".
                "<span class='audioclass'>" . $classname . "</span>".
                "<a href='" . $directoryname . "/" . $value . "' target='_blank'>".
                  "<img src='images/avatars/" . $classname . ".jpg'>".
                  "<div class='audiotext'>".
                    "<span class='audiotitle'>" . $name . "</span>".
                    "<span class='audioorigin'>" . $origin .  "</span>".
                  "</div>".
                "</a>".
              "</li>";
      }
    ?>
  </ul>

</div>

This list basically generates blocks like:

frank
hello how are you
link to audio file

william
i am fine
link to audio file

frank
what?
link to audio file

The filtering is done by this code

$('#global_filter').keyup(function() {
    var col_name = $(this).attr('class');
    var search_val = $(this).val().toLowerCase();
    $('.' + col_name).closest('#loadfiles > li').css('display', 'none');
    $('.' + col_name).each(function() {
        var val = $(this).text();
        console.log($(this).text(), 'text');
        if(val.toLowerCase().indexOf(search_val) >= 0) {
            $(this).closest('#loadfiles > li').css('display', 'block');
        }
    });
});

which works together with

<div class="input">
  <h4>Search field</h4>
  <div class="all_all" id="filter_global">
    <div align="left"><input type="text" name="global_filter" id="global_filter" class="audiofile"></div>
    <div align="left"><input type="checkbox" name="global_regex"  id="global_regex" ></div>
    <div align="left"><input type="checkbox" name="global_smart"  id="global_smart"  checked></div>
  </div>
</div>

Question

How can I change the filter to allow for multiple search phrases with [AND] and maybe also [OR] if possible. So the user can type in for instance:

frank [and] hello

and this will then return

frank
hello how are you
link to audio file

Solution

  • Although this project appears to have not been updated in quite some time, you can probably utilize parts of it to work for your needs: https://github.com/bloomtime/boolean-expression-js

    $('#global_filter').keyup(function() {
    
        // Init
        var col_name = $(this).attr('class');
        var search_val = $(this).val().toLowerCase();
    
        // Setup boolean expression
        var parsed = new Expression(search_val);
    
        $('.' + col_name).closest('#loadfiles > li').css('display', 'none');
        $('.' + col_name).each(function() {
            var val = $(this).text();
            if(parsed.test(val) == true) {
                $(this).closest('#loadfiles > li').css('display', 'block');
            }
        });
    });
    

    It utilizes ReParse behind the scenes to be able to split your search on pre-defined grammar and then test for matching.

    Edit


    If you are really trying to keep it super simple, it may not be extremely flexible, but you could try this approach. This basically gives the ability to search using [AND] or using [OR] but not both. Could probably use some refactoring as I just quickly whipped it up.

    $('#global_filter').keyup(function() {
    
        // Init
        var col_name = $(this).attr('class');
        var search_val = $(this).val().toLowerCase();
        var columns = $('.' + col_name);
    
        // If doing a boolean AND
        if (search_val.toLowerCase().indexOf('[and]') >= 0) {
            // Get search parts
            var parts = search_val.split('[and]');
            $(columns).each(function(columnIndex, column) {
                var val = $(column).text();
                var matched = true;
                $(parts).each(function(partIndex, part) {
                    // Since AND is inclusive, failing to match should assume this column is a non-match
                    if (val.toLowerCase().indexOf(part.toLowerCase()) < 0) {
                        matched = false;
                        // Break early
                        return false;
                    }
                });
                if (matched) {
                    $(column).closest('#loadfiles > li').css('display', 'block');
                }
            });
        }
    
        // If doing a boolean OR
        else if (search_val.toLowerCase().indexOf('[or]') >= 0) {
            // Get search parts
            var parts = search_val.split('[or]');
            $(columns).each(function(columnIndex, column) {
                var val = $(column).text();
                var matched = false;
                $(parts).each(function(partIndex, part) {
                    // With OR, if ANY of the parts match then it is a match
                    if (val.toLowerCase().indexOf(part.toLowerCase()) >= 0) {
                        matched = true;
                        // Break early
                        return false;
                    }
                });
                if (matched) {
                    $(column).closest('#loadfiles > li').css('display', 'block');
                }
            });
        } else {
            var val = $(this).text();
            if(val.toLowerCase().indexOf(search_val) >= 0) {
                $(column).closest('#loadfiles > li').css('display', 'block');
             }
        }
    });