📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
🎓 Top 15 Udemy Courses (80-90% Discount): My Udemy Courses - Ramesh Fadatare — All my Udemy courses are real-time and project oriented courses.
▶️ Subscribe to My YouTube Channel (176K+ subscribers): Java Guides on YouTube
▶️ For AI, ChatGPT, Web, Tech, and Generative AI, subscribe to another channel: Ramesh Fadatare on YouTube
Creating a Meal Finder App is a fantastic way to learn and apply JavaScript, HTML, and CSS, especially when working with external APIs. In this guide, we will create an application that allows users to search for meals using keywords and fetch random meals from the "TheMealDB" API. This project not only enhances your coding skills but also introduces you to working with APIs, DOM manipulation, and event handling in JavaScript. Let's dive into the specifications and the implementation steps.
Objective: Allow users to search for meals using keywords or fetch random meals from TheMealDB API and display the results in a user-friendly interface.
Project Specifications
Display UI with Form to Search and Button to Generate: Create a user interface with an input field for meal searches and a button to fetch random meal suggestions.
Connect to API and Get Meals: Utilize TheMealDB API to search for meals based on user input or to fetch a random meal.
Display Meals in DOM with Image and Hover Effect: Show meal results dynamically in the DOM, including meal images. Implement a hover effect for better user interaction.
Click on Meal and See the Details: Allow users to click on a meal to view detailed information about it, such as ingredients and preparation steps.
Click on the Generate Button and Fetch & Display a Random Meal: Implement functionality to let users fetch a random meal by clicking on the designated button.
Implementation
- index.html - for the app structure.
- style.css - for styling the app.
- script.js - for the app's functionality.
1. index.html - HTML Structure
The HTML markup sets the foundation of the application, featuring a container for the search input and buttons and div elements to display search results and meal details. It's designed to be intuitive and user-friendly.
Open the index.html file and add the following HTML code to it:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.2/css/all.min.css" /> <link rel="stylesheet" href="style.css" /> <title>Meal Finder</title> </head> <body> <div class="container"> <h1>Meal Finder</h1> <div class="flex"> <form class="flex" id="submit"> <input type="text" id="search" placeholder="Search for meals or keywords" /> <button class="search-btn" type="submit"> <i class="fas fa-search"></i> </button> </form> <button class="random-btn" id="random"> <i class="fas fa-random"></i> </button> </div> <div id="result-heading"></div> <div id="meals" class="meals"></div> <div id="single-meal"></div> </div> <script src="script.js"></script> </body> </html>
2. style.css - CSS Styling
* { box-sizing: border-box; } body { background: #2d2013; color: #fff; font-family: Verdana, Geneva, Tahoma, sans-serif; margin: 0; } .container { margin: auto; max-width: 800px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; } .flex { display: flex; } input, button { border: 1px solid #dedede; border-top-left-radius: 4px; border-bottom-left-radius: 4px; font-size: 14px; padding: 8px 10px; margin: 0; } input[type='text'] { width: 300px; } .search-btn { cursor: pointer; border-left: 0; border-radius: 0; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } .random-btn { cursor: pointer; margin-left: 10px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } .meals { display: grid; grid-template-columns: repeat(4, 1fr); grid-gap: 20px; margin-top: 20px; } .meal { cursor: pointer; position: relative; height: 180px; width: 180px; text-align: center; } .meal img { width: 100%; height: 100%; border: 4px #fff solid; border-radius: 2px; } .meal-info { position: absolute; top: 0; left: 0; height: 100%; width: 100%; background: rgba(0, 0, 0, 0.7); display: flex; align-items: center; justify-content: center; transition: opacity 0.2s ease-in; opacity: 0; } .meal:hover .meal-info { opacity: 1; } .single-meal { margin: 30px auto; width: 70%; } .single-meal img { width: 300px; margin: 15px; border: 4px #fff solid; border-radius: 2px; } .single-meal-info { margin: 20px; padding: 10px; border: 2px #e09850 dashed; border-radius: 5px; } .single-meal p { margin: 0; letter-spacing: 0.5px; line-height: 1.5; } .single-meal ul { padding-left: 0; list-style-type: none; } .single-meal ul li { border: 1px solid #ededed; border-radius: 5px; background-color: #fff; display: inline-block; color: #2d2013; font-size: 12px; font-weight: bold; padding: 5px; margin: 0 5px 5px 0; } @media (max-width: 800px) { .meals { grid-template-columns: repeat(3, 1fr); } } @media (max-width: 700px) { .meals { grid-template-columns: repeat(2, 1fr); } .meal { height: 200px; width: 200px; } } @media (max-width: 500px) { input[type='text'] { width: 100%; } .meals { grid-template-columns: 1fr; } .meal { height: 300px; width: 300px; } }
3. script.js - JavaScript Logic
const search = document.getElementById('search'), submit = document.getElementById('submit'), random = document.getElementById('random'), mealsEl = document.getElementById('meals'), resultHeading = document.getElementById('result-heading'), single_mealEl = document.getElementById('single-meal'); // Search meal and fetch from API function searchMeal(e) { e.preventDefault(); // Clear single meal single_mealEl.innerHTML = ''; // Get search term const term = search.value; // Check for empty if (term.trim()) { fetch(`https://www.themealdb.com/api/json/v1/1/search.php?s=${term}`) .then(res => res.json()) .then(data => { console.log(data); resultHeading.innerHTML = `<h2>Search results for '${term}':</h2>`; if (data.meals === null) { resultHeading.innerHTML = `<p>There are no search results. Try again!<p>`; } else { mealsEl.innerHTML = data.meals .map( meal => ` <div class="meal"> <img src="${meal.strMealThumb}" alt="${meal.strMeal}" /> <div class="meal-info" data-mealID="${meal.idMeal}"> <h3>${meal.strMeal}</h3> </div> </div> ` ) .join(''); } }); // Clear search text search.value = ''; } else { alert('Please enter a search term'); } } // Fetch meal by ID function getMealById(mealID) { fetch(`https://www.themealdb.com/api/json/v1/1/lookup.php?i=${mealID}`) .then(res => res.json()) .then(data => { const meal = data.meals[0]; addMealToDOM(meal); }); } // Fetch random meal from API function getRandomMeal() { // Clear meals and heading mealsEl.innerHTML = ''; resultHeading.innerHTML = ''; fetch(`https://www.themealdb.com/api/json/v1/1/random.php`) .then(res => res.json()) .then(data => { const meal = data.meals[0]; addMealToDOM(meal); }); } // Add meal to DOM function addMealToDOM(meal) { const ingredients = []; for (let i = 1; i <= 20; i++) { if (meal[`strIngredient${i}`]) { ingredients.push( `${meal[`strIngredient${i}`]} - ${meal[`strMeasure${i}`]}` ); } else { break; } } single_mealEl.innerHTML = ` <div class="single-meal"> <h1>${meal.strMeal}</h1> <img src="${meal.strMealThumb}" alt="${meal.strMeal}" /> <div class="single-meal-info"> ${meal.strCategory ? `<p>${meal.strCategory}</p>` : ''} ${meal.strArea ? `<p>${meal.strArea}</p>` : ''} </div> <div class="main"> <p>${meal.strInstructions}</p> <h2>Ingredients</h2> <ul> ${ingredients.map(ing => `<li>${ing}</li>`).join('')} </ul> </div> </div> `; } // Event listeners submit.addEventListener('submit', searchMeal); random.addEventListener('click', getRandomMeal); mealsEl.addEventListener('click', e => { const mealInfo = e.composedPath().find(item => { if (item.classList) { return item.classList.contains('meal-info'); } else { return false; } }); if (mealInfo) { const mealID = mealInfo.getAttribute('data-mealid'); getMealById(mealID); } });
Comments
Post a Comment
Leave Comment