Search code examples
javascriptnode.jsajaxejsedamam-api

Updating ejs template with AJAX request


I am creating a web app that returns recipes when a user searches for a specific ingredient(s). I am using the edamam api and decided to use pagination so that I could load more recipes relative to the users search when the user clicks on the "Load more" button. Note that edamam will return 20 recipes at a time. This is from edamam's documentation on how their pagination works:

The way pagination works in recipe search API has changed significantly from version 1. The parameters from and to are no longer supported. Instead, responses will contain a pre-constructed URL for the next request in _links.next.href. If this path is not present, then this was the last page and there are no more results.

I largely got this functionality working however, there is a part of my html that utilizes an ejs if statement that is causing some problems (issue in "loadrecipes.js").

My question is 2 fold:

  1. How can I resolve the issues with the if statement?
  2. Is there a better way to update ejs when using an AJAX request?

The code. Git hub repo is here for easier viewing

I have 3 files:

  1. index.js - server side code
  2. index.ejs - html with ejs templating
  3. loadrecipes.js - client side code

index.js

import express from "express";
import axios from "axios";
import bodyParser from "body-parser";
import 'dotenv/config';

const app = express();
const port = 3000;
const API_URL = "https://api.edamam.com/api/recipes/v2?type=public";
const myId = process.env.ID;
const myAPIKey = process.env.API_KEY;

app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static("public"));

let nextPageUrl = null;

app.get("/", (req, res) => {
    res.render("index.ejs");
})

app.post("/search", async (req, res) => {
    const inputText = req.body.ingrediants;
    try{
        const requestUrl = nextPageUrl || API_URL;

        const response = await axios.get(requestUrl, {
            params: {
                q: inputText,
                app_id: myId,
                app_key: myAPIKey
            }
        });
        const result = response.data;

        //store next page url for pagination
        if(result._links.next.href){
            nextPageUrl = result._links.next.href;
        } else{
            nextPageUrl = null; //no more pages/recipes given the user search
        }

        res.render("index.ejs", {
            recipes: result,
            userInput: inputText,
            nextPageUrl: nextPageUrl
        });
    }catch(error){
        res.render("index.ejs", {
            content: "Looks like there's an issue: " + error.message
        });

    }
})


app.listen(port, () => {
    console.log(`Server listening on port ${port}.`);
})

index.ejs

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cook Your Kitchen</title>
    <link rel="stylesheet" type="text/css" href="styles/main.css">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
</head>

<body>
    <div class="container">
        <div class="px-4 py-5 my-4 text-center">
            <h1 class="display-5 fw-bold text-body-emphasis">Cook Your Kitchen</h1>
            <div class="col-lg-6 mx-auto">
                <p class="lead mb-4">Have some random ingrediants in your kitchen? <br>
                    Those veggies about to go bad?? <br>
                    Enter the ingrediants you want to use and get recipe suggestions!
                </p>
                <form action="/search" method="post">
                    <div class="d-grid gap-2 d-sm-flex justify-content-sm-center">
                        <input type="text" id="search" name="ingrediants" class="form-control"
                            placeholder="Chicken, beans, tomato,..." aria-label="Recipient's username"
                            aria-describedby="button-addon2">
                        <button class="btn btn-outline-secondary" type="submit" value="Submit" id="button-addon2">Get a recipe</button>
                    </div>
                </form>

            </div>
        </div>
    </div>

    <% if(locals.recipes){ %>
        <div class="container px-4 py-5" id="custom-cards">
            <h2>You searched for: <%= userInput %>
            </h2>
            <div id="recipe-list" class="row row-cols-1 row-cols-lg-3 align-items-stretch g-4 py-5">
                <% recipes.hits.forEach(recipe=> { %>
                    <div class="col" onclick="location.href='<%= recipe.recipe.url %>';">
                        <div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4"
                            style="background-image: url('<%= recipe.recipe.image %>');">
                            <div class="d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1 text-bg">
                                <h3 class="mt-5 mb-4 display-6 lh-1 fw-bold">
                                    <%= recipe.recipe.label %>
                                </h3>
                                <ul class="d-flex list-unstyled mt-auto">
                                    <% if (recipe.recipe.dietLabels.length > 0) { %>
                                        <li class="badge text-bg-dark rounded-pill px-2">
                                            <small>
                                                <%= recipe.recipe.dietLabels[0]%>
                                            </small>
                                        </li>
                                        <%} %>

                                            <li class="badge text-bg-dark rounded-pill mx-2 px-2">
                                                <small>
                                                    <%= recipe.recipe.healthLabels[0] %>
                                                </small>
                                            </li>
                                            <li class="badge text-bg-dark rounded-pill px-2">
                                                <small>
                                                    <%= recipe.recipe.healthLabels[1] %>
                                                </small>
                                            </li>

                                </ul>
                            </div>
                        </div>
                    </div>
                    <% })} %>
            </div>
        </div>

      <% if (locals.nextPageUrl){ %>
        <div class="text-center mb-4">
            <button type="button" class="btn btn-light" id="loadMoreButton" data-next-page="<%= nextPageUrl %>">Load More</button>
        </div>
      <% } %>
        
      
       <script src="https://code.jquery.com/jquery-3.7.1.js" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" 
       crossorigin="anonymous"></script>
        <script src="loadrecipes.js"></script>
