1//create the scry search window object 2window.scrySearch = { 3 version: '1.0.0', //version of the window object 4 searchForms: [], //collection of search forms on the page 5 upgrades: {}, //array of upgrades that have been registered for custom functionality 6 //initialize the window object 7 init: function () { 8 this.searchForms = document.querySelectorAll('form'); 9 10 //first, get all forms on the page 11 var candidateFormElements = Array.from(document.querySelectorAll('form')); 12 13 //get search forms 14 var searchFormElements = candidateFormElements.filter(function (form) { 15 if (!form) return false; 16 17 var role = form.getAttribute('role'); 18 if (role && role.toLowerCase() === 'search') { 19 return true; 20 } 21 22 return !!form.querySelector('input[type="text"][name="s"], input[type="search"][name="s"]'); 23 }); 24 25 //convert the search form elements to search form objects 26 this.searchForms = searchFormElements.map(function (formElement) { 27 return new ScrySearch_SearchForm(formElement); 28 }); 29 30 //emit a custom event to let other scripts know the window is ready 31 document.dispatchEvent(new CustomEvent('scrySearchReady', { 32 detail: { 33 version: this.version, 34 searchForms: this.searchForms, 35 upgrades: this.upgrades, 36 } 37 })); 38 }, 39 40 //get all search forms 41 getSearchForms: function () { 42 return this.searchForms; 43 }, 44 45 //get all search forms with a particular class set on the <form> tag 46 getSearchFormsByClass: function (className) { 47 return this.searchForms.filter(function (form) { 48 return form?.formElement?.classList.contains(className) || false; 49 }); 50 }, 51 52 //add an upgrade to the window object 53 registerUpgrade: function (name, version) { 54 if (!this.upgrades[name]) { 55 this.upgrades[name] = new ScrySearch_Upgrade(name, version); 56 } else { 57 console.warn('Upgrade already exists, skipping', name); 58 } 59 }, 60 61} 62 63//an object that backs a search form in the dom and provides an anchor point for logic 64class ScrySearch_SearchForm { 65 constructor(formElement) { 66 this.formElement = formElement; 67 this.data = { 68 core: { 69 autoSuggestElement: null, //holds the autosuggest results container element 70 autosuggestResults: [], //holds the autosuggest results data 71 }, 72 }; //arbitrary data storage for the search form 73 74 this.preSubmitActions = []; //a list of ScrySearch_SubmitAction objects to call before the form is submitted 75 this.postSubmitActions = []; //a list of ScrySearch_SubmitAction objects to call after the form is submitted 76 77 this.preSubmitAjaxActions = []; //a list of ScrySearch_SubmitAction objects to call before the form is submitted via AJAX 78 this.postSubmitAjaxActions = []; //a list of ScrySearch_SubmitAction objects to call after the form is submitted via AJAX 79 80 this.searchInput = formElement.querySelector('input[type="text"][name="s"], input[type="search"][name="s"]'); 81 82 this._ajaxRequestId = 0; //monotonically increasing id used to drop stale AJAX responses 83 84 //ensure when the formElement is submitted, we always call the submit function first 85 this.formElement.addEventListener('submit', (e) => { 86 e.preventDefault(); 87 this.submit(); 88 }); 89 90 // Ensure *all* callers hit the debounce (even external code calling form.submitAjax()). 91 this.submitAjax = this.debounceAjaxSubmit(this.submitAjax.bind(this)); 92 } 93 94 //submit the search form 95 submit() { 96 97 //sort and call the pre submit handles 98 this.preSubmitActions.sort((a, b) => a.order - b.order).forEach(action => action.call(this)); 99 100 //submit the form 101 this.formElement.submit(); 102 103 //sort and call the post submit actions 104 //these will likely not be used much due to the page refresh on submit 105 this.postSubmitActions.sort((a, b) => a.order - b.order).forEach(action => action.call(this)); 106 } 107 108 //submit the search form via AJAX 109 async submitAjax() { 110 //sort and call the pre submit ajax handles 111 this.preSubmitAjaxActions.sort((a, b) => a.order - b.order).forEach(action => action.call(this)); 112 113 //submit the form via AJAX 114 var data = await this._handleAjaxSubmit(); 115 116 //sort and call the post submit ajax actions 117 this.postSubmitAjaxActions.sort((a, b) => a.order - b.order).forEach(action => action.call(this, data)); 118 119 //return the data 120 return data; 121 } 122 123 //handle ajax submission 124 async _handleAjaxSubmit() { 125 // The REST endpoint only exists for autosuggest; never fire requests when it is disabled. 126 if (!scrySearchWindow.autoSuggestEnabled || !scrySearchWindow.restApiUrl) { 127 return undefined; 128 } 129 // Tag this request so out-of-order responses can be dropped. 130 const requestId = ++this._ajaxRequestId; 131 // Turn FormData into a JSON-able object (supports names like filters[facets][], etc.). 132 const deepSet = (obj, path, value) => { 133 if (Object(obj) !== obj) return obj; 134 if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || []; 135 path.slice(0, -1).reduce((a, c, i) => 136 Object(a[c]) === a[c] 137 ? a[c] 138 : a[c] = (Math.abs(path[i + 1]) >> 0) === +path[i + 1] ? [] : {}, 139 obj 140 )[path[path.length - 1]] = value; 141 return obj; 142 }; 143 144 //call the deep set function to convert the form data to a json object 145 const formData = new FormData(this.formElement); 146 const formDataObject = {}; 147 for (const [path, value] of formData) { 148 deepSet(formDataObject, path, value); 149 } 150 //send a request to the rest api 151 var searchResults = await fetch(scrySearchWindow.restApiUrl, { 152 method: 'POST', 153 headers: { 154 'Content-Type': 'application/json', 155 }, 156 credentials: 'same-origin', 157 body: JSON.stringify(formDataObject), 158 }); 159 var data = await searchResults.json(); 160 161 // A newer request started while this one was in flight: signal the caller to keep the newer results. 162 if (requestId !== this._ajaxRequestId) { 163 return undefined; 164 } 165 166 return data; 167 } 168 169 //debounce the ajax submission 170 debounceAjaxSubmit(fn, waitMs = 250) { 171 // Debounce network submissions so rapid typing can't overwhelm the server. 172 // Returns a promise so callers can still await the eventual AJAX result. 173 let timerId = null; 174 return (...args) => { 175 if (timerId) { 176 clearTimeout(timerId); 177 } 178 return new Promise((resolve, reject) => { 179 timerId = setTimeout(async () => { 180 try { 181 resolve(await fn(...args)); 182 } catch (err) { 183 reject(err); 184 } 185 }, waitMs); 186 }); 187 }; 188 } 189 190 //add actions to the pre submit actions 191 addPreSubmitAction(func, order) { 192 //ensure the function of the submit action is not already in the list 193 if (this.preSubmitActions.some(action => action.func === func)) { 194 console.warn('Submit action already exists, skipping', func); 195 return; 196 } 197 this.preSubmitActions.push(new ScrySearch_SubmitAction(func, order)); 198 } 199 200 //add actions to the post submit actions 201 addPostSubmitAction(func, order) { 202 //ensure the function of the submit action is not already in the list 203 if (this.postSubmitActions.some(action => action.func === func)) { 204 console.warn('Submit action already exists, skipping', func); 205 return; 206 } 207 this.postSubmitActions.push(new ScrySearch_SubmitAction(func, order)); 208 } 209 210 //add actions to the pre submit ajax actions 211 addPreSubmitAjaxAction(func, order) { 212 //ensure the function of the submit action is not already in the list 213 if (this.preSubmitAjaxActions.some(action => action.func === func)) { 214 console.warn('Submit action already exists, skipping', func); 215 return; 216 } 217 this.preSubmitAjaxActions.push(new ScrySearch_SubmitAction(func, order)); 218 } 219 220 //add actions to the post submit ajax actions 221 addPostSubmitAjaxAction(func, order) { 222 //ensure the function of the submit action is not already in the list 223 if (this.postSubmitAjaxActions.some(action => action.func === func)) { 224 console.warn('Submit action already exists, skipping', func); 225 return; 226 } 227 this.postSubmitAjaxActions.push(new ScrySearch_SubmitAction(func, order)); 228 } 229} 230 231//a function that is supplied externally that can be used to perform actions before or after a search 232class ScrySearch_SubmitAction { 233 constructor(func, order) { 234 this.func = func; 235 this.order = order; 236 } 237 238 //call the function 239 call(searchForm, data) { 240 // Actions may be written as (searchForm) or (searchForm, data). 241 // We key off argument count so "falsy" data (0, "", null) isn't accidentally dropped. 242 if (arguments.length >= 2) { 243 this.func(searchForm, data); 244 return; 245 } 246 this.func(searchForm); 247 } 248} 249 250//an object that represents an external upgrade to the scry search window system 251class ScrySearch_Upgrade { 252 constructor(name, version) { 253 this.name = name; 254 this.version = version; 255 this.data = {}; //arbitrary data storage for the upgrade 256 } 257} 258 259//initialize the scry search window object 260document.addEventListener('DOMContentLoaded', function () { 261 window.scrySearch.init(); 262});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.