Results page and collection filtering

Highlighted
New Member
5 0 0

I have been thinking about this problem and found it to be a little complex.

I built a custom tag filter for the collections page that allows the shop owner to add 'size-' , 'color-' etc to tags and then the collection page will let them filter by certain properties..
Screen Shot 2020-07-16 at 1.04.12 PM.png What I want to do now is apply a similar filter for the search results...
The problem is, search results are not collections so functionality is going to be different. For example a search result might return a link to a blog or some other page.
One solution was to create a new snippet that looks exactly like the filter but is customized for search results but that would then involve having to refresh or re-list each time a tag is clicked. 

My question is, has anyone built something similar and if so, what problems arise and what do I need to be careful of when doing this?

0 Likes
Highlighted
New Member
5 0 0

So far I have put the tags as a hidden div on each product rendered, then I was thinking of using JS to filter out the divs that have the tags. It may work.. Any one else have any ideas on this?

0 Likes
Highlighted
New Member
5 0 0

If anyone else decides to embark upon filtering search results.. I worked on it over the weekend, It is certainly not super efficient in terms of time complexity if the data set was bigger but since I know for a fact how many results it will display per page and how many tags I can expect I can afford the risk...
Here is what I came up with.. Its in JS look for the script tags. Please comment and critique I want to learn and get better..

<!-- /templates/search.liquid -->
{% comment %}

  To return only products or pages in results:
    - http://docs.shopify.com/manual/configuration/store-customization/return-only-product-in-storefront-search-results
    - Or manually add type=product or type=page to the search URL as a parameter

{% endcomment %}

{% comment %}
  Avoid accessing search.results before the opening paginate tag.
  If you do, the pagination of results will be broken.
{% endcomment %}
{% paginate search.results by 12 %}

  <div class="grid" data-section-type="search">
    <div class="grid__item">
      <header class="section-header text-center">
        {% if search.performed %}
          {% if search.results_count == 0 %}
            <h1 class="text-center">{{ 'general.search.no_results_html' | t: terms: search.terms }}</h1>
          {% else %}
            <h1 class="text-center">{{ 'general.search.results_for_html' | t: terms: search.terms }}</h1>
          {% endif %}
        {% else %}
          <h1 class="text-center">{{ 'general.search.title' | t }}</h1>
        {% endif %}
        <hr class="hr--small">
      </header>
     
      {% include 'search-bar', search_btn_style: 'btn', search_bar_location: 'search-bar--page' %}

      {% if search.performed %}
      {% include 'popup' %}


