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 }());