DEV Community

loren-michael
loren-michael

Posted on

My Recipe Manager Project

Today I wrapped up working on my first project. I decided to make a recipe manager that can help me with meal planning every week. The basic concept is that I can use this site to enter in recipes with forms for their ingredients, instructions, where it came from, etc... and have them all get stored in my own database to pull up later.

Figuring out a database

First I had to figure out a database. I referenced some free recipe API's on the internet, but for my vision there was just too much information and it felt too bulky. Instead, I created my own json file and figured out exactly what information I wanted to be able to store. I started with basic things like the name of the recipe, the link where I found it, an image, ingredients and instructions. Knowing what information I wanted to save, I created a form on the site to allow me to input all of this information. Then I wrote a function that creates a recipe object when the form is submitted, then that recipe object gets posted to the database. Here is an example of one of the recipe objects:

{
      "img": "https://www.rabbitandwolves.com/wp-content/uploads/2018/07/DSC_0872.jpg",
      "video": "",
      "name": "Vegan Caesar Pasta Salad",
      "source": "https://www.rabbitandwolves.com/caesar-pasta-salad-vegan/#recipe",
      "author": "Lauren Hartmann",
      "mealtype": "Side",
      "preptime": "10 mins",
      "cooktime": "30 mins",
      "servings": "6",
      "instructions": [
        "Cook your pasta according to package directions. Drain and let cool. ",
        "Make the dressing. Add all the dressing ingredients to a blender. Blend until smooth, taste and adjust seasoning. ",
        "Once the croutons are done, in a large bowl, add the chopped romaine, pasta, croutons and then pour the dressing on and toss. Serve immediately. "
      ],
      "ingredients": [
        "1/2 C. Vegan Mayo",
        "2 tsp. Dijon mustard",
        "1 Tbsp. Capers",
        "Juice of 2 Lemons, about 2 Tbsp.",
        "2 Tbsp. Nutritional yeast",
        "1 Clove Garlic",
        "1/4 tsp. Salt",
        "Pinch of black pepper",
        "1 Head Romaine, chopped",
        "16 oz. Pasta"
      ],
      "comments": [
        "Omitted the crouton portion of the recipe as we don't make them.",
        "Mini penne is perfect",
        "We don't use nutritional yeast in our dressing, and only use half a lemon."
      ],
      "id": 17
    }
Enter fullscreen mode Exit fullscreen mode

Rendering is fun!

In many places, I encountered the need to iterate through arrays, including the recipes themselves. As an example, I decided to use some forEach loops to render the recipe names in a list:

function renderAllRecipes(recArr) {
recArr.forEach(renderOneRecipe)
}

Of course, the renderOneRecipe is much more involved, but this was a powerful way to be able to iterate through each of the items in my database and simply pick out the name of the recipe to display it as an option. Now, when our page loads, we see this:

RecipeManager1.0

Remember when I mentioned that the renderOneRecipe function was a little more involved? Well, I had to make sure that I was not only displaying the name of the recipe as the inner text of my list item, I also set the id of each of them to the id number of the recipe and added an event listener so that you can click on them to load the entire recipe. I use the id number set from the original fetch to fetch the entire recipe card and display each of the relevant elements on the page in a meaningful way. I made sure that my images would be the same size, I made sure that credit was given and source links provided for each recipe that I use from somewhere else on the web, and I iterated over any arrays in my recipe object to display lists where they're needed.

Getting around without reloading

I also wanted to build some navigational functionality, but without having to reload the page to go between recipes and details. I was able to use two different methods to demonstrate this. For one, I use part of my function to change the display parameter of an element from inline-block to none and vice versa. These two lines of code look like this:

recListSec.style="display: inline-block";
and
recListSec.style="display: none";

The other method I used to hide elements was by assigning or removing the "hide" class to things I wanted to be hidden. These lines looks like this:

selectBar.classList = "hide";
and
selectBar.classList.remove("hide")

Now, when we go back and forth between the index view and a recipe details view, we don't see things that aren't relevant to the details view.

Form building

Now to tackle some forms. I wanted to create a way to enter in all of the information I need for a new recipe. Things like "recipe name" and "servings" are pretty straightforward, but every recipe is different. How do I know how many ingredients are on the list? How many steps are there? Instead of filling up the page with empty boxes, I decided to make a button that would give you a new text box so you never have more than you need. (Don't worry if you hit it too many times, I have a solution for that too!) From there, I wrote a function called createNewRecObj that takes the information from the form and enters it into a new object that then gets sent to a fetch method to POST it to the database. Here is the createNewRecObj function:

function createNewRecObj (e) {
    e.preventDefault();

    // Ingredient array
    let ingArr = [];
    let ingColl = document.getElementsByClassName("add-ingredient");
    for (let i = 0; i < ingColl.length; i++) {
        if (ingColl[i].value.length > 0) {
            ingArr.push(ingColl[i].value)
        }
    };

    // Instruction array
    let instrArr = [];
    let instrColl = document.getElementsByClassName("add-instructions");
    for (let i = 0; i < instrColl.length; i++) {
        if (instrColl[i].value.length > 0) {
            instrArr.push(instrColl[i].value)
        }
    };

    // Comment array
    let commArr = [];
    if (document.getElementById("init-comm").value.length > 0) {
        commArr.push(document.getElementById("init-comm").value)
    };

    // Create the new recipe object that will get sent to the database
    let newRecObj = {
        img: document.getElementById("add-img").value,
        video: document.getElementById("add-video").value,
        name: document.getElementById("add-name").value,
        source: document.getElementById("add-source").value,
        author: document.getElementById("add-author").value,
        mealtype: document.getElementById("add-meal-selector").value,
        preptime: document.getElementById("add-preptime").value,
        cooktime: document.getElementById("add-cooktime").value,
        servings: document.getElementById("add-servings").value,
        instructions: instrArr,
        ingredients: ingArr,
        comments: commArr
    }
    postNewRecipe(newRecObj);
}
Enter fullscreen mode Exit fullscreen mode

Looking into this code a little more, we can see that I'm making three arrays. Looking at the ingredient array, we can see that I am finding each of the text boxes being used for the ingredient list, using a for...of loop to iterate through them, and also looking at the value length to make sure that I'm not adding an empty string to my array.

Then, we simply take each of the form inputs and assign them to their proper key in the object, and send them off to my postNewRecipe function with the newly made recipe object as the argument.

To post, we fetch our resource, tell it we're making a POST with content-type of application/json, and in the body of our POST, stringify our recipe object. This will add our new recipe to the database, and the page will revert to displaying the recipe list with our newly added recipe included.

Moving forward and a note on accessibility

Obviously, there were many other features added to this project and I don't want to nitpick over each and every one of them. I plan on continuing to work on this to add more functionality in the future. If you noticed in the original recipe database item at the top, there are some other tags that aren't used quite yet, like "video embed" and "tags". I would like to find a way to use those things in future versions of this project.

I also am learning the importance of making things as accessible as possible. There are parts of this project that are not as accessible as they could be, so I plan to update them as I learn more about javascript to demonstrate that I am able to consider users of all abilities who might like to use this project moving forward.

Thank you for looking over my project! Make sure you check it out in the future for new versions!

Top comments (0)