Comment créer un générateur de repas aléatoire

La semaine dernière, j'ai décidé de relever un nouveau défi. Je l'ai appelé: le défi # 100Days100Projects.

Le but du défi est de créer un projet chaque jour. Pensez-y comme une prochaine étape pour le défi # 100DaysOfCode.

Un projet peut être soit:

  • Une application
  • Un composant
  • un site Web
  • un jeu
  • une bibliothèque

    etc...

Le langage de programmation utilisé n'est pas non plus important, mais je dois terminer le projet avant 23 h 59 (mon heure), sinon je me «punis» en donnant 5 $ pour 5 personnes (25 $ au total) - les 5 premières personnes qui faites remarquer sur Twitter que j'ai manqué la date limite. ?

Si vous souhaitez vous inscrire, vous pouvez en savoir plus sur ce défi et les autres variantes qu'il propose ici.

Remarque : vous n'avez pas à donner 5 $ si vous échouez, définissez simplement une autre «punition» pour vous-même. En outre, il existe d'autres variantes avec moins de jours ( 7Days7Projects et 30Days30Projects ) si vous n'avez pas envie de relever le défi des 100Days.

Pour le premier projet des # 100Days100Projects, j'ai pensé à travailler avec une API publique afin d'obtenir des données qui seraient affichées dans une page Web - une chose habituelle à faire avec une API.

Pour cela, j'ai choisi d'utiliser l'API publique de TheMealDB afin d'obtenir des repas aléatoires en appuyant sur un bouton. Quelque chose de simple! ?

Découvrez la version en direct de ce que nous allons créer dans cet article sur CodePen:

Comme toujours, commençons par le début:

Le HTML

Feeling hungry?

Get a random meal by clicking below
Get Meal ?

Nous avons un petit texte, mais les deux parties les plus importantes sont:

  • le #get_mealbouton et
  • le #mealdiv

Nous allons utiliser le buttonpour faire une requête à l'API. Cela renverra des données que nous allons mettre dans le #mealdiv qui agit comme un conteneur - dans ce cas.

Habituellement, après le HTML, je vais directement dans le CSS. Mais nous n'avons pas encore l'intégralité du balisage car il sera renseigné dans la section JavaScript , c'est donc ce que nous allons faire ensuite.

Le JavaScript

Comme mentionné ci-dessus, nous avons besoin du buttonet de ce conteneur div:

const get_meal_btn = document.getElementById('get_meal'); const meal_container = document.getElementById('meal'); 

Ensuite, avant de plonger plus dans le code, voyons ce que l'API va retourner. Pour cela, veuillez ouvrir l'URL suivante: //www.themealdb.com/api/json/v1/1/random.php.

Comme vous pouvez le voir sur l'URL, nous obtenons un repas aléatoire de cette API (actualisez pour voir le caractère aléatoire ). Lorsque nous faisons une requête GET à ce point de terminaison (comme y accéder à partir du navigateur), il renvoie une réponse JSON, que nous pouvons analyser afin de récupérer les données souhaitées.

Les données ressemblent à ceci:

{ meals: [ { idMeal: '52873', strMeal: 'Beef Dumpling Stew', strDrinkAlternate: null, strCategory: 'Beef', strArea: 'British', strInstructions: 'Long description', strMealThumb: '//www.themealdb.com/images/media/meals/uyqrrv1511553350.jpg', strTags: 'Stew,Baking', strYoutube: '//www.youtube.com/watch?v=6NgheY-r5t0', strIngredient1: 'Olive Oil', strIngredient2: 'Butter', strIngredient3: 'Beef', strIngredient4: 'Plain Flour', strIngredient5: 'Garlic', strIngredient6: 'Onions', strIngredient7: 'Celery', strIngredient8: 'Carrots', strIngredient9: 'Leek', strIngredient10: 'Swede', strIngredient11: 'Red Wine', strIngredient12: 'Beef Stock', strIngredient13: 'Bay Leaf', strIngredient14: 'Thyme', strIngredient15: 'Parsley', strIngredient16: 'Plain Flour', strIngredient17: 'Baking Powder', strIngredient18: 'Suet', strIngredient19: 'Water', strIngredient20: '', strMeasure1: '2 tbs', strMeasure2: '25g', strMeasure3: '750g', strMeasure4: '2 tblsp ', strMeasure5: '2 cloves minced', strMeasure6: '175g', strMeasure7: '150g', strMeasure8: '150g', strMeasure9: '2 chopped', strMeasure10: '200g', strMeasure11: '150ml', strMeasure12: '500g', strMeasure13: '2', strMeasure14: '3 tbs', strMeasure15: '3 tblsp chopped', strMeasure16: '125g', strMeasure17: '1 tsp ', strMeasure18: '60g', strMeasure19: 'Splash', strMeasure20: '', strSource: '//www.bbc.co.uk/food/recipes/beefstewwithdumpling_87333', dateModified: null } ]; } 

Fondamentalement, nous récupérons un tableau de meals, mais avec un seul élément - celui généré aléatoirement. Et cet article contient toutes les données que nous voulons mettre en valeur dans notre petite application. Des choses comme:

  • nom du repas (sous strMeal)
  • restauration (sous strCategory)
  • image du repas (sous strMealThumb)
  • une vidéo youtube avec la recette (ci-dessous strYoutube)
  • les ingrédients et les mesures (sous strIngredientsXet strMeasureX- X représentant le nième ingrédient et sa mesure) .C'est un peu gênant car je m'attendrais à avoir ici un tableau avec ces informations, mais ils choisissent de l'ajouter comme accessoires d'objet. Sur le bien... ? La chose importante à noter est qu'il y a un maximum de 20 ingrédients / mesures, bien qu'ils ne soient pas tous remplis - certains d'entre eux peuvent être vides, nous devons donc en tenir compte.

