简体   繁体   中英

Dynamically populating modal in express app

I'm writing an express app that will allow someone to create a recipe (cooking recipe), which is then saved to mongodb. When they open the app they can see the list of recipes, edit them, delete them etc.

The recipes are being stored in mongo like this

> db.recipes.find()
{ "_id" : ObjectId("542fc7d635ebd51b8afd507b"), "name" : "chili", 
  "ingredients" : [ 
    { "ingredient" :     "mince", "quantity" : "500", "unit" : "g" }, 
    { "ingredient" : "cumin", "quantity" : "1", "unit" : "tsp" }, 
    { "ingredient" : "paprika", "quantity" : "2", "unit" : "tsp" } ] 
}
{ "_id" : ObjectId("542fccbb6de6181f8da58346"), "name" : "test", 
  "ingredients" : [ 
    { "ingredient" : "stuff", "quantity" : "10", "unit" : "g" }, 
    { "ingredient" : "stuff", "quantity" : "10", "unit" : "g" } ] 
}

The db is read from whenever the 'recipes' page is viewed

router.get('/recipes', function(req, res) {
  db.collection('recipes').find().toArray(function(err, results) {
    if(results.length > 0) {
      recipes = results;

      for(var i = 0; i < results.length; i++) {
        recipeList[i] = results[i].name;
      }

      res.render('recipes', {recipes: recipes, recipeList: recipeList});
    } else {
      recipes = [];

      res.render('recipes', {recipes: recipes, recipeList: recipeList});
    }

  });
});

and then in recipes.jade the recipes are listed as follows

table.table
  thead
    tr
      th Name
  tbody
  - for(var i = 0; i < recipes.length; i++)
    tr
      td= recipes[i].name

食谱清单

When one of these recipes is clicked on a modal is launched as follows

script("type=text/javascript").
  $('td').on('click', function() {
    var recipe = $(this).clone().children().remove().end().text();

    $('#myModalLabel.modal-title').html(recipe);
    $('#myModal').modal();
  });

which should list the ingredients

在此处输入图片说明

I'm having difficulty figuring out how to display the ingredients for a recipe when it is clicked on. I tried to figure it out by adding this code in the modal, which I know is wrong but I don't know what to change.

p ingredients
- for(var j = 0; j < recipes.length; j++)
  ul
    li= recipes[j].ingredients[0].ingredient

在此处输入图片说明



Following nanoamps's answer, and modifying slightly, I now have a separate template file singleRecipe.jade

- var theIngredients = recipes[recipeIndex].ingredients;
p ingredients
ul
- for(var j = 0; j < theIngredients.length; j++)
  li= theIngredients[j].quantity + theIngredients[j].unit + ' ' + theIngredients[j].ingredient

so when a recipe is clicked, this handler gets the name and index

$('td').on('click', function() {
  var recipeIndex = $(this).parent().index();
  var recipeName = $(this).text();
  $('#myModal .modal-body').load('/singleRecipe/' + recipeIndex, function() {
    $('#myModal').modal();
  });
});

and the route handler in app.js is now

router.get('/singleRecipe/:d', function(req, res) {
  db.collection('recipes').find().toArray(function(err, results) {
    if(results.length > 0) {
      recipes = results;
      for(var i = 0; i < results.length; i++) {
        recipeList[i] = results[i].name;
      }
      res.render('singleRecipe', {recipes: recipes, recipeIndex: req.params.d});
    } else {
      recipes = [];
      res.render('recipes', {recipes: recipes, recipeList: recipeList});
    }
  });
});

I know this is still quite messy, but it's working as shown below, so now I can just start refactoring.

在此处输入图片说明

Your current display code is looping through all the recipes (with the for j loop) and then printing the first ingredient of each (with the .ingredient[0] reference).

Start by finding out which recipe you've chosen in your click handler with something like:

var recipeIndex = $(this).index();
var recipeName = $(this).text();

Then you can use the recipe index to pick the right one from your recipes array. You could build a massive list somewhere in your initial recipes.jade page, hide it with CSS and then clone into the modal when required, but that's not very scalable. You'd be better off putting the rendering of the individual recipe details into a separate template with its own route:

- var theIngredients = recipes[recipeIndex].ingredients;
p ingredients
ul
- for(var j = 0; j < theIngredients.length; j++)
  li= theIngredients[j].quantity + theIngredients[j].unit + ' ' + theIngredients[j].ingredient

This will build a list of ingredients for the given index, looping to create each list item with the concatenated ingredient details.

Then pull that into your modal using a separate GET request in the click handler with something like:

$('#myModal .modal-body').load('/singleRecipe?index=' + recipeIndex, function() {
  $('#myModal').modal();
});

which requests your new template, passing the index in the URL.

Although that should work, any changes to the collection will bring up the wrong recipe. Build on that by instead rendering MongoDB's unique _id identifier somewhere in the initial table rows (eg. as a data-id attribute) and using that as the thing you lookup and pass through your click handler and request. Then the single recipe route can lookup the single record in the database instead of loading the whole collection. It'll be faster, less data down the wire, and less brittle.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM