3
\$\begingroup\$

See this this question for background. This is a live filter where results are filtered as you type. I've taken into account Kerri's answer and expanded on her recommendation. I've added a message that appears if no results are found, and a display for how many results are being displayed out of the original set. You can test this in the attached snippet.

I'm pretty happy with my work here, but I think the code is too large, and becoming complicated. Perhaps it can benefit from creating an object and splitting out the functionality into smaller methods.

var liveFilter = (function() {
  /**
   * Initializes a search field with associated list and filters it live while typing
   *
   * @param {string} searchFieldId     An ID of a search field input element
   * @param {string} listId            An ID of an element containing items that should be filtered
   * @param {object} [options]         Specifies additional options:
   *                                   * selector: A query to be run against list; the results
   *                                     will be filtered according to the value of the search field. If
   *                                     not specified, the filter matches all direct children.
   *                                   * filterClass: A class name to be applied to items that
   *                                     are going to be filtered out. If not specified, defaults to
   *                                     "filter-hidden".
   *                                   * counterElementId: An ID of an element that displays results count.
   *                                   * messageElementId: An ID of an element that displays a message when no results are found.
   */

  function liveFilter(searchFieldId, listId, options) {

    var searchField = document.getElementById(searchFieldId),
        list = document.getElementById(listId),
        selector,
        filterClass;

    if (!searchField) {
      throw new Error("No search element found with id " + searchFieldId);
    }

    if (!list) {
      throw new Error("No list element found with id " + listId);
    }

    if (options !== undefined) {
      selector = options.selector;
      filterClass = options.filterClass;

      if (options.counterElementId) {
        var counter = document.getElementById(options.counterElementId);

        if (!counter) {
          throw new Error("No counter element found with id " + options.counterElementId);
        }
      }

      if (options.messageElementId) {
        var messageContainer = document.getElementById(options.messageElementId);

        if (!messageContainer) {
          throw new Error("No counter element found with id " + options.messageElementId);
        }
      }
    }

    if (filterClass === undefined) {
      filterClass = "filter-hidden";
    }

    var handler = function searchFieldChanged() {
      var text = this.value,
          regexp = RegExp(text, 'i'),
          nodes = (selector === undefined) ? list.children
                                           : list.querySelectorAll(selector),
          filteredNodesCount = 0,
          node;

      for (var i = 0, nodesLength = nodes.length; i < nodesLength; i++) {
        node = nodes[i];

        if (node.textContent.search(regexp) < 0) {
          filteredNodesCount += 1;
          node.classList.add(options.filterClass);
        }
        else {
          node.classList.remove(options.filterClass);
        }

        // Counter should only be visible if:
        // 1. There is text in the search box
        // 2. A counter element id was passed in the options
        if (counter) {
          if (text) {
            counter.textContent = (nodesLength - filteredNodesCount) + "/" + nodesLength;
            counter.style.display = 'block';
          }
          else {
            counter.style.display = 'none';
          }
        }

        // Message should only be shown if:
        // 1. A message element id was passed in the options
        // 2. There is text in the search box
        // 3. All results get filtered and nothing is found.
        //    I.e. the number of results filtered is equal to the number of items
        if (messageContainer) {
          messageContainer.style.display =
            (filteredNodesCount === nodesLength) ? 'block' : 'none';
        }
      }
    };

    searchField.addEventListener("input", handler, false);
  }

  return liveFilter;
})();

// Usage

liveFilter('js-users-list-filter', 'js-users-list', {
  selector: 'li',
  filterClass: 'filter-hidden',
  counterElementId: 'js-users-list-filter-counter',
  messageElementId: 'js-users-list-filter-message'
});
#js-users-list-filter-message {
  display: none;  
}

#js-users-list-filter-counter {
  display: none;
}

.filter-hidden {
  opacity: .1;
}
<input type="text" id="js-users-list-filter" />
<p id="js-users-list-filter-counter"></p>
<div id="js-users-list-filter-message">
  <p>No users where found.</p>
</div>
<ol id="js-users-list">
  <li>Barney Gumble</li>
  <li>Bart Simpson</li>
  <li>Waylon Smithers</li>
  <li>Lisa Simpson</li>
  <li>Montegormey Burns</li>
  <li>Clarence Wiggum</li>
  <li>Ned Flanders</li>
  <li>Mo Szyslak</li>
  <li>Apu Nahasapeemapetilon</li>
  <li>Marg Simpson</li>
  <li>Nelson Muntz</li>
</ol>

\$\endgroup\$
  • \$\begingroup\$ Live filters is where frameworks like AngularJS excel. \$\endgroup\$ – Dmitri Zaitsev Apr 6 '15 at 4:31
  • \$\begingroup\$ That maybe true, but it would require me to introduce a framework into my existing framework (Rails), and start working with JSON data and APIs instead of just rendering HTML on the server, which, for my purposes, is good enough. \$\endgroup\$ – Mohamad Apr 6 '15 at 11:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.