In this task we will implement the search functionality for our Dictionary app. The search functionality will allow users to enter a word and retrieve its definition(s) and pronunciation(s) from the Free Dictionary API.
Open the main.js
file and add the following code to fetch the API data:
const dictionaryAPI = "https://api.dictionaryapi.dev/api/v2/entries/en_US/";
const searchWord = async (word) => {
try {
const response = await fetch(`${dictionaryAPI}${word}`);
const data = await response.json();
return data;
} catch (error) {
throw new Error("Failed to fetch data from the API");
}
};
searchWord("hello")
.then((data) => {
console.log(data);
}).catch((error) => {
console.error("Error: ", error);
});
Run the app using pnpm dev
and open the browser console to see the API data logged.
The API response contains the word’s definition, phonetics, synonyms, antonyms, and examples of usage.
There are three parts to this code:
dictionaryAPI
constant with the base URL of the Free Dictionary API.async
function searchWord
that takes a word
parameter. This function fetches the API data for the given word and returns the data as a JSON object.searchWord
function with the word “hello” and log the data to the console.We have not seen the async
and await
keywords before. These are part of the modern JavaScript syntax for handling asynchronous operations. The fetch
function returns a Promise
that resolves to the Response
object representing the response to the request. We use await
to pause the execution of the function until the Promise
is resolved. This allows us to write asynchronous code that looks synchronous.
The use of the then
and catch
methods is another way to handle Promise
objects. The then
method is called when the Promise
is resolved, and the catch
method is called when the Promise
is rejected (i.e., an error occurs).
We will explore these concepts further in the lecture notes associated with this tutorial lesson on Asynchronous Programming in JavaScript. For now, focus on understanding the flow of the code and how we are fetching data from the API.
The try...catch
block is a familiar pattern used in error handling in languages like Java and C++. The try
block holds the code that could potentially cause an error, while the catch
block catches the error and handles it gracefully. In our scenario, we throw a new Error
object with a message if the API request fails. This is a typical practice to provide meaningful error messages to the user or developer. I’ve used it in this instance to illustrate how to raise an error.
Now, let’s modify the code to run the search when the user enters a word in the input field and clicks the search button. Add this code to the main.js
file:
const inputWord = document.getElementById("input");
const submitBtn = document.getElementById("submit");
submitBtn.addEventListener("click", (event) => {
const word = inputWord.value.trim();
if (!word) return;
searchWord(word)
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error("Error: ", error);
});
});
In the code above, we select the input field and the submit button using document.getElementById
. We then add an event listener to the submit button that triggers the search when clicked. The search function is called with the word entered by the user. The API data is logged to the console for now.
Run the app using pnpm dev
and test the search functionality by entering a word in the input field and clicking the search button.
Next, we will extract the word’s definition from the the API data and display it in the console. Add the following code to the main.js
file:
const extractWordDefinitions = (data) => {
if (data && Array.isArray(data)) {
if (data[0].meanings && Array.isArray(data[0].meanings)) {
data[0].meanings.forEach((meaning) => {
const { partOfSpeech, definitions } = meaning;
console.log({ partOfSpeech, definitions });
});
}
}
};
The extractWordDefinitions
function takes the API data as input and extracts the word’s part of speech and definitions. We check if the data is an array and if it contains the necessary properties before extracting the information. This is called defensive programming, where we check for the existence of properties before accessing them to avoid errors. We then loop through the meanings and log the part of speech and definitions to the console.
Modify the then
block in the event listener to call the extractWordDefinitions
function:
submitBtn.addEventListener("click", (event) => {
const word = inputWord.value.trim();
if (!word) return;
searchWord(word)
.then((data) => {
- console.log(data);
+ extractWordDefinitions(data);
})
.catch((error) => {
console.error("Error: ", error);
});
});
Run the app using pnpm dev
and test the search functionality. Try searching for “asynchronous” and check the console for the extracted word definition. You should see “adjective” as the part of speech and three definitions for the word “asynchronous” when it is used as an adjective.
Now that we have extracted the word’s definitions, let’s display them in the browser. We will create a section
element to hold the definitions and append it to the main
section. Add the following code to the main.js
file:
const displayWordDefinition = (meanings) => {
const definitionsSection = document.getElementById("definitions");
definitionsSection.innerHTML = "";
const definitionsHeading = document.createElement("h1");
definitionsHeading.classList.add("text-2xl", "font-semibold");
definitionsHeading.innerText = "Definitions";
definitionsSection.appendChild(definitionsHeading);
meanings.forEach((meaning) => {
const definitionDiv = document.createElement("div");
definitionDiv.classList.add("bg-sky-50");
definitionsSection.appendChild(definitionDiv);
const { partOfSpeech, definitions } = meaning;
console.log({ partOfSpeech, definitions });
const partOfSpeechName = document.createElement("p");
partOfSpeechName.classList.add(
"px-4",
"py-2",
"font-semibold",
"text-white",
"bg-sky-600",
);
partOfSpeechName.innerText = partOfSpeech;
definitionDiv.appendChild(partOfSpeechName);
const definitionsList = document.createElement("ul");
definitionsList.classList.add(
"p-2",
"ml-6",
"font-light",
"list-disc",
"text-sky-700",
);
definitionDiv.appendChild(definitionsList);
definitions.forEach((definitionObj) => {
const definitionsItem = document.createElement("li");
definitionsItem.innerText = definitionObj.definition;
definitionsList.appendChild(definitionsItem);
});
});
};
In the above code, we define the displayWordDefinition
function that takes an array of meanings as input. We first clear the existing content of the definitions
section. We then create a header element for the definitions section and append it to the section. For each meaning, we create a div
element to hold the part of speech and definitions. We create a p
element for the part of speech and a ul
element for the definitions. We loop through the definitions and create a li
element for each definition, appending it to the ul
element.
Next, update the extractWordDefinitions
function to return the meanings array:
const extractWordDefinitions = (data) => {
if (data && Array.isArray(data)) {
if (data[0].meanings && Array.isArray(data[0].meanings)) {
- data[0].meanings.forEach((meaning) => {
- const { partOfSpeech, definitions } = meaning;
- console.log({ partOfSpeech, definitions });
- });
+ return data[0].meanings;
}
}
};
Finally, modify the then
block in the event listener to call the displayWordDefinition
function:
submitBtn.addEventListener("click", (event) => {
const word = inputWord.value.trim();
if (!word) return;
searchWord(word)
.then((data) => {
- extractWordDefinitions(data);
+ const meanings = extractWordDefinitions(data);
+ displayWordDefinition(meanings);
})
.catch((error) => {
console.error("Error: ", error);
});
});
Run the app using pnpm dev
and test the search functionality. Enter a word in the input field and click the search button. You should see the word’s definitions displayed in the browser.
Let’s refactor the displayWordDefinition
function to make it more modular and readable. Replace the existing displayWordDefinition
function with the following code:
const extractWordDefinitions = (data) => {
if (data && Array.isArray(data)) {
if (data[0].meanings && Array.isArray(data[0].meanings)) {
return data[0].meanings;
}
}
};
const clearDefinitionsSection = () => {
const definitionsSection = document.getElementById("definitions");
definitionsSection.innerHTML = "";
return definitionsSection;
}
const createDefinitionsHeading = () => {
const definitionsHeading = document.createElement("h1");
definitionsHeading.classList.add("text-2xl", "font-semibold");
definitionsHeading.innerText = "Definitions";
return definitionsHeading;
};
const createDefinitionDiv = () => {
const definitionDiv = document.createElement("div");
definitionDiv.classList.add("bg-sky-50");
return definitionDiv;
}
const createPartOfSpeechElement = (partOfSpeech) => {
const partOfSpeechName = document.createElement("p");
partOfSpeechName.classList.add(
"px-4",
"py-2",
"font-semibold",
"text-white",
"bg-sky-600"
);
partOfSpeechName.innerText = partOfSpeech;
return partOfSpeechName;
}
const createDefinitionsList = () => {
const definitionsList = document.createElement("ul");
definitionsList.classList.add(
"p-2",
"ml-6",
"font-light",
"list-disc",
"text-sky-700"
);
return definitionsList;
}
const createDefinitionItem = (definitionObj) => {
const definitionsItem = document.createElement("li");
definitionsItem.innerText = definitionObj.definition;
return definitionsItem;
}
const displayWordDefinition = (meanings) => {
const definitionsSection = clearDefinitionsSection();
const definitionsHeading = createDefinitionsHeading();
definitionsSection.appendChild(definitionsHeading);
meanings.forEach((meaning) => {
const definitionDiv = createDefinitionDiv();
definitionsSection.appendChild(definitionDiv);
const { partOfSpeech, definitions } = meaning;
const partOfSpeechName = createPartOfSpeechElement(partOfSpeech);
definitionDiv.appendChild(partOfSpeechName);
const definitionsList = createDefinitionsList();
definitionDiv.appendChild(definitionsList);
const definitionListItems = definitions.map(createDefinitionItem);
definitionsList.append(...definitionListItems);
});
};
In the refactored code, we have broken down the displayWordDefinition
function into smaller, more focused functions. Each function is responsible for creating a specific part of the UI element. This makes the code more modular, easier to read, and maintainable.
Next, let’s extract the word’s phonetics from the API data and log them to the console. Add the following code to the main.js
file:
const extractWordPhonetics = (data) => {
if (data && Array.isArray(data)) {
if (data[0].phonetics && Array.isArray(data[0].phonetics)) {
data[0].phonetics.forEach((phonetic) => {
const { text, audio } = phonetic;
console.log({ text, audio });
});
}
}
};
The extractWordPhonetics
function takes the API data as input and extracts the word’s phonetics. We check if the data is an array and if it contains the necessary properties before extracting the information. We then loop through the phonetics and log the text and audio to the console.
Modify the then
block in the event listener to call the extractWordPhonetics
function:
submitBtn.addEventListener("click", (event) => {
const word = inputWord.value.trim();
if (!word) return;
searchWord(word)
.then((data) => {
console.log(data);
const meanings = extractWordDefinitions(data);
displayWordDefinition(meanings);
+ extractWordPhonetics(data);
})
.catch((error) => {
console.error("Error: ", error);
});
});
Run the app using pnpm dev
and test the search functionality. Enter a word in the input field and click the search button. You should see the word’s phonetics displayed in the console.
Now that we have extracted the word’s phonetics, let’s display them in the browser. Add the following code to the main.js
file:
const displayWordPhonetic = (phonetics) => {
const phoneticsSection = document.getElementById("phonetics");
phoneticsSection.innerHTML = "";
phoneticsSection.classList.add("flex", "flex-col", "gap-4");
const phoneticsHeading = document.createElement("h1");
phoneticsHeading.classList.add("text-2xl", "font-semibold");
phoneticsHeading.innerText = "Phonetics";
phoneticsSection.appendChild(phoneticsHeading);
phonetics.forEach((phonetic) => {
const { text, audio } = phonetic;
if (!text || !audio) return;
const phoneticsDiv = document.createElement("div");
phoneticsDiv.classList.add("bg-stone-100");
phoneticsSection.appendChild(phoneticsDiv);
const phoneticText = document.createElement("p");
phoneticText.classList.add("px-4", "py-3", "text-white", "bg-stone-700");
phoneticText.innerText = text;
phoneticsDiv.appendChild(phoneticText);
const audioControl = document.createElement("audio");
audioControl.style = "width: 100%";
audioControl.setAttribute("controls", "true");
phoneticsDiv.appendChild(audioControl);
const source = document.createElement("source");
source.setAttribute("src", audio);
source.setAttribute("type", "audio/mpeg");
audioControl.appendChild(source);
const fallBackText = document.createTextNode(
"Your browser does not support the audio element.",
);
audioControl.appendChild(fallBackText);
});
};
In the above code, we define the displayWordPhonetic
function that takes an array of phonetics as input. We first clear the existing content of the phonetics
section. We then create a header element for the phonetics section and append it to the section. For each phonetic, we create a div
element to hold the text and audio. We create a p
element for the phonetic text and an audio
element for the audio. We set the source of the audio element to the audio URL.
Next, update the extractWordPhonetics
function to return the phonetics array:
const extractWordPhonetics = (data) => {
if (data && Array.isArray(data)) {
if (data[0].phonetics && Array.isArray(data[0].phonetics)) {
- data[0].phonetics.forEach((phonetic) => {
- const { text, audio } = phonetic;
- console.log({ text, audio });
- });
+ return data[0].phonetics;
}
}
};
Finally, modify the then
block in the event listener to call the displayWordPhonetic
function:
submitBtn.addEventListener("click", (event) => {
const word = inputWord.value.trim();
if (!word) return;
searchWord(word)
.then((data) => {
console.log(data);
const meanings = extractWordDefinitions(data);
displayWordDefinition(meanings);
- extractWordPhonetics(data);
+ const phonetics = extractWordPhonetics(data);
+ displayWordPhonetic(phonetics);
})
.catch((error) => {
console.error("Error: ", error);
});
});
Run the app using pnpm dev
and test the search functionality. Enter a word in the input field and click the search button. You should see the word’s phonetics displayed in the browser.
Let’s refactor the displayWordPhonetic
function to make it more modular and readable. Replace the existing displayWordPhonetic
function with the following code:
const createPhoneticsSection = () => {
const phoneticsSection = document.getElementById("phonetics");
phoneticsSection.innerHTML = "";
phoneticsSection.classList.add("flex", "flex-col", "gap-4");
return phoneticsSection;
};
const createPhoneticsHeading = () => {
const phoneticsHeading = document.createElement("h1");
phoneticsHeading.classList.add("text-2xl", "font-semibold");
phoneticsHeading.innerText = "Phonetics";
return phoneticsHeading;
};
const createPhoneticsDiv = () => {
const phoneticsDiv = document.createElement("div");
phoneticsDiv.classList.add("bg-stone-100");
return phoneticsDiv;
};
const createPhoneticElement = (text) => {
const phoneticText = document.createElement("p");
phoneticText.classList.add("px-4", "py-3", "text-white", "bg-stone-700");
phoneticText.innerText = text;
return phoneticText;
};
const createAudioControl = () => {
const audioControl = document.createElement("audio");
audioControl.style = "width: 100%";
audioControl.setAttribute("controls", "true");
return audioControl;
};
const createAudioSource = (audio) => {
const source = document.createElement("source");
source.setAttribute("src", audio);
source.setAttribute("type", "audio/mpeg");
return source;
};
const fallbackText = document.createTextNode(
"Your browser does not support the audio element.",
);
const displayWordPhonetic = (phonetics) => {
const phoneticsSection = createPhoneticsSection();
const phoneticsHeading = createPhoneticsHeading();
phoneticsSection.appendChild(phoneticsHeading);
phonetics.forEach((phonetic) => {
const { text, audio } = phonetic;
if (!text || !audio) return;
const phoneticsDiv = createPhoneticsDiv();
phoneticsSection.appendChild(phoneticsDiv);
const phoneticText = createPhoneticElement(text);
phoneticsDiv.appendChild(phoneticText);
const audioControl = createAudioControl();
phoneticsDiv.appendChild(audioControl);
const source = createAudioSource(audio);
audioControl.appendChild(source);
const fallBackText = fallbackText;
audioControl.appendChild(fallBackText);
});
};
In the refactored code, we have broken down the displayWordPhonetic
function into smaller, more focused functions. Each function is responsible for creating a specific part of the UI element. This makes the code more modular, easier to read, and maintainable.
Here is the final version of the main.js
file with the search functionality, word definitions, and phonetics implemented:
import "../style.css";
const dictionaryAPI = "https://api.dictionaryapi.dev/api/v2/entries/en_US/";
// Fetch data from the API
const searchWord = async (word) => {
try {
const response = await fetch(`${dictionaryAPI}${word}`);
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching data: ", error);
}
};
// Extract word definitions from the data
const extractWordDefinitions = (data) => {
if (data && Array.isArray(data)) {
if (data[0].meanings && Array.isArray(data[0].meanings)) {
return data[0].meanings;
}
}
};
// Extract word phonetics from the data
const extractWordPhonetics = (data) => {
if (data && Array.isArray(data)) {
if (data[0].phonetics && Array.isArray(data[0].phonetics)) {
return data[0].phonetics;
}
}
};
// Helper to clear the definitions section
const clearDefinitionsSection = () => {
const definitionsSection = document.getElementById("definitions");
definitionsSection.innerHTML = "";
return definitionsSection;
};
// Helper to create the definitions heading
const createDefinitionsHeading = () => {
const definitionsHeading = document.createElement("h1");
definitionsHeading.classList.add("text-2xl", "font-semibold");
definitionsHeading.innerText = "Definitions";
return definitionsHeading;
};
// Helper to create the definition div
const createDefinitionDiv = () => {
const definitionDiv = document.createElement("div");
definitionDiv.classList.add("bg-sky-50");
return definitionDiv;
};
// Helper to create the part of speech element
const createPartOfSpeechElement = (partOfSpeech) => {
const partOfSpeechName = document.createElement("p");
partOfSpeechName.classList.add(
"px-4",
"py-2",
"font-semibold",
"text-white",
"bg-sky-600",
);
partOfSpeechName.innerText = partOfSpeech;
return partOfSpeechName;
};
// Helper to create the definitions list
const createDefinitionsList = () => {
const definitionsList = document.createElement("ul");
definitionsList.classList.add(
"p-2",
"ml-6",
"font-light",
"list-disc",
"text-sky-700",
);
return definitionsList;
};
// Helper to create the definition item
const createDefinitionItem = (definitionObj) => {
const definitionsItem = document.createElement("li");
definitionsItem.innerText = definitionObj.definition;
return definitionsItem;
};
// Display the word definitions
const displayWordDefinition = (meanings) => {
const definitionsSection = clearDefinitionsSection();
const definitionsHeading = createDefinitionsHeading();
definitionsSection.appendChild(definitionsHeading);
meanings.forEach((meaning) => {
const definitionDiv = createDefinitionDiv();
definitionsSection.appendChild(definitionDiv);
const { partOfSpeech, definitions } = meaning;
const partOfSpeechName = createPartOfSpeechElement(partOfSpeech);
definitionDiv.appendChild(partOfSpeechName);
const definitionsList = createDefinitionsList();
definitionDiv.appendChild(definitionsList);
const definitionListItems = definitions.map(createDefinitionItem);
definitionsList.append(...definitionListItems);
});
};
// Helper to clear the phonetics section
const createPhoneticsSection = () => {
const phoneticsSection = document.getElementById("phonetics");
phoneticsSection.innerHTML = "";
phoneticsSection.classList.add("flex", "flex-col", "gap-4");
return phoneticsSection;
};
// Helper to create the phonetics heading
const createPhoneticsHeading = () => {
const phoneticsHeading = document.createElement("h1");
phoneticsHeading.classList.add("text-2xl", "font-semibold");
phoneticsHeading.innerText = "Phonetics";
return phoneticsHeading;
};
// Helper to create the phonetics div
const createPhoneticsDiv = () => {
const phoneticsDiv = document.createElement("div");
phoneticsDiv.classList.add("bg-stone-100");
return phoneticsDiv;
};
// Helper to create the phonetic element
const createPhoneticElement = (text) => {
const phoneticText = document.createElement("p");
phoneticText.classList.add("px-4", "py-3", "text-white", "bg-stone-700");
phoneticText.innerText = text;
return phoneticText;
};
// Helper to create the audio control
const createAudioControl = () => {
const audioControl = document.createElement("audio");
audioControl.style = "width: 100%";
audioControl.setAttribute("controls", "true");
return audioControl;
};
// Helper to create the audio source
const createAudioSource = (audio) => {
const source = document.createElement("source");
source.setAttribute("src", audio);
source.setAttribute("type", "audio/mpeg");
return source;
};
// Helper to create the fallback text
const fallbackText = document.createTextNode(
"Your browser does not support the audio element.",
);
// Display the word phonetics
const displayWordPhonetic = (phonetics) => {
const phoneticsSection = createPhoneticsSection();
const phoneticsHeading = createPhoneticsHeading();
phoneticsSection.appendChild(phoneticsHeading);
phonetics.forEach((phonetic) => {
const { text, audio } = phonetic;
if (!text || !audio) return;
const phoneticsDiv = createPhoneticsDiv();
phoneticsSection.appendChild(phoneticsDiv);
const phoneticText = createPhoneticElement(text);
phoneticsDiv.appendChild(phoneticText);
const audioControl = createAudioControl();
phoneticsDiv.appendChild(audioControl);
const source = createAudioSource(audio);
audioControl.appendChild(source);
const fallBackText = fallbackText;
audioControl.appendChild(fallBackText);
});
};
// Get the input word and search for its definition
const inputWord = document.getElementById("input");
const submitBtn = document.getElementById("submit");
submitBtn.addEventListener("click", (event) => {
const word = inputWord.value.trim();
if (!word) return;
searchWord(word)
.then((data) => {
console.log(data);
const meanings = extractWordDefinitions(data);
displayWordDefinition(meanings);
const phonetics = extractWordPhonetics(data);
displayWordPhonetic(phonetics);
})
.catch((error) => {
console.error("Error: ", error);
});
});
Now that we have implemented the search functionality, extracted and displayed the word’s definitions and phonetics, and refactored the code for readability, run the app using pnpm dev
and test the search functionality. Enter a word in the input field and click the search button. You should see the word’s definitions and phonetics displayed in the browser.