Maintenant que nous avons le bouton, nous allons ajouter un écouteur d'événement pour l' clickévénement. À l'intérieur, nous allons faire une demande à l'API:

get_meal_btn.addEventListener('click', () => { fetch('//www.themealdb.com/api/json/v1/1/random.php') .then(res => res.json()) .then(res => { createMeal(res.meals[0]); }) .catch(e => { console.warn(e); }); }); 

Nous utilisons l'API fetch pour faire la demande. Nous devons juste passer l'url de l'API à laquelle nous voulons faire une requête GET , et nous allons récupérer une promesse.

Une fois que cela est résolu, nous avons une réponse ( res). Ce resn'est pas encore dans l'état que nous voulons qu'il soit, nous allons donc appeler la .json()méthode dessus. Puis enfin nous avons le bel objet. Yay! ?

Comme mentionné ci-dessus, l'API renvoie le mealstableau mais uniquement avec un élément. Nous allons donc passer cet élément (à l'index 0) dans notre createMealfonction, que nous définirons ensuite.

Je vais coller tout le bloc de code ci-dessous et nous allons entrer dans les détails par la suite, alors attendez une seconde. ?

const createMeal = meal => { const ingredients = []; // Get all ingredients from the object. Up to 20 for (let i = 1; i <= 20; i++) { if (meal[`strIngredient${i}`]) { ingredients.push( `${meal[`strIngredient${i}`]} - ${meal[`strMeasure${i}`]}` ); } else { // Stop if there are no more ingredients break; } } const newInnerHTML = `  ${ meal.strCategory ? `

Category: ${meal.strCategory}

` : '' } ${meal.strArea ? `

Area: ${meal.strArea}

` : ''} ${ meal.strTags ? `

Tags: ${meal.strTags .split(',') .join(', ')}

` : '' }
Ingredients:
    ${ingredients.map(ingredient => `
  • ${ingredient}
  • `).join('')}

${meal.strMeal}

${meal.strInstructions}

${ meal.strYoutube ? `
Video Recipe
` : '' } `; meal_container.innerHTML = newInnerHTML; };

Fondamentalement, le but de la fonction entière est d'obtenir la réponse JSON, de l'analyser et de la transformer en un composant HTML. Pour cela, nous devons faire deux ou trois choses, car les données ne sont pas encore formées exactement comme nous le souhaitons.

First, we're getting all the ingredients and their measures. As mentioned above there are a maximum of 20 ingredients, but they are separated into their own properties in the object like: strIngredient1, strIngredient2, etc... (I still don't know why they did that, but... ?).

So, we're creating a for loop which goes from 1 to 20 and checks if the meal has that corresponding ingredient-measure pair. If it does, we're putting it into the ingredients array. If there aren't any more ingredients we're stopping the for loop with a break condition.

Next, we're creating the newInnerHTML string which is going to hold the entire HTML markup. In it we are parsing the remaining properties that we want to be displayed.

Note that some of the properties might not be available. So for that we're using the ternary operator to check if we have the data to display the corresponding tag. If we don't have it then we're returning an empty string and nothing will be displayed on the page. The category and the area are examples of these type of properties.

The tags are coming in a string divided by a comma like: 'tag1,tag2,tag3'. So we need to split it by that comma, and join it back by a comma and a space as it looks nicer ('tag1, tag2, tag3' ❤️). Or at least for me does. ?

To show the ingredients, we're mapping over the array and we're creating an

Original text


  • for each ingredient/measure pair. At the end we're joining the array back to form a string. (This is something you would do in ReactJS but without the joining part ?).

    There is also a Youtube video string (maybe) which is returning the URL of the video. But in order for us to embed the video in the page we need to extract the video ID only. For that we're using .slice(-11) to get the last 11 characters of the string as this is where the ID is hiding ?.

    And finally, we're setting this entire newInnerHTML to be the meal_container's innerHTML -> this will populate that div with all this information!

    This entire process will repeat every time we're pressing the Get Meal button.

    The CSS

    The last part is to style it a little bit, right? ?

    For the CSS I wanted to use something new so I tried out the SkeletonCSS library. It's useful if you have a small project and don't want to get overwhelmed with all those classes, as it only has a couple of them that take care of some basic styling (the button for example) and the responsive part.

    @import url('//fonts.googleapis.com/css?family=Muli&display=swap'); * { box-sizing: border-box; } body { display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 30px 0; min-height: calc(100vh - 60px); } img { max-width: 100%; } p { margin-bottom: 5px; } h3 { margin: 0; } h5 { margin: 10px 0; } li { margin-bottom: 0; } .meal { margin: 20px 0; } .text-center { text-align: center; } .videoWrapper { position: relative; padding-bottom: 56.25%; padding-top: 25px; height: 0; } .videoWrapper iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } 

    You can see that the CSS is pretty simple. The only part that's worth mentioning is the .videoWrapper CSS declaration. This makes sure that the YouTube embed is responsive. (Got this from CSS-Tricks - thanks guys! ?)

    Conclusion

    And voilà! We're done! ?

    You should now know how to use a public API to get some data which you can then insert on the page easily! Well done! ?

    This is the first project I did for the #100Days100Projects challenge. You can check out what other projects I've built and what are the rules of the challenge (if you might want to join) by clicking here.

    You can read more of my articles on www.florin-pop.com.

    Happy Coding! ?