0

I'm building a simple recipe app using Node/Express. User gets a 'new recipe' HTML form, where they can click an 'add ingredient' button and add as many ingredients to the form as they need. Using jQuery, every time the plus icon is clicked, a new text input for the new ingredient is generated:

        var newIngredient = $('#newIngredient').val();

        ingredientCount++;

        //give each ingredient a unique name based on count
        var ingredientID = "ingredient_" + ingredientCount

        // reset input to blank
        $("#newIngredient").val("");

        // add ingredient to list for user to see or edit.
        $("#ingredientsList").append(
           '<input class="form-control" type="text" required 
            name=' + ingredientID + ' value="' + newIngredient + '">')

Each ingredient added is assigned a unique name via the counter. This all works fine and when the form is submitted I get:

{
  name: 'Soup',
  ingredient_1: 'carrots',
  ingredient_2: 'salt',
  ingredient_3: 'turnips',
  method: 'Throw all these ingredients together and voila, you have a terrible soup.'
}

In the backend, I want to take each of these ingredients and add them to an 'ingredients' array (because I don't think I can send an array of ingredients from the HTML form). I obviously don't want to hard code ingredients.push(req.body.ingredient_1), ingredients.push(req.body.ingredient_2) etc... because the number of ingredients for each recipe will vary. Is there a way to increment variable names? This might illustrate what I'm trying to do, though I know it obviously does not work:

ingredientsList=[];

var counter=1;

while(req.body.**"ingredient_"+counter**){
    ingredientsList.push(req.body.**'ingredient_'+counter**);
    counter++
}

Alternative ideas for passing dynamically created name:value pairs from an HTML form to JS backend would be welcome, but I would also like to know if it is possibly to dynamically increment parameters in the way I have tried above - req.body.parameter[i]

Thanks!

2
  • Seems like you are reinventing an array Commented Feb 4, 2020 at 18:09
  • I would just make them all name='ingredient' and then on the back end get an array of ingredients. Commented Feb 4, 2020 at 18:20

4 Answers 4

1

You can dynamically check for keys like that, as long as you are sure they are numeric. If it's possible to have ingredient_1 and ingredient_3 but no ingredient_2, then you would need to remove the break statement.

var data = {
  name: 'Soup',
  ingredient_1: 'carrots',
  ingredient_2: 'salt',
  ingredient_3: 'turnips',
  method: 'Throw all these ingredients together and voila, you have a terrible soup.'
}
var ingredientsList=[];
for(var num=1; num<10000;num++){
  if(!data['ingredient_'+num]){break;}
  ingredientsList.push(data['ingredient_'+num]);
}
console.log(ingredientsList);

Sign up to request clarification or add additional context in comments.

1 Comment

you can accomplish this in one line if you'd like: let ingredients = Object.keys(data).filter(k => k.startsWith("ingredient_")).map(o => data[o]);
1

because I don't think I can send an array of ingredients from the HTML form

That's not true. Can I suggest to send a JSON object to a server?

This way you will be able to:

  1. Send any structure you want
  2. Simplify your life by moving parsing to the backend

From client:

var data = {
    name: 'Soup',
    ingredient_1: 'carrots',
    ingredient_2: 'salt',
    ingredient_3: 'turnips',
    method: 'Throw all these ingredients together and voila, you have a terrible soup.'
};

$.ajax({
    url: url,
    type: 'POST',
    contentType: 'application/json',
    data: JSON.stringify(data),
    dataType: 'json',
    success: function(data) {
        //On success do something
        alert(data);
    },
    error: function(xhr, ajaxOptions, thrownError) {
        //On error do something
    }
});

... and on the server side something like:

const bodyParser = require('body-parser')

app.use(
  bodyParser.urlencoded({
    extended: true
  })
)

app.use(bodyParser.json())

app.post('/endpoint', (req, res) => {
  console.log(req.body.name) // 'Soup'
})

I hope it helps! 🙂

2 Comments

Thanks, this is useful, but I suppose I still have the same issue server-side. If I want to select all the ingredients I would have to manually specify req.body.ingredient_1 + req.body.ingredient_2 ... etc, which is not only inefficient but problematic as my program would not know how many ingredients have been submitted and therefore how many ingredients to try to add. That is why I was hoping there was some way of selecting all properties that begin with ingredient_, or code that would append the number on each time and thus allow me to write something like ingredient_{counter} etc...
@bobby_belfast no problem. The only thing I'd like to mention - you can loop thru JavaScript object properties dynamically like for (var prop in obj) {if (Object.prototype.hasOwnProperty.call(obj, prop)) { //prop is property i.e. ingredient_1, 2,3 and obj[prop] its value}}
0

You can use string literals:

const ingredientsList = [];
var counter=1;
while(yourCondition){
    ingredientsList.push(`req.body.ingredient_${counter++}`);
}

1 Comment

This simply adds 'req.body.ingredient_1' to the array. ie [ 'req.body.ingredient_1' , 'req.body.ingredient_2] etc
0

Edit: you can accomplish this pretty easily in one line:

const DATA = {
  name: 'Soup',
  ingredient_1: 'carrots',
  ingredient_2: 'salt',
  ingredient_3: 'turnips',
  method: 'Throw all these ingredients together and voila, you have a terrible soup.'
}

let i = Object.keys(DATA).filter(k => k.startsWith("ing")).map(o => DATA[o]);

console.log(i);

Instead of adding properties to your object as ingredient1, ingredient2, etc.. why don't you just use an array called ingredients?

Something like this demonstrates, at a very high level, how to accomplish what you are wanting.

var RECIPE = {
  name: "",
  ingredients: [],
  method: "",
}

$("#add-ingredient").on('click', event => {
  let ingredient = $("#ingredient").val();
  if (ingredient) {
    RECIPE.ingredients.push(ingredient);
    $("#ingredient-list").append(`<li>${ingredient}</li>`);
    $("#ingredient").val("");
  } else {
    alert("Please enter ingredient first");
  }
});

$("#submit").on('click', event => {
  let name = $("#recipe-name").val();
  let method = $("#recipe-method").val();
  let empties = [];
  if (name) { RECIPE.name = name; } 
  else { empties.push("recipe name"); }
  if (method) { RECIPE.method = method; } 
  else { empties.push("recipe method"); }
  if (RECIPE.ingredients.length <= 0) { empties.push("ingredients"); }
  if (empties.length) { alert(`Please fill out ${empties.join(" and ")}`); } 
  else { alert("This is what we would post:\r\n\r\n" + JSON.stringify(RECIPE,null,2)); }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="recipe-name" type="text" placeholder="Enter Recipe Name"/>
<br />
<textarea id="recipe-method" placeholder="Enter Recipe Method"></textarea>
<br />
<input id="ingredient" type="text" placeholder="Enter Ingredient" />
<button id="add-ingredient">Add Ingredient</button>
<ul id="ingredient-list"></ul>
<br /><hr />
<button id="submit">Submit Recipe</button>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.