13

I can't find or figure out how to take a list of items (cupcakes) and display them in razor with a quantity field.

What is happening is I am not able to get the values for each cupcake quantity in the list. Can you do textbox arrays in Razor?

VIEW

<div class="form-group">
    <label>Cupcakes</label>
    @foreach (var cupcake in Model.CupcakeList)
    {
        @Html.TextBox("CupcakeQuantities", cupcake.Id)  @cupcake.Name <br/>
    }
</div>

MODEL

public List<Cupcake> CupcakeList { get; set; }
public List<int> CupcakeQuantities { get; set; }

CONTROLLER

public ActionResult Create()
{
    var model = new PartyBookingModel()
    {
        CupcakeList = db.Cupcakes.ToList(),
        CupcakeQuantities = new List<int>()
    };

    return View(model);
}

CUPCAKE (ENTITY)

public class Cupcake
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal PerDozen { get; set; }
}
devfunkd
  • 3,164
  • 12
  • 45
  • 73
  • Your question seems to be inconsistent, you're showing CupcakeQuantites in your model, but in controller you're filling CupcakeSelection array. Could you please specify where is your quantity field located? – Oleksii Aza Jun 10 '14 at 21:10
  • Sorry, typo. Should be the CupcakeQuantities. Question updated. – devfunkd Jun 10 '14 at 21:15

2 Answers2

39

You have to use an index, rather than foreach for it to work.

@for (int i = 0; i < Model.CupcakeList.Count; i++)
{
    @Html.TextBoxFor(x=>Model.CupcakeQuantities[i]) @Model.CupcakeList[i].Name <br/>
}

This will create sequentially named+number entries that will be recombined back into the model on post back.

I realise this may seem like "why doesn't foreach work?", but with foreach there is not enough reflected information available to TextBoxFor (as it is just a single object), whereas the array index is extracted by reflection from the Model.CupcakeQuantities[i] expression.

The receiving controller method should take the same as the model passed to the view:

e.g.

[HttpPost]
public ActionResult(PartyBookingModel model)
iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
  • Yes, that is what I was trying to figure out. But I don't know how to "capture" the values on post in the controller. Can your answer show that please? – devfunkd Jun 10 '14 at 21:17
  • Added example of receiving controller method. When you use the scaffolding wizards for EF models + create, you will usually get a good example to build on. – iCollect.it Ltd Jun 10 '14 at 22:08
  • Just realised I have put the wrong model property in the array... Now fixed to be your quantities :) It would be cleaner if the CupCake model contained a quantity property, rather than have 2 lists. e.g. adding a `CupCakeCreate` model that derives from `CupCake` and adds a `Qty` property is one option. – iCollect.it Ltd Jun 10 '14 at 22:13
  • I am not sure how to implement a CupCakeCreate model into the current model/view/controller. Your thought makes sense but I am a bit of a noob on this :) – devfunkd Jun 10 '14 at 22:34
  • My suggestion is not really applicable here as you are only editing the qty, but basically ViewModels are just C# classes with properties so you can always extend other models with inheritance. In this instance stick with the list of ints. Hope this helped. The key is the indexing of the properties. Cheers – iCollect.it Ltd Jun 10 '14 at 22:38
  • changing it to x=>Model.CupcakeQuantities[i] gives an "Index out of range error. Also shouldn't i < Model.CupcakeList have .Count on the end? – devfunkd Jun 10 '14 at 22:39
  • 1
    Yes my bad. Typed all that from memory. Add basic .Count :) – iCollect.it Ltd Jun 10 '14 at 22:46
2

Try it this way:

view:

@for (int i = 0; i < Model.Count; i++)
{
    @Html.HiddenFor(x=>Model[i].Id) @Model[i].Name  
    @Html.TextBoxFor(x => Model[i].Quantity) <br/>
}

model:

public class CupcakeViewModel
{
   public int Id {get;set;}
   public string Name {get;set;}
   public int Quantity {get;set;}   
}

controller:

public ActionResult Create()
{
    var model = db.Cupcakes.Select(c => new CupcakeViewModel {
                                                Id = c.Id,
                                                Name = c.Name,
                                                Quantity = 0 
                           })
                           .ToList();

    return View(model);
}

[HttpPost]
public ActionResult Create(CupcakeViewModel[] cakes)
{
     //Save choosen cakes
}
Oleksii Aza
  • 5,368
  • 28
  • 35
  • I am getting textboxes with "ProjectTest.Domain.Cupcake" text inside of them. – devfunkd Jun 10 '14 at 21:23
  • @Html.TextBoxFor(x=>Model.CupcakeList[i].Id) puts the Id in the value of the textbox, I need it to be empty as the user is entering in how many of each they want. I changed it to @Html.TextBoxFor(x=>Model.CupcakeList[i].Id, null) but it won't let me assign an int (Id) to a string. :( – devfunkd Jun 10 '14 at 21:36
  • Which property do you want to allow user to change? If it's Id - why is it should be empty? for geting it empty you should pass object with `Value = ""` like this: `@Html.TextBoxFor(x=>Model.CupcakeList[i].Id, new { Value = "" })` – Oleksii Aza Jun 10 '14 at 21:49
  • The way I am trying to make the form is 1) Call database, get list of cupcakes 2) display cupcakes with qty field for user to enter how many of each they want 3) Get values and build a order from each qty. – devfunkd Jun 10 '14 at 22:12
  • Thanks, actually I copied your code as template ))) – Oleksii Aza Jun 11 '14 at 07:30
  • Well in that case :P :) – iCollect.it Ltd Jun 11 '14 at 10:22