</body>

</html>

This is the part in index.ejs I am trying to update (red box) enter image description here

loadrecipes.js

const nextPageUrl = $("#loadMoreButton").data("next-page");

$("#loadMoreButton").on("click", (event) => {
    event.preventDefault();
    event.stopPropagation(); 

//using ajax to load more recipes
    if (nextPageUrl) {
        $.ajax({
            method: "GET",
            url: nextPageUrl,
            success: (result) => {
                // Append result to the existing recipes
                // and update the nextPageUrl data attribute if there's a next page.
                const moreRecipes = result.hits;
                if(moreRecipes.length > 0){
                    moreRecipes.forEach((recipe) => {
                        //copying card html/ejs from index.ejs and replacing ejs tags for each additionally retrieved recipe
                        const recipeCard = `
                        <div class="col" onclick="location.href='${recipe.recipe.url}';">
                        <div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4"
                            style="background-image: url('${recipe.recipe.image}');">
                            <div class="d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1 text-bg">
                                <h3 class="mt-5 mb-4 display-6 lh-1 fw-bold">
                                    ${recipe.recipe.label}
                                </h3>
                                <ul class="d-flex list-unstyled mt-auto">
                                    <% if (${recipe.recipe.dietLabels.length} > 0) { %>
                                        <li class="badge text-bg-dark rounded-pill px-2">
                                            <small>
                                                ${recipe.recipe.dietLabels[0]}
                                            </small>
                                        </li>
                                        <%} %>

                                            <li class="badge text-bg-dark rounded-pill mx-2 px-2">
                                                <small>
                                                    ${recipe.recipe.healthLabels[0]}
                                                </small>
                                            </li>
                                            <li class="badge text-bg-dark rounded-pill px-2">
                                                <small>
                                                    ${recipe.recipe.healthLabels[0]}
                                                </small>
                                            </li>

                                </ul>
                            </div>
                        </div>
                    </div>
                        `;

                         // Append the new recipe card to the recipe list div
                         $("#recipe-list").append(recipeCard);
                    });

                        // Update the nextPageUrl data attribute
                        $("#loadMoreButton").data("next-page", result._links.next.href);
                    } else {
                        // If there are no more recipes, hide the "Load More" button
                        $("#loadMoreButton").hide();
                    }
                }
        });
    }
})

I've been looking through documentation and other similar questions posted on stack overflow but haven't found much that is helpful to this exact issue. Any help will be appreciated!


Solution

  • After a lot trial and error and reading various pieces of documentation I solved my problem by creating a global variable to hold the diet label html/ejs that needed to be updated + appended. I pulling out the if statement in recipeCard variable in loadrecipes.js. and assigned the argument of the if statement to diet label variable. I then referenced the diet label variable in the recipe card update. I am convinced there is likley a better solution but this what I've come up that works.

    
    const nextPageUrl = $("#loadMoreButton").data("next-page");
    
    $("#loadMoreButton").on("click", (event) => {
        event.preventDefault();
        event.stopPropagation(); 
    
        let dietLabel = "";
    
    //using ajax to load more recipes
        if (nextPageUrl) {
            $.ajax({
                method: "GET",
                url: nextPageUrl,
                success: (result) => {
                    // Append result to the existing recipes
                    // and update the nextPageUrl data attribute if there's a next page.
                    const moreRecipes = result.hits;
                    if(moreRecipes.length > 0){
                        moreRecipes.forEach((recipe) => {
                            if(recipe.recipe.dietLabels.length > 0){
                                dietLabel = `
                                <li class="badge text-bg-dark rounded-pill px-2">
                                                <small>
                                                    ${recipe.recipe.dietLabels[0]}
                                                </small>
                                            </li>
                                `;
                            }
                            //copying card html/ejs from index.ejs and replacing ejs tags for each additionally retrieved recipe
                            const recipeCard = `
                            <div class="col" onclick="location.href='${recipe.recipe.url}';">
                            <div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4"
                                style="background-image: url('${recipe.recipe.image}');">
                                <div class="d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1 text-bg">
                                    <h3 class="mt-5 mb-4 display-6 lh-1 fw-bold">
                                        ${recipe.recipe.label}
                                    </h3>
                                    <ul class="d-flex list-unstyled mt-auto">
                                                 ${dietLabel}
                                                <li class="badge text-bg-dark rounded-pill mx-2 px-2">
                                                    <small>
                                                        ${recipe.recipe.healthLabels[0]}
                                                    </small>
                                                </li>
                                                <li class="badge text-bg-dark rounded-pill px-2">
                                                    <small>
                                                        ${recipe.recipe.healthLabels[1]}
                                                    </small>
                                                </li>
    
                                    </ul>
                                </div>
                            </div>
                        </div>
                            `;
    
                             // Append the new recipe card to the recipe list div
                             $("#recipe-list").append(recipeCard);
                        });
    
                            // Update the nextPageUrl data attribute
                            $("#loadMoreButton").data("next-page", result._links.next.href);
                        } else {
                            // If there are no more recipes, hide the "Load More" button
                            $("#loadMoreButton").hide();
                        }
                    }
            });
        }
    })