padcoding
padcoding

Reputation: 11

How pass Mongo collection to EJS template and then create select tag with customizable options

For a cookbook, I create a new recipe. Within that recipe, I enter the recipe name, the instructions, and then have an input field for the number of ingredients. When you enter a number, you clicked the Add button. Based upon the input, the equal number of Dropdown menus will appear.

Caveat: The options available in each dropdown list should be the values from a Mongo database. Selecting one value in a markdown should prevent another value from being available. Ex: (Enter 3 into the field. Click add. 3 dropdowns appear. In each dropdown you have: Carrots Onions Potatoes

Selecting Carrots in the first dropdown makes the second dropdown only have onions and potatoes available.

Current Crux: I can pass in the ingredients from my controller and display them in HTML. I cannot within a element.

I have tried JSON.Stringify within the script (within the HTML). I have tried JSON.Stringify within the route of the controller.

I can iterate within the element using EJS output tags. <%= ingredientsData %> or <% ingredientsData.name %>

I cannot do the same with the element.

Model

const mongoose = require("mongoose");

// SCHEMA /
const ingredientSchema = new mongoose.Schema({
    name: { type: String, required: true }
});

const Ingredient = mongoose.model('Ingredient', ingredientSchema)
module.exports = Ingredient;

Controller

router.get('/new', async (req, res) => {
  console.log("router get recipes/OGnew.ejs");
  try {
    let allIngredients = await Ingredient.find({});
    allIngredients = JSON.stringify(allIngredients);
    console.log(allIngredients);
    res.render('recipes/OGnew.ejs', {ingredientsData: allIngredients});
  } 
  catch (error) {
    console.log("ERROR: get /new", error);
    res.redirect('/recipes');
  }
});

HTML/EJS

<!-- views/recipes/OGnew.ejs -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Create New Recipe</title>
  </head>
<body>
    <%- include('../partials/_navbar') %>
    <h1>Create Recipe</h1>
    <form action="/recipes" method="POST">
      <label for="name">Recipe Name:</label>
      <input type="text" name="name" id="name" placeholder="Required" required/>
      <label for="instructions">Instructions:</label>
      <input type="text" name="instructions" id="instructions"/>
      <button type="submit">Add Recipe to Database</button>
    </form>
  <% console.log(ingredientsData); %>
  <% console.log(ingredientsData.length); %>
  <% if (ingredientsData.length === 0) { %>
      <h2>You have not added any ingredients yet.</h2>
  <% } %>
  <label for="num">Select Ingredients:</label>
  <input type="text" name="num" id="num" placeholder="Enter # of ingredients" min="1" required>  
  <div id="dropdown-div"></div>
  <button id="add-dropdown-button">Add</button> 
  <script type="text/javascript">
  console.log("Running script...")
  function createDropdowns() {
    console.log("Creating Dropdowns...")
    const dropdownContainer = document.getElementById('dropdown-div');
    dropdownContainer.innerHTML = '';
    for (let i = 0; i < numberInput.value; i++) {
      const dropdown = document.createElement('select');
      dropdown.name = `ingredient${i + 1}`;
      // Loop through ingredientsData array and create options
      const listIngredients = <%= JSON.stringify(ingredientsData) %>;
      //listIngredients = [1,2,3,4];   **IF I REMOVE ANY REFERENCE TO ingredientsDATA and USE this array, the dropdowns will appear but I cannot populate them with the ingredientsData
      listIngredients.forEach(ingredient => {
        const option = document.createElement('option');
        option.value = 1;//Should be ingredient.name (or ingredient._id)
        option.text = 1; //Should be ingredient.name
        dropdown.appendChild(option);
      });
    dropdownContainer.appendChild(dropdown);
    }
  }
      const numberInput = document.getElementById('num');
      let button = document.getElementById("add-dropdown-button");
      button.addEventListener("click", createDropdowns);
  </script>
  </body>
</html>


My code: https://github.com/padcoding1/men-stack-relating-data-lab-cookbook

Upvotes: 1

Views: 60

Answers (1)

traynor
traynor

Reputation: 8752

On the server try removing JSON.stringify:

const allIngredients = await Ingredient.find({});
console.log(allIngredients);
res.render('recipes/OGnew.ejs', {ingredientsData: allIngredients});

and in the template, use unescaped value tag (<%- instead of <%=) when passing variable to JavaScript:

  const listIngredients = <%- JSON.stringify(ingredientsData) %>;
  console.log('listIngredients', listIngredients);

Regarding excluding selected values from other dropdowns: I'd store all dropdown elements in an array, and add change event listener to each, and when option is selected, remove that option from other dropdowns:

console.log("Running script...")

function createDropdowns() {
  console.log("Creating Dropdowns...")
  const dropdownContainer = document.getElementById('dropdown-div');
  dropdownContainer.innerHTML = '';

  // store dropdowns for later
  const dropdowns = [];
  
  // on change, remove selected option from all dropdowns changed one
  function updateOpts(e) {

    console.log('updating all but: ', e.target.name);

    dropdowns.forEach(dropdownEl => {

      if (dropdownEl.name != e.target.name) {

        [...dropdownEl.options].forEach((opt, i) => {

          if (opt.value === e.target.value) {
            dropdownEl.options.remove(i);
          }
        });
      }
    })
  }


  for (let i = 0; i < numberInput.value; i++) {
    const dropdown = document.createElement('select');
    dropdown.name = `ingredient${i + 1}`;

    // add select indicator option, default selected
    const option = document.createElement('option');
    option.text = '--select--';
    option.setAttribute('disabled', 'true');
    option.setAttribute('selected', 'true');
    dropdown.appendChild(option);

    // dummy data
    const listIngredients = [{
      name: 'Carrots',
      _id: '111'
    }, {
      name: 'Onions',
      _id: '2222'
    }, {
      name: 'Potatoes',
      _id: '333'
    }];

    listIngredients.forEach(ingredient => {
      const option = document.createElement('option');
      option.value = ingredient._id;
      option.text = ingredient.name;
      dropdown.appendChild(option);
    });
    // on change, update options
    dropdown.onchange = updateOpts;
    dropdownContainer.appendChild(dropdown);
    dropdowns.push(dropdown);
  }
 

}
const numberInput = document.getElementById('num');
let button = document.getElementById("add-dropdown-button");
button.addEventListener("click", createDropdowns);
<label for="num">Select Ingredients:</label>
<input type="text" name="num" id="num" placeholder="Enter # of ingredients" min="1" required>
<div id="dropdown-div"></div>
<button id="add-dropdown-button">Add</button>

Upvotes: 1

Related Questions