From 59fe08958ca4e1e26db41aad51f770fd2bc7e265 Mon Sep 17 00:00:00 2001 From: spiralnebulam31 Date: Fri, 20 Mar 2026 16:41:34 +0000 Subject: [PATCH 1/7] Fecth cities data --- 06 - Type Ahead/index-START.html | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/06 - Type Ahead/index-START.html b/06 - Type Ahead/index-START.html index 5a9aa7e4e8..9250e10854 100644 --- a/06 - Type Ahead/index-START.html +++ b/06 - Type Ahead/index-START.html @@ -16,8 +16,25 @@ From d9035ad61f8a7aafc01721bb8a083adeadfb0f1f Mon Sep 17 00:00:00 2001 From: spiralnebulam31 Date: Fri, 20 Mar 2026 16:54:34 +0000 Subject: [PATCH 2/7] Created function to find word matches --- 06 - Type Ahead/index-START.html | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/06 - Type Ahead/index-START.html b/06 - Type Ahead/index-START.html index 9250e10854..ac7b1c8ede 100644 --- a/06 - Type Ahead/index-START.html +++ b/06 - Type Ahead/index-START.html @@ -34,7 +34,30 @@ .then( response => response.json() ) .then( data => cities.push( ...data ) ); +/** + * The findMatches function takes a word to match and an array of cities as arguments. It returns an array of cities that match the word to match. + * The function uses the filter() method to create a new array of cities that match the word to match. The filter() method takes a callback function that is called for each city in the cities + * array. The callback function creates a regular expression using the word to match and checks if the city name or state name matches the regular expression. If either the city name or state name matches, the city is included in the new array of matching cities. + * + * @param {string} wordToMatch - The word that we want to match against the city and state names. + * @param {Array} cities - The array of city objects that we want to search through. + * @returns {Array} An array of city objects that match the word to match. + */ +function findMatches( wordToMatch, cities ) { + // We use the filter() method to create a new array of cities that match the wordToMatch. + return cities.filter( place => { + // Create a regular expression using the wordToMatch. + // The 'g' flag is for global search, and the 'i' flag is for case-insensitive search. + // We do this because we want to find all matches of the wordToMatch in the city and state names, regardless of case. + const regex = new RegExp( wordToMatch, 'gi' ); + + // We use the match() method to check if the city name or state name matches the regular expression. + // The || operator short-circuits: if place.city.match() is truthy, place.state.match() is never evaluated. + // This is more efficient than storing both results in variables first, which would force both .match() calls to always run. + return place.city.match( regex ) || place.state.match( regex ); + } ); +} From bb62f0b5cb318a190157b8ecb84aeede6e298ec2 Mon Sep 17 00:00:00 2001 From: spiralnebulam31 Date: Fri, 20 Mar 2026 17:23:37 +0000 Subject: [PATCH 3/7] Display matching cities --- 06 - Type Ahead/index-START.html | 61 ++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/06 - Type Ahead/index-START.html b/06 - Type Ahead/index-START.html index ac7b1c8ede..198b2f58e4 100644 --- a/06 - Type Ahead/index-START.html +++ b/06 - Type Ahead/index-START.html @@ -58,6 +58,67 @@ } ); } + +/** + * The numberWithCommas function takes a number as an argument and returns a string with the number formatted with commas as thousands separators. + * For example, if the input is 1234567, the output will be "1,234,567". + * This function is used to format the population numbers in the suggestions list. + * + * @param {number} x - The number that we want to format with commas. + * @returns {string} A string representation of the number with commas as thousands separators. + */ +function numberWithCommas(x) { + x = x.toString(); + var pattern = /(-?\d+)(\d{3})/; + while (pattern.test(x)) + x = x.replace(pattern, "$1,$2"); + return x; +} + +/** + * When the user types in the search input, we want to display the matching cities and states in the suggestions list. + * The displayMatches function calls the findMatches function to get an array of matching cities. + * + */ +function displayMatches() { + // We call the findMatches function, passing in the current value of the search input (this.value) and the cities array. + // The findMatches function returns an array of matching cities, which we store in the matchArray variable. + const matchArray = findMatches( this.value, cities ); + + // Display the matching cities and states in the suggestions list. + // We use the map() method to create an array of HTML strings for each matching city. + const html = matchArray.map( place => { + // We create a regular expression using the current value of the search input, just like we did in the findMatches function. + // This is because we want to highlight the matching text in the city and state names. + const regex = new RegExp( this.value, 'gi' ); + + // We use the replace() method to replace the matching text in the city and state names with a span element that has the class 'hl' (for highlight). + const cityName = place.city.replace( regex, `${ this.value }` ); + const stateName = place.state.replace( regex, `${ this.value }` ); + + return ` +
  • + ${ cityName }, ${ stateName } + ${ numberWithCommas(place.population) } +
  • + `; + }).join( '' ); // We use the join() method to join the array of HTML strings into a single string, which we can then set as the innerHTML of the suggestions list. + + // We set the innerHTML of the suggestions list to the HTML string we created. + suggestions.innerHTML = html; +} + +// We select the search input and suggestions list from the DOM. +const searchInput = document.querySelector( '.search' ); +const suggestions = document.querySelector( '.suggestions' ); + +// We add an event listener to the search input that listens for the 'change' event. +// When the 'change' event is fired, the displayMatches function is called. +searchInput.addEventListener( 'change', displayMatches ); + +// The 'change' event only fires when we click outside the input field after changing its value. +// To make the search more responsive, we also listen for the 'keyup' event, which fires every time a key is released while the input field is focused. +searchInput.addEventListener( 'keyup', displayMatches ); From d0633a4d3a2d830f786c5c1a12c3a10f8d8b08eb Mon Sep 17 00:00:00 2001 From: spiralnebulam31 Date: Fri, 20 Mar 2026 22:48:25 +0000 Subject: [PATCH 4/7] Code improvements --- 06 - Type Ahead/index-START.html | 69 ++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/06 - Type Ahead/index-START.html b/06 - Type Ahead/index-START.html index 198b2f58e4..1b3f32576b 100644 --- a/06 - Type Ahead/index-START.html +++ b/06 - Type Ahead/index-START.html @@ -36,20 +36,22 @@ /** * The findMatches function takes a word to match and an array of cities as arguments. It returns an array of cities that match the word to match. - * The function uses the filter() method to create a new array of cities that match the word to match. The filter() method takes a callback function that is called for each city in the cities + * The function uses the filter() method to create a new array of cities that match the word to match. The filter() method takes a callback function that is called for each city in the cityList * array. The callback function creates a regular expression using the word to match and checks if the city name or state name matches the regular expression. If either the city name or state name matches, the city is included in the new array of matching cities. * * @param {string} wordToMatch - The word that we want to match against the city and state names. - * @param {Array} cities - The array of city objects that we want to search through. + * @param {Array} cityList - The array of city objects that we want to search through. * @returns {Array} An array of city objects that match the word to match. */ -function findMatches( wordToMatch, cities ) { +function findMatches( wordToMatch, cityList ) { // We use the filter() method to create a new array of cities that match the wordToMatch. - return cities.filter( place => { + return cityList.filter( place => { // Create a regular expression using the wordToMatch. // The 'g' flag is for global search, and the 'i' flag is for case-insensitive search. // We do this because we want to find all matches of the wordToMatch in the city and state names, regardless of case. - const regex = new RegExp( wordToMatch, 'gi' ); + // We first escape any special characters in the wordToMatch using a regular expression, so that they are treated as literal characters in the regular expression. + const escaped = wordToMatch.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); + const regex = new RegExp( escaped, 'gi' ); // We use the match() method to check if the city name or state name matches the regular expression. // The || operator short-circuits: if place.city.match() is truthy, place.state.match() is never evaluated. @@ -69,32 +71,67 @@ */ function numberWithCommas(x) { x = x.toString(); - var pattern = /(-?\d+)(\d{3})/; - while (pattern.test(x)) + + const pattern = /(-?\d+)(\d{3})/; + + while (pattern.test(x)) { x = x.replace(pattern, "$1,$2"); + }; + return x; } +/** + * Escapes HTML special characters in a string to prevent XSS when inserting into innerHTML. + * Any character that the browser could interpret as HTML markup is replaced with its safe entity equivalent. + * + * @param {string} str - The string to escape. + * @returns {string} The escaped string, safe for insertion into HTML. + */ +function escapeHtml( str ) { + return str + .replace( /&/g, '&' ) + .replace( //g, '>' ) + .replace( /"/g, '"' ); +} + /** * When the user types in the search input, we want to display the matching cities and states in the suggestions list. * The displayMatches function calls the findMatches function to get an array of matching cities. + * It then uses the map() method to create an array of HTML strings for each matching city, which includes the city name, state name, and population. + * Finally, it sets the innerHTML of the suggestions list to the HTML string we created. + * We also escape any special characters in the user's input and the city/state names to prevent X * + * @returns {void} This function does not return anything. It updates the DOM directly by setting the innerHTML of the suggestions list. */ function displayMatches() { + // If the input is empty, restore the default placeholder suggestions and stop. + // Without this guard, new RegExp('', 'gi') matches between every character and wraps + // every gap in the city/state names with a highlight , producing broken output. + if ( !this.value.trim() ) { + suggestions.innerHTML = '
  • Filter for a city
  • or a state
  • '; + return; + } + // We call the findMatches function, passing in the current value of the search input (this.value) and the cities array. // The findMatches function returns an array of matching cities, which we store in the matchArray variable. const matchArray = findMatches( this.value, cities ); - + // Display the matching cities and states in the suggestions list. // We use the map() method to create an array of HTML strings for each matching city. const html = matchArray.map( place => { - // We create a regular expression using the current value of the search input, just like we did in the findMatches function. - // This is because we want to highlight the matching text in the city and state names. - const regex = new RegExp( this.value, 'gi' ); + // Escape special regex characters in the user's input before building the RegExp, + // and escape HTML special characters before inserting it into the to prevent XSS. + const escaped = this.value.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); + const regex = new RegExp( escaped, 'gi' ); + const safeInput = escapeHtml( this.value ); - // We use the replace() method to replace the matching text in the city and state names with a span element that has the class 'hl' (for highlight). - const cityName = place.city.replace( regex, `${ this.value }` ); - const stateName = place.state.replace( regex, `${ this.value }` ); + // Escape the city and state names from the remote data before inserting into HTML, + // then highlight the matching text by wrapping it in a . + // We escape first so the regex runs on safe text, not raw HTML-dangerous characters. + const cityName = escapeHtml( place.city ).replace( regex, `${ safeInput }` ); + const stateName = escapeHtml( place.state ).replace( regex, `${ safeInput }` ); return `
  • @@ -117,8 +154,8 @@ searchInput.addEventListener( 'change', displayMatches ); // The 'change' event only fires when we click outside the input field after changing its value. -// To make the search more responsive, we also listen for the 'keyup' event, which fires every time a key is released while the input field is focused. -searchInput.addEventListener( 'keyup', displayMatches ); +// To make the search more responsive and handle all kinds of input (typing, paste, autofill, etc.), +searchInput.addEventListener( 'input', displayMatches ); From 1d7589fa0dfe9a631af1fab6df84b6ad023263e9 Mon Sep 17 00:00:00 2001 From: spiralnebulam31 Date: Fri, 20 Mar 2026 22:50:46 +0000 Subject: [PATCH 5/7] Code formatting --- 06 - Type Ahead/index-START.html | 320 ++++++++++++++++--------------- 1 file changed, 164 insertions(+), 156 deletions(-) diff --git a/06 - Type Ahead/index-START.html b/06 - Type Ahead/index-START.html index 1b3f32576b..36d73a6c69 100644 --- a/06 - Type Ahead/index-START.html +++ b/06 - Type Ahead/index-START.html @@ -1,161 +1,169 @@ - + - - - Type Ahead 👀 - - - - - -
    - -
      -
    • Filter for a city
    • -
    • or a state
    • -
    -
    - - + }) + .join( '' ); // We use the join() method to join the array of HTML strings into a single string, which we can then set as the innerHTML of the suggestions list. + + // We set the innerHTML of the suggestions list to the HTML string we created. + suggestions.innerHTML = html; + } + + // We select the search input and suggestions list from the DOM. + const searchInput = document.querySelector( '.search' ); + const suggestions = document.querySelector( '.suggestions' ); + + // We add an event listener to the search input that listens for the 'change' event. + // When the 'change' event is fired, the displayMatches function is called. + searchInput.addEventListener( 'change', displayMatches ); + + // The 'change' event only fires when we click outside the input field after changing its value. + // To make the search more responsive and handle all kinds of input (typing, paste, autofill, etc.), + searchInput.addEventListener( 'input', displayMatches ); + + From ae611b29e729fd8c8a19b2dc232871bf808abea2 Mon Sep 17 00:00:00 2001 From: spiralnebulam31 Date: Fri, 20 Mar 2026 22:51:10 +0000 Subject: [PATCH 6/7] More code formatting --- 06 - Type Ahead/index-START.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/06 - Type Ahead/index-START.html b/06 - Type Ahead/index-START.html index 36d73a6c69..fc9987244f 100644 --- a/06 - Type Ahead/index-START.html +++ b/06 - Type Ahead/index-START.html @@ -30,9 +30,9 @@ * Finally, we use the spread operator to push all the city objects from the parsed data into the cities array. * Note: We use the spread operator instead of just pushing the data array because we want to push each city object individually into the cities array, rather than pushing the entire data array as a single element. */ - fetch(endpoint) - .then((response) => response.json()) - .then((data) => cities.push(...data)); + fetch( endpoint ) + .then( ( response ) => response.json() ) + .then( ( data ) => cities.push( ...data ) ); /** * The findMatches function takes a word to match and an array of cities as arguments. It returns an array of cities that match the word to match. From db1529e336a3a42088fff9dada838bb0705cbc99 Mon Sep 17 00:00:00 2001 From: spiralnebulam31 Date: Fri, 20 Mar 2026 22:54:36 +0000 Subject: [PATCH 7/7] Replaced loose boolean check --- 06 - Type Ahead/index-START.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/06 - Type Ahead/index-START.html b/06 - Type Ahead/index-START.html index fc9987244f..021f8284d8 100644 --- a/06 - Type Ahead/index-START.html +++ b/06 - Type Ahead/index-START.html @@ -108,7 +108,7 @@ // If the input is empty, restore the default placeholder suggestions and stop. // Without this guard, new RegExp('', 'gi') matches between every character and wraps // every gap in the city/state names with a highlight , producing broken output. - if ( !this.value.trim() ) { + if ( '' === this.value.trim() ) { suggestions.innerHTML = '
  • Filter for a city
  • or a state
  • '; return;