<!--   -->
<!-- start of filters with tags -->
<div class="custom--flex-wrap">
  <div class="custom--flex-column">
    <h6  style="font-size: x-small;">Filter Products by</h6>
          <div class="custom-filter-row custom-collapsible-menu">
            <input onclick="animateSideArrow('color-filter-menu')" type="checkbox" id="color-filter-menu">
              <label for="color-filter-menu">Color <i class="fa fa-angle-down"></i></label>
                <div class="custom-menu-content">
                    <ul class="subnav clearfix" id="color">
                    
                    {% for product in search.results %}  
                       {% for tag in product.tags %}
            
                             {% if tag contains "color-"%} 
                
                                  <li class="unselect-tag" onclick="filterBy(event)">
                                    {{ tag | remove: "color-" }}
                                  </li>
                              {% endif %} 
                          {% endfor %}
                  
                      {% endfor %}
                    
                  </ul>
                </div>
           </div>
           <div class="custom-filter-row custom-collapsible-menu">
            <input onclick="animateSideArrow('material-filter-menu')" type="checkbox" id="material-filter-menu">
              <label for="material-filter-menu">Material <i class="fa fa-angle-down"></i></label>
                <div class="custom-menu-content">
                    <ul class="subnav clearfix" id="material">
                    
                    {% for product in search.results %}  
                       {% for tag in product.tags %}
            
                             {% if tag contains "material-"%} 
                
                                  <li class="unselect-tag" onclick="filterBy(event)">
                                    {{ tag | remove: "material-"  }}
                                  </li>
                              {% endif %} 
                          {% endfor %}
                  
                      {% endfor %}
                    
                  </ul>
                </div>
           </div>
           <div class="custom-filter-row custom-collapsible-menu">
            <input onclick="animateSideArrow('clasp-filter-menu')" type="checkbox" id="clasp-filter-menu">
              <label for="clasp-filter-menu">Closure type <i class="fa fa-angle-down"></i></label>
                <div class="custom-menu-content">
                    <ul class="subnav clearfix" id="clasp">
                    
                    {% for product in search.results %}  
                       {% for tag in product.tags %}
            
                             {% if tag contains "clasp-"%} 
                
                                  <li class="unselect-tag"  onclick="filterBy(event)">
                                    {{ tag | remove: "clasp-" }}
                                  </li>
                              {% endif %} 
                          {% endfor %}
                  
                      {% endfor %}
                    
                  </ul>
                </div>
           </div>
           <div class="custom-filter-row custom-collapsible-menu">
            <input onclick="animateSideArrow('penmat-filter-menu')" type="checkbox" id="penmat-filter-menu">
              <label for="penmat-filter-menu">Pendant Material <i class="fa fa-angle-down"></i></label>
              <div class="custom-menu-content">
              <ul class="subnav clearfix" id="pen-mat">
                {% for product in search.results %}  
                {% for tag in product.tags %}
     
                      {% if tag contains "pen-mat-"%} 
         
                           <li class="unselect-tag"  onclick="filterBy(event)">
                             {{ tag | remove: "pen-mat-" }}
                           </li>
                       {% endif %} 
                   {% endfor %}
           
               {% endfor %}
              </ul>
            </div>
          </div>
    
          <div class="custom-filter-row custom-collapsible-menu">
            <input onclick="animateSideArrow('category-filter-menu')" type="checkbox" id="category-filter-menu">
              <label for="category-filter-menu">Category <i class="fa fa-angle-down"></i></label>
              <div class="custom-menu-content">
              <ul class="subnav clearfix" id="category">
                {% for product in search.results %}  
                {% for tag in product.tags %}
     
                      {% if tag contains "category-"%} 
         
                           <li class="unselect-tag"  onclick="filterBy(event)">
                             {{ tag | remove: "category-" }}
                           </li>
                       {% endif %} 
                   {% endfor %}
           
               {% endfor %}
              </ul>
            </div>
          </div>
    
          <div class="custom-filter-row custom-collapsible-menu">
            <input onclick="animateSideArrow('size-filter-menu')" type="checkbox" id="size-filter-menu">
              <label for="size-filter-menu">Size <i class="fa fa-angle-down"></i></label>
              <div class="custom-menu-content">
              <ul class="subnav clearfix" id="size">
                {% for product in search.results %}  
                {% for tag in product.tags %}
     
                      {% if tag contains "size-"%} 
         
                           <li class="unselect-tag"  onclick="filterBy(event)">
                             {{ tag | remove: "size-" }}
                           </li>
                       {% endif %} 
                   {% endfor %}
           
               {% endfor %}
              </ul>
            </div>
          </div>
<style>
  /* Contain floats: nicolasgallagher.com/micro-clearfix-hack/ */
  .clearfix:before,
  .clearfix:after {
    content: "";
    display: table;
  }
  .clearfix:after {
    clear: both;
  }
  .clearfix {
    zoom: 1;
  }
  /* Subnavigation styles */
  .subnav {
    clear: both;
    list-style-type: none;
    padding: 0;
  }
  .subnav li {
    display: block;
    float: left;
  }
  .subnav li {
    cursor: pointer;
  }
  .subnav li:hover{
    background: #666;
    color: #fff;
  }
</style>
</div>
<!-- end of filters with tags -->


      <hr class="hr--medium hr--clear">
        <div class="grid-uniform">
          {% for item in search.results %}
            {% if item.object_type == 'product' %}
              {% assign product = item %}
              
              {% include 'custom-search-filter-product' %}

              {% else %}
              <div class="grid__item grid-search large--one-third medium--one-half">
                <div class="grid-search__page">
                  <a href="{{ item.url }}" class="grid-search__page-link">
                    <span class="grid-search__page-content">
                      <span class="h4 text-center">{{ item.title }}</span>
                      {{ item.content | strip_html | truncatewords: 60 }}
                    </span>
                  </a>
                </div>
              </div>
            {% endif %}

          {% endfor %}
        </div>
      </div>

        {% if paginate.pages > 1 %}
          {% include 'pagination' %}
        {% endif %}

      {% endif %}

    </div>
  </div>

{% endpaginate %}
<script>
  let filterArray = ['color', 'material', 'clasp', 'pen-mat','category', 'size' ]
  let allHiddenFilterDivs = document.getElementsByClassName("hidden-filter")
  let resultsContainer = document.getElementsByClassName("grid-uniform")[0]
  let _filterByArray = []; //This array is the clicked on tags

  function filterBy(event){
    event.preventDefault();

    //Already selected the tag 
    if(_filterByArray.includes(event.target.innerText)){
      _filterByArray = _filterByArray.filter(el=> el !== event.target.innerText)
      let checkedTags = checkedFilterArray(_filterByArray)
      hideTaggedElements(resultsContainer, checkedTags)
      event.target.classList.remove("select-tag");
      event.target.classList.add("unselect-tag");
    }
    //Selected a new tag
    else{
      _filterByArray.push(event.target.innerText);
      let checkedTags = checkedFilterArray([... new Set(_filterByArray) ]) //remove duplicate tags
      hideTaggedElements(resultsContainer, checkedTags)
      event.target.classList.remove("unselect-tag");
      event.target.classList.add("select-tag");
    }
  }

//This finds the hidden divs attached to a product/search result and returns an array of matching products based on filterBy
  const checkedFilterArray=(filterByArray)=>{
      let _filterSelectionElements = []; //this array is the matching elements of clicked tags
      let tagsArray = [...allHiddenFilterDivs] 
      let selectedTags = [...filterByArray]
      for (let i = 0; i < tagsArray.length; i++) {
        for (let j=0; j< selectedTags.length; j++){
          if(tagsArray[i].innerText.includes( selectedTags[j] ) ){
            _filterSelectionElements.push(tagsArray[i].parentElement.parentElement)
         }
        }
      }
      return removeDuplicates(_filterSelectionElements, item => item.innerText);
  }

//Sets the divs to hidden based on the selected tags in sideBar filters
function hideTaggedElements(container, newElements){
    let allResults = Array.from(container.childNodes)
    let count = 0;
    allResults = allResults.filter(elem=> elem.className == "grid__item grid-product large--one-third medium--one-half" )
    allResults.forEach(elem => {
      elem.style.display = 'none'
    })
    //This could cause problems on larger datasets but should manage okay with pagination set at 12
    //Also depends on tag sizes.. So far no problems.. If script is too slow look here for optimisation
    allResults.forEach(element => { 
      for(let i=0; i< newElements.length; i++){
        if(newElements[i].innerHTML == element.innerHTML){
          element.style.display = 'block'
          count += 1
      }}
    })
    if(count < 1){
        allResults.forEach(elem => {
         elem.style.display = 'block'
        })
      }
  }

//Useful function for removing duplicated objects based on key data
function removeDuplicates(data, key) {
          return [
            ...new Map(data.map(item => [key(item), item])).values()
          ]
        };

function renderSideBarFilters(){
  const createSingleUseTags = (filterType)=>{
    let filterTagArray = []
    let locationOnDom = document.getElementById(`${filterType}`)
    for(item of locationOnDom.children){
            if(item.localName === 'li'){
              filterTagArray.push(item)
            }
         }
    let filterTagsList = removeDuplicates(filterTagArray, item => item.innerText);
    locationOnDom.innerHTML = ''
    for (let i = 0; i < filterTagsList.length; i++) {
      locationOnDom.appendChild(filterTagsList[i])  
    }
  }
//Creates the tags selectors in sidebar, preventing duplicates
  for (let index = 0; index < filterArray.length; index++) {
    createSingleUseTags(filterArray[index]); 
  }
}

  function animateSideArrow(id){
    let rightArrowIcon = `<i class="fa fa-angle-right"></i>`
    let downArrowIcon = `<i class="fa fa-angle-down"></i>`
    let filters = document.getElementById(id)
    let label = filters.nextElementSibling.innerHTML
    if(label.includes(downArrowIcon)){
      label = label.replace(downArrowIcon, rightArrowIcon)
      filters.nextElementSibling.innerHTML = label
    }
    else if(label.includes(rightArrowIcon)){
      label = label.replace(rightArrowIcon, downArrowIcon)
      filters.nextElementSibling.innerHTML = label
    }
  }          

  renderSideBarFilters();

</script>

 

0 Likes