github.com/darmach/terratest@v0.34.8-0.20210517103231-80931f95e3ff/docs/assets/js/collection-browser_search.js (about)

     1  /**
     2   * Javascript for the Collection Browser search.
     3   *
     4   * TOC:
     5   *  - FILTER FUNCTIONS - functions to extract the terms from DOM element(s) and use them in search engine to show/hide elements.
     6   *  - MAIN - INITIALIZE - initializes Browser Search and registers actions (click etc.) on filter components.
     7   *  - SEARCH ENGINE - here is the logic to show & hide elements accoriding to filters terms.
     8   *  - OTHER
     9   */
    10  (function () {
    11  
    12    /** ********************************************************************* **/
    13    /** *********                 FILTER FUNCTIONS                  ********* **/
    14    /** ********************************************************************* **/
    15  
    16    /**
    17     * Note
    18     * This function is wrapped in a "debounce" so that if the user is typing quickly, we aren't trying to run searches
    19     * (and fire Google Analytics events!) on every key stroke, but only when they pause from typing.
    20     * @type {Function}
    21     */
    22    const searchInputFilter = debounce(function (event) {
    23      const target = $(event.currentTarget)
    24      const collectionName = target.data('collection_name')
    25      filterSearchData(collectionName)
    26    }, 250);
    27  
    28  
    29    /** ********************************************************************* **/
    30    /** *********               MAIN - INITIALIZE                   ********* **/
    31    /** ********************************************************************* **/
    32  
    33    /**
    34    * Bind actions to search input and tags.
    35    * If you want to add more filter components (like cloud providers on gruntwork.io/guides)
    36    * you can register actions on them here.
    37    */
    38    function initializeCbSearch(el) {
    39      const collectionName = el.data('collection_name')
    40  
    41      /* SEARCH INPUT box on page */
    42      $('#cb-search-box-'+collectionName).on("keyup", searchInputFilter);
    43  
    44      /* Triggered when TAGS filter checkboxes are checked */
    45      $(document)
    46        .on('click', '[data-collection_name="'+collectionName+'"] .tags', function () {
    47          filterSearchData(collectionName);
    48        });
    49    }
    50  
    51    /* Find collection browser's search component on the page and initialize: */
    52    if($('.cb-search-cmp').length > 0) {
    53      $('.cb-search-cmp').each(function () {
    54        initializeCbSearch($(this))
    55        showNoResults(false)
    56      })
    57    }
    58  
    59    /** ********************************************************************* **/
    60    /** *********                 SEARCH ENGINE                     ********* **/
    61    /** ********************************************************************* **/
    62  
    63    /**
    64     * Filters posts/docs/entries against search input and tags.
    65     * It always gets terms from both: search input and tags whenever any of them changed.
    66     */
    67    function filterSearchData(collectionName) {
    68      // Get data from all filter components
    69      // a) Get Search input:
    70      const searchInputValue = $('#cb-search-box-'+collectionName).val().toLowerCase().split(" ").filter(v => v != '')
    71      // b) Get tags:
    72      let checkedTags = []
    73      $('[data-collection_name="'+collectionName+'"] input[type="checkbox"]:checked')
    74        .each(function () {
    75          checkedTags.push($(this).val())
    76        })
    77  
    78      // If there is no filter terms, show all posts. Otherwise, filter posts:
    79      if (searchInputValue.length === 0 && checkedTags.length === 0) {
    80        showNoResults(false)
    81        showAll()
    82      } else {
    83        // Get the list of posts and categories  to show
    84        const toShow = filterDocs(collectionName, searchInputValue, checkedTags)
    85  
    86        // If there is no posts to show, display no-results component
    87        if (toShow.docs.length === 0) {
    88          hideAll()
    89          showNoResults(true)
    90        } else {
    91          // Hide no-results component
    92          showNoResults(false)
    93          // Hide all elements
    94          hideAll()
    95          // Show elements
    96          toShow.docs.forEach(docId => {
    97            showDoc(docId)
    98          })
    99          toShow.categories.forEach(catId => {
   100            showCategory(catId)
   101          })
   102        }
   103      }
   104    }
   105  
   106    /**
   107     * Filter docs (entries) against search input and checked tags
   108     * It returns list of documents and categories to show (satisfying search terms).
   109     */
   110    function filterDocs(collectionName, searchInputValue, checkedTags) {
   111      // Fetch docs data
   112      const docs = fetchDocsData(collectionName)
   113      let toShowDocs = []
   114      let toShowCategories = []
   115      // Check each doc's data against search value and selected tags:
   116      docs.forEach(doc => {
   117        if (containsText(doc, searchInputValue) && containsTag(doc, checkedTags)) {
   118          toShowDocs.push(doc.id)
   119          if (toShowCategories.indexOf(doc.category) === -1) {
   120            toShowCategories.push(doc.category.replace(/\s+/g, '-'))
   121          }
   122        }
   123      })
   124      return { docs: toShowDocs, categories: toShowCategories }
   125    }
   126  
   127    function containsTerms(content, terms) {
   128      let allMatches = true
   129      terms.forEach(term => {
   130        if (content.indexOf(term.toLowerCase()) < 0) {
   131          allMatches = false
   132        }
   133      })
   134      return allMatches
   135    }
   136  
   137    function containsText(doc, terms) {
   138      const content = doc.text || doc.title + " " + doc.excerpt + " " + doc.category + " " + doc.content + " " + doc.tags
   139      return containsTerms(content, terms)
   140    }
   141  
   142    function containsTag(doc, terms) {
   143      const content = doc.tags
   144      return containsTerms(content, terms)
   145    }
   146  
   147    /**
   148     * Function to fetch posts/docs data.
   149     * Now it gets from window, but it can be transformed to get it from API.
   150     */
   151    function fetchDocsData(collectionName) {
   152      return window['bc_'+collectionName+'Entries']
   153    }
   154  
   155    /** ********************************************************************* **/
   156    /** *********                 OTHER                             ********* **/
   157    /** ********************************************************************* **/
   158  
   159    // Returns a function, that, as long as it continues to be invoked, will not be
   160    // triggered. The function will be called after it stops being called for N
   161    // milliseconds. If `immediate` is passed, trigger the function on the leading
   162    // edge, instead of the trailing. Ensures a given task doesn't fire so often
   163    // that it bricks browser performance. From:
   164    // https://davidwalsh.name/javascript-debounce-function
   165    function debounce(func, wait, immediate) {
   166      let timeout
   167      return function () {
   168        const context = this,
   169          args = arguments
   170        const later = function () {
   171          timeout = null
   172          if (!immediate)
   173            func.apply(context, args)
   174        };
   175        const callNow = immediate && !timeout
   176        clearTimeout(timeout)
   177        timeout = setTimeout(later, wait)
   178        if (callNow)
   179          func.apply(context, args)
   180      };
   181    }
   182  
   183    /**
   184     * Functions to show & hide items on the page
   185     */
   186    function showAll() {
   187      $('.cb-doc-card').show()
   188      $('.category-head').show()
   189      $('.categories ul li').show()
   190    }
   191  
   192    function hideAll() {
   193      $('.cb-doc-card').hide()
   194      $('.category-head').hide()
   195      $('.categories ul li').hide()
   196    }
   197  
   198    function showDoc(docId) {
   199      $('#' + docId + '.cb-doc-card').show()
   200    }
   201  
   202    function showCategory(categoryId) {
   203      $(`.categories ul [data-category=${categoryId}]`).show()
   204      $(`#${categoryId}.category-head`).show()
   205    }
   206  
   207    /**
   208     * Show / hide no-results component
   209     */
   210     function showNoResults(state) {
   211       if (state) {
   212         $('#no-matches').show()
   213       } else {
   214         $('#no-matches').hide()
   215       }
   216     }
   217  
   218  }());