javascriptnode.jsexpressejs

EJS isn't being rendered


I'm making a rock, scissors, paper game on the backend of Node.js with express server, frontend.js client-side, index.ejs and main.css files. I firstly want to render a string line of the result of your decision (won, lost, the same - try again). However, it doesn't render the result. I tried to use console.log to detect if the data is passed between the routes, which indeed happens and it literally shows that the data is in the ejs template, but it still doesn't render. I tried to use chat GPT to figure it out and it failed...

backend.js:

    import express from "express";
import bodyParser from "body-parser";
import pg from "pg";
import ejs from "ejs";
import axios from "axios";
import { dirname } from "path";
import { fileURLToPath } from "url";


// import icons from "bootstrap-icons";
const __dirname = dirname(fileURLToPath(import.meta.url));
const app = express();
const port = 3000;


let gameResult = [];

app.set('view engine', 'ejs');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static("public"));


app.get("/", (req, res) => {

   const results = [...gameResult];
    gameResult = [];

    console.log("Result from session:", results);

    res.render("index", {results});
});

app.post("/add", (req,res) => {

    const {targetedId, dataIdList} = req.body;

    let compSelector = dataIdList[Math.floor(Math.random() * dataIdList.length)];

    console.log(`Chosen by client: ${targetedId}`);
    console.log(`Chosen by computer: ${compSelector}`);



  function determineResult(targetedId, compSelector) {

    switch (targetedId) {
        case "paper":
            if(compSelector === "scissors") {
                return "You lost";
                
            } else if(compSelector === "rock") {
                return "You won!";
                
            } else {
                return "The same, try again";
                
            }
            break;

        case "scissors":
            if(compSelector === "rock") {
                return "You lost";

            } else if(compSelector === "paper") {
                return "You won!"

            } else {
                return "The same, try again";
            }
            break;

            case "rock":
                if(compSelector === "paper") {
                    return "You lost";
    
                } else if(compSelector === "scissors") {
                    return "You won!"
    
                } else {
                    return "The same, try again";
                }
            break;

        default:
            console.log("Error");
            break;
    }

 }

 try {
    const result = determineResult(targetedId, compSelector);
    console.log(result);
   
    gameResult = [result];
   
    res.redirect("/");
 } catch (error) {
    console.error("Error handling POST /add request:", error);
        res.status(500).send("Internal Server Error");
 }




});



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

frontend.js:

console.log("Frontend.js is loaded");

function myFrontFunction(event) {

    const selectedImage = event.target;
    const targetedId = selectedImage.getAttribute("data-id");
    const images = document.querySelectorAll(".image");
    const dataIdList = [];

    images.forEach(dataId => {
       dataIdList.push(dataId.getAttribute("data-id"));
    });

    console.log(`Data list: ${dataIdList}`);


// console.log(typeof(idData));


    console.log(targetedId);

    fetch("/add", {
        method: "POST",
        headers: {"Content-Type": "application/json"},
        body: JSON.stringify({targetedId, dataIdList}),
    })
    .then(response => {

        if(response.ok) {
        console.log("Data sent successfully");
        response.text().then(text => console.log(text));
        console.log("All images:", images); 

        images.forEach(image => {
            if(image !== selectedImage){
                console.log("Hiding image:", image);
                image.classList.add('hidden'); // Use class to hide
            }
        });
        } else {
            console.error("Failed to send data to the server");
        }

        

    })
    .catch(error => console.error("Error:", error));

}

index.ejs:

<%- include('partials/header.ejs') %>


<h1>The RSP App!</h1>

<h2>Choose Rock, Scissors or Paper:</h2>

<img class="image" src="/images/paper.png" alt="A piece of paper" width="400px" height="400px" data-id="paper" onclick="myFrontFunction(event)">
<img class="image" src="/images/rock.png" alt="A rock" width="400px" height="400px" data-id="rock" onclick="myFrontFunction(event)">
<img class="image" src="/images/scissors.png" alt="Scissors" width="400px" height="400px" data-id="scissors" onclick="myFrontFunction(event)">


<div>
<% if (results.length > 0) { %>
    <% results.forEach( result => { %>
       <p> <%= result %></p>
    <% }) %>
<% } %>
</div>






<script src="/frontend.js"></script>

<%- include('partials/footer.ejs') %>

main.css:

.hidden {
    display: none;
}

Solution

  • With the way you have set up your code it cannot show the results. Let's go through what happens:

    1. User opens page at / and can choose one of the images
    2. Once any image is clicked, let's say scissors, you send a POST request to /add at the backend with the data {targetedId: "scissors", dataIdList: ["paper", "rock", "scissors"]}
    3. The route handler of /add selects an random option and then checks for the result.
    4. Once the result is determined, the array gameResults will now contain the result and respond with a redirect to /. The router handler for that route will then take and reset the value of gameResults, so now gameResults is an empty array again.
    5. The page will be rendered and the rendered markup will be sent back to the fetch request as response. That means that the line response.text().then(text => console.log(text)); should print the HTML markup for the entire page with the results rendered. But as nothing is done with the page the user is on, nothing except the not clicked images "hiding" and the console log of the markup will happen.
    6. If the user tries to reload the page, it will still show nothing as result, because if we remember, the redirect to / in the /add handler has reset the value of gameResults to []

    So basically what you ended up doing is "ignoring" the result that came back with the fetch request, while at the same time discarding the value at the backend.

    So something you could do to make your current code work, is to send the result itself back as response and add it to the page in a new element, so something like:

    backend.js

     try {
        const result = determineResult(targetedId, compSelector);
       
        res.send(result);
     } catch (error) {
        ...
     }
    

    frontend.js

    fetch(...)
    .then(res => res.text())
    .then(text => {
      let p = document.createElement('p');
      p.textContent = text;
      document.querySelector('div').appendChild(p);
    });
    

    And there are many other ways to do it, it depends on how you want to do it. If you look at what @KooiInc linked in the comments, you can see an approach that entirely works on the client-side. It might look a little imtimidating for a beginner, I recommend seperating the lines with some newlines to have a better overview and then understanding whats going on.

    Even if you want to do it with backend calculations and EJS there are different ways to approach this, but as I said, in the end it comes down to how you want to make it. If you want to see a different approach with backend+EJS just let me know :)