how to group products to display in page - asp.net-mvc

I would like to ask for your help to make some code modifications to some code I am working on.
Currently the code displays 'x' number of products on a page in the fashion of:
box 1 (product id 140)
price $10
box 2 (product id 140)
price $10
box 3 (product id 143) - different Id
price $20
Order Sub-Total: $40.00
CODE
<div class="yourorder">
#foreach (var prod in Model.Products)
{
for (var i = 0; i < prod.Count; i++)
{
<div data-index="#i" data-type="product" data-id="#prod.ID" data-multiple="#prod.Multiple" data-multiplecatid="#prod.MultipleCategoryID">
#{Html.RenderAction("ShoppingCartProduct", "ShoppingCart", new { id = prod.ID });}
<div class="shippingArea">
<div class="shippingPickerLabel">Use this shipping address:</div>
<div>
<select class="shippingPicker" data-type="prod" data-id="#(prod.ID)">
<option value="-1">Add New</option>
</select>
</div>
</div>
</div>
<br />
<hr />
}
}
<div class="totals" style="font-weight:normal;margin-top:20px;">
#if (Model.TotalPriceFrom == Model.TotalPriceTo)
{
<div><strong>Order Sub-Total:</strong> $<span class="grandTotal">#Model.TotalPriceTo.ToString("0.00")</span></div>
}
else
{
<div><strong>Order Sub-Total:</strong> $<span class="grandTotal">#Model.TotalPriceFrom.ToString("0.00") - $#Model.TotalPriceTo.ToString("0.00")</span></div>
}
</div>
</div>
The Price is calculated in a PartialView named "ShoppingCartProduct". the code that does that:
CODE
<div class="shoppingCartPrice">
#if (Model.Prod.HasRange && Model.Prod.WeightFrom.HasValue && Model.Prod.WeightTo.HasValue)
{
if (Model.Prod.UnitID.HasValue)
{
<div>$#Model.Prod.Price.Value.ToString("0.00") per #Model.UnitName</div>
}
<div>Price: $#((Model.Prod.Price.Value * Model.Prod.WeightFrom.Value).ToString("0.00")) - $#((Model.Prod.Price.Value * Model.Prod.WeightTo.Value).ToString("0.00"))</div>
<div style="display:none;" class="minPrice">#((Model.Prod.Price.Value * Model.Prod.WeightFrom.Value).ToString("0.00"))</div>
<div style="display:none;" class="maxPrice">#((Model.Prod.Price.Value * Model.Prod.WeightTo.Value).ToString("0.00"))</div>
}
else
{
<div>Price: $#Model.Prod.Price.Value.ToString("0.00")</div>
<div style="display:none;" class="minPrice">#((Model.Prod.Price.Value).ToString("0.00"))</div>
<div style="display:none;" class="maxPrice">#((Model.Prod.Price.Value).ToString("0.00"))</div>
}
</div>
I need help in making it calculate different and display different too.
Like:
box 1 qty 2 (products with same id)
price each $10
sub-total $20.00
box 2 qty 1 (products with different id)
price $20
sub-total $20.00
Order Sub-Total: $40.00

I had the same problem not that long ago. We solved it by using a cookie.
The products were loaded from an SQL server, but for each product we increased it's ID with 1, so the product ID ranged from 1 to X. With the thought of a huge webshop, we made this number 4 charters long, so: 0001.
Then we just read the product ID into a string and added the quantity behind that, so a string of 6 chars. Then:
int count = Response.Cookie["producten"].Length / 6
for (int i = 0; i < count; i++)
{
//paste your string decoder here to sort out the product ID and the quantity
Panel p = new Panel();
p.ID = i;
p.CssClass = [your cssclass]
//paste here code for the rest of your div
cartdiv.Controls.Add(p);
}
By doing this, the code generates a div (or anything else you'd want) with unique ID's and the same Class, for each product.

Related

ASP.NET MVC 5 always display the first row in the table [duplicate]

this is a tricky one to explain, so I'll try bullet pointing.
Issue:
Dynamic rows (collection) available to user on View (add/delete)
User deletes row and saves (POST)
Collection passed back to controller with non-sequential indices
Stepping through code, everything looks fine, collection items, indices etc.
Once the page is rendered, items are not displaying correctly - They are all out by 1 and therefore duplicating the top item at the new 0 location.
What I've found:
This happens ONLY when using the HTML Helpers in Razor code.
If I use the traditional <input> elements (not ideal), it works fine.
Question:
Has anyone ever run into this issue before? Or does anyone know why this is happening, or what I'm doing wrong?
Please check out my code below and thanks for checking my question!
Controller:
[HttpGet]
public ActionResult Index()
{
List<Car> cars = new List<Car>
{
new Car { ID = 1, Make = "BMW 1", Model = "325" },
new Car { ID = 2, Make = "Land Rover 2", Model = "Range Rover" },
new Car { ID = 3, Make = "Audi 3", Model = "A3" },
new Car { ID = 4, Make = "Honda 4", Model = "Civic" }
};
CarModel model = new CarModel();
model.Cars = cars;
return View(model);
}
[HttpPost]
public ActionResult Index(CarModel model)
{
// This is for debugging purposes only
List<Car> savedCars = model.Cars;
return View(model);
}
Index.cshtml:
As you can see, I have "Make" and "Actual Make" inputs. One being a HTML Helper and the other a traditional HTML Input, respectively.
#using (Html.BeginForm())
{
<div class="col-md-4">
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div id="car-row-#i" class="form-group row">
<br />
<hr />
<label class="control-label">Make (#i)</label>
#Html.TextBoxFor(m => m.Cars[i].Make, new { #id = "car-make-" + i, #class = "form-control" })
<label class="control-label">Actual Make</label>
<input class="form-control" id="car-make-#i" name="Cars[#i].Make" type="text" value="#Model.Cars[i].Make" />
<div>
<input type="hidden" name="Cars.Index" value="#i" />
</div>
<br />
<button id="delete-btn-#i" type="button" class="btn btn-sm btn-danger" onclick="DeleteCarRow(#i)">Delete Entry</button>
</div>
}
<div class="form-group">
<input type="submit" class="btn btn-sm btn-success" value="Submit" />
</div>
</div>
}
Javascript Delete Function
function DeleteCarRow(id) {
$("#car-row-" + id).remove();
}
What's happening in the UI:
Step 1 (delete row)
Step 2 (Submit form)
Step 3 (results)
The reason for this behavior is that the HtmlHelper methods use the value from ModelState (if one exists) to set the value attribute rather that the actual model value. The reason for this behavior is explained in the answer to TextBoxFor displaying initial value, not the value updated from code.
In your case, when you submit, the following values are added to ModelState
Cars[1].Make: Land Rover 2
Cars[2].Make: Audi 3
Cars[3].Make: Honda 4
Note that there is no value for Cars[0].Make because you deleted the first item in the view.
When you return the view, the collection now contains
Cars[0].Make: Land Rover 2
Cars[1].Make: Audi 3
Cars[2].Make: Honda 4
So in the first iteration of the loop, the TextBoxFor() method checks ModelState for a match, does not find one, and generates value="Land Rover 2" (i.e. the model value) and your manual input also reads the model value and sets value="Land Rover 2"
In the second iteration, the TextBoxFor() does find a match for Cars[1]Make in ModelState so it sets value="Land Rover 2" and manual inputs reads the model value and sets value="Audi 3".
I'm assuming this question is just to explain the behavior (in reality, you would save the data and then redirect to the GET method to display the new list), but you can generate the correct output when you return the view by calling ModelState.Clear() which will clear all ModelState values so that the TextBoxFor() generates the value attribute based on the model value.
Side note:You view contains a lot of bad practice, including polluting your markup with behavior (use Unobtrusive JavaScript), creating label element that do not behave as labels (clicking on them will not set focus to the associated control), unnecessary use of <br/> elements (use css to style your elements with margins etc) and unnecessary use of new { #id = "car-make-" + i }. The code in your loop can be
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div class="form-group row">
<hr />
#Html.LabelFor(m => m.Cars[i].Make, "Make (#i)")
#Html.TextBoxFor(m => m.Cars[i].Make, new { #class = "form-control" })
....
<input type="hidden" name="Cars.Index" value="#i" />
<button type="button" class="btn btn-sm btn-danger delete">Delete Entry</button>
</div>
}
$('.delete').click(function() {
$(this).closest('.form-group').remove();
}

TextBoxFor displaying the wrong name? [duplicate]

this is a tricky one to explain, so I'll try bullet pointing.
Issue:
Dynamic rows (collection) available to user on View (add/delete)
User deletes row and saves (POST)
Collection passed back to controller with non-sequential indices
Stepping through code, everything looks fine, collection items, indices etc.
Once the page is rendered, items are not displaying correctly - They are all out by 1 and therefore duplicating the top item at the new 0 location.
What I've found:
This happens ONLY when using the HTML Helpers in Razor code.
If I use the traditional <input> elements (not ideal), it works fine.
Question:
Has anyone ever run into this issue before? Or does anyone know why this is happening, or what I'm doing wrong?
Please check out my code below and thanks for checking my question!
Controller:
[HttpGet]
public ActionResult Index()
{
List<Car> cars = new List<Car>
{
new Car { ID = 1, Make = "BMW 1", Model = "325" },
new Car { ID = 2, Make = "Land Rover 2", Model = "Range Rover" },
new Car { ID = 3, Make = "Audi 3", Model = "A3" },
new Car { ID = 4, Make = "Honda 4", Model = "Civic" }
};
CarModel model = new CarModel();
model.Cars = cars;
return View(model);
}
[HttpPost]
public ActionResult Index(CarModel model)
{
// This is for debugging purposes only
List<Car> savedCars = model.Cars;
return View(model);
}
Index.cshtml:
As you can see, I have "Make" and "Actual Make" inputs. One being a HTML Helper and the other a traditional HTML Input, respectively.
#using (Html.BeginForm())
{
<div class="col-md-4">
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div id="car-row-#i" class="form-group row">
<br />
<hr />
<label class="control-label">Make (#i)</label>
#Html.TextBoxFor(m => m.Cars[i].Make, new { #id = "car-make-" + i, #class = "form-control" })
<label class="control-label">Actual Make</label>
<input class="form-control" id="car-make-#i" name="Cars[#i].Make" type="text" value="#Model.Cars[i].Make" />
<div>
<input type="hidden" name="Cars.Index" value="#i" />
</div>
<br />
<button id="delete-btn-#i" type="button" class="btn btn-sm btn-danger" onclick="DeleteCarRow(#i)">Delete Entry</button>
</div>
}
<div class="form-group">
<input type="submit" class="btn btn-sm btn-success" value="Submit" />
</div>
</div>
}
Javascript Delete Function
function DeleteCarRow(id) {
$("#car-row-" + id).remove();
}
What's happening in the UI:
Step 1 (delete row)
Step 2 (Submit form)
Step 3 (results)
The reason for this behavior is that the HtmlHelper methods use the value from ModelState (if one exists) to set the value attribute rather that the actual model value. The reason for this behavior is explained in the answer to TextBoxFor displaying initial value, not the value updated from code.
In your case, when you submit, the following values are added to ModelState
Cars[1].Make: Land Rover 2
Cars[2].Make: Audi 3
Cars[3].Make: Honda 4
Note that there is no value for Cars[0].Make because you deleted the first item in the view.
When you return the view, the collection now contains
Cars[0].Make: Land Rover 2
Cars[1].Make: Audi 3
Cars[2].Make: Honda 4
So in the first iteration of the loop, the TextBoxFor() method checks ModelState for a match, does not find one, and generates value="Land Rover 2" (i.e. the model value) and your manual input also reads the model value and sets value="Land Rover 2"
In the second iteration, the TextBoxFor() does find a match for Cars[1]Make in ModelState so it sets value="Land Rover 2" and manual inputs reads the model value and sets value="Audi 3".
I'm assuming this question is just to explain the behavior (in reality, you would save the data and then redirect to the GET method to display the new list), but you can generate the correct output when you return the view by calling ModelState.Clear() which will clear all ModelState values so that the TextBoxFor() generates the value attribute based on the model value.
Side note:You view contains a lot of bad practice, including polluting your markup with behavior (use Unobtrusive JavaScript), creating label element that do not behave as labels (clicking on them will not set focus to the associated control), unnecessary use of <br/> elements (use css to style your elements with margins etc) and unnecessary use of new { #id = "car-make-" + i }. The code in your loop can be
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div class="form-group row">
<hr />
#Html.LabelFor(m => m.Cars[i].Make, "Make (#i)")
#Html.TextBoxFor(m => m.Cars[i].Make, new { #class = "form-control" })
....
<input type="hidden" name="Cars.Index" value="#i" />
<button type="button" class="btn btn-sm btn-danger delete">Delete Entry</button>
</div>
}
$('.delete').click(function() {
$(this).closest('.form-group').remove();
}

how to loop and group items that have same id

I need to be able to display each product by id.
For example if I have 3 products with same id: count the quantity and just display it once.
When the id is different display the other product.
product 1 qty 2 price 10 subtotal 20
product 2 qty 1 price 20 subtotal 20
This is the current code.
<div class="yourorder">
#foreach (var group in Model.Products.GroupBy(p => p.ID))
{
foreach (var prod in group)
{
<div data-type="product" data-id="#group.First().ID" data-multiple="#group.First().Multiple" data-multiplecatid="#group.First().MultipleCategoryID" style="overflow:hidden;"></div>
#{Html.RenderAction("ShoppingCartProduct", "ShoppingCart", new { id = group.First().ID });}
</div>
<br />
<hr />
}
}
</div
The proposed solution by Joce gets me the grouped boxes and the quantity. I think I can get quantity using: #group.First().Count - for each box
If a box has the same 5 items, I would like to be able to show the item's unit price, and the subtotal for that box. I know that calculation is unit price * quantity
But I do not know how can I calculate each product sub-total if the price is calculated in another PartialView named "ShoppingCartProduct". This is the line that call that view that calculates the price:
#{Html.RenderAction("ShoppingCartProduct", "ShoppingCart", new { id = group.First().ID});}
The contents of ShoppingCartProduct:
<div class="shoppingCartPrice">
#if (Model.Prod.HasRange && Model.Prod.WeightFrom.HasValue && Model.Prod.WeightTo.HasValue)
{
<div>Price: $#Model.Prod.Price.Value.ToString("0.00")</div>
}
Is it possible that someone can guide me how to get that calculation so my output is:
product 1 qty 2 price 10 subtotal 20 (qty * unit price)
product 2 qty 1 price 20 subtotal 20 (qty * unit price)
Many thanks.
You could use the extension method of IEnumerable GroupBy and then loop in each group.
<div class="yourorder">
#foreach (var group in Model.Products.GroupBy(p => p.ID))
{
foreach (var prod in group)
{
//Calculate subtotal here
}
<div data-type="product" data-id="#group.First().ID" data-multiple="#group.First().Multiple" data-multiplecatid="#group.First().MultipleCategoryID" style="overflow:hidden;"></div>
<br />
<div class="fancyDivider"></div>
}
</div>
Edit:
The function GroupBy group each items that correspond to the grouping. In your example, it's the ID.
That is why you have to:
loop through each group and calculate the subtotal if there is more than
one item in the group
show a div for each group.
To calculate the subtotal, you could simply get the product unit price by getting the first product of the group and mutiply it by the number of product:
var subtotal = group.First().UnitPrice * group.Count()
Now, to be able to calculate the subtotal in your PartialView you need 2 things:
The product
The count of this product
So, you could do something like this:
#{Html.RenderAction("ShoppingCartProduct", "ShoppingCart",
new { id = group.First().ID, Count = group.Count()});}

Razor Ternary Operator to render Html Tags (ASP.NET MVC)

I'm trying to apply a responsive design to my asp.net mvc 4 application. I want to loop my model and render 3 items per line. Each line shall be wrapped in a div. The result should look something like this:
<div class='ResponsiveWrapper'>
<div>
<!-- item1 -->
</div>
<div>
<!-- item2 -->
</div>
<div>
<!-- item3 -->
</div>
</div>
<div class='ResponsiveWrapper'>
<div>
<!-- item4 -->
...
In order to do so, I'm trying to use ternary operators:
#{ var i = 0; }
#foreach (var item in Model)
{
#Html.Raw(i == 0 ? Html.Encode("<div class='section group'>") : "")
<div>
//Responsive Content comes here
</div>
#Html.Raw(i == 2 ? Html.Encode("</div>") : "")
#(i<3 ? i++ : i=0)
}
Now I have 2 Problems:
The HTML Tags which the ternary operators should render come in plain text. I tried different combinations of #Html.Raw and #Html.Encode and Strings, but it nothing worked for me
It seems like the last Ternary Operator renders the current value of the variable i. How can I prevent this?
Additional information/Code explanation
The logic already works fine:
The i Variable is the count variable.
If i = 0 I first render the start <div> tag of the wrapper and than I render the current model.item
If i = 1 I only render the current model.item
If i = 2 I first render the current model.item and than the </div> end tag
Thank you
UPDATE
Both, MajoB's and Chris Pratt's approaches basically work. Since MajoB's solution was more detailed, I went with that one.
However, I had to make some modifications in order to get it to work:
At the Controller, I had to assure, that a IList is being returned, rather than a IEnumberable
public ActionResult Index()
{
return View(db.leModel.ToList());
}
At the View, I had to change the signature (like 1., IList instead of IEnumerable)
#model IList<leProject.Models.leModel>
Various modifications at the razor code (otherwise it would throw me exceptions)
Final Code:
<div class="ResponsiveWrapper">
#for (var i = 0; i < Model.Count; i++)
{
// the lambda expression modelItem => item.leProperty did not work for some reason. So I had to replace the item with Model[i], which means, the following line is not neccessary
{ var item = Model[i]; }
<div>
#Html.DisplayFor(modelItem => Model[i].leProperty)
</div>
if ((i + 1) % 3 == 0 || i == (Model.Count - 1))
{
#:</div>
if (Model.Count + 1 - i >= 3)
{
#:<div class="ResponsiveWrapper">
}
}
}
Thank you guys :)
Solution without the wrapper div:
#for(var i = 0; i < Model.Count; i++)
{
#{ var item = Model[i]; }
<div style="float:left;">
<!-- item1 -->
</div>
#if((i+1) % 3 == 0)
{
<div style="clear:both;"></div>
}
}
Solution with wrapper:
<div class="ResponsiveWrapper">
#for(var i = 0; i < Model.Count; i++)
{
#{ var item = Model[i]; }
<div>
<!-- item1 -->
</div>
#if((i+1) % 3 == 0 || i == (Model.Count-1)) // in case you have for example 7 items in your list
{
#:</div> <!-- end ResponsiveWrapper -->
#if (i != Model.Count-1)
{
#:<div class='ResponsiveWrapper'>
}
}
}
Here's the usual way to handle this:
<div class="ResponsiveWrapper">
#for (var i = 0; i < Model.Count; i++)
{
<div>
<!-- item -->
</div>
#if ((i + 1) % 3 == 0)
{
#:</div><div class="ResponsiveWrapper">
}
}
</div>
Every third item, the wrapper div is closed and opened again. The #: prevents Razor from trying to parse this line, so it doesn't throw erroneous syntax errors.

C# foreach within foreach, display 3 item in each item. carousel

I am using asp.net MVC 4 for my project, and I have a Model.
and I am using Bootstrap Carousel to show products for my Index Page (see the image below)
Now my question is: I want to show 3 Products in each slide item.
should I write a ViewModel for this?
<div class="carousel slide>
#for (int i = 0; i <= Model.Count()/3; i++ ) <!-- this works well, paging -->
{
<div class="item #if(i==0){<text>active</text>}">
#foreach(var well in Model)
{
<div class="span4">
<!-- some html here -->
</div>
}
</div>
}
</div>
Your inner foreach is iterating over the entire Model collection - you'll need to restrict it to just the relevant three items.
I'm guessing you want something like:
<div class="carousel slide>
#for (int i = 0; i <= Model.Count()/3; i++ ) <!-- this works well, paging -->
{
<div class="item #if(i==0){<text>active</text>}">
#foreach(var well in Model.Skip(i*3).Take(3))
{
<div class="span4">
<!-- some html here -->
</div>
}
</div>
}
</div>
One way to think about this problem is to split the original collection into groups for each 3 sequential elements. Fortunately, you can use the GroupBy LINQ method using the "element index divided by 3" as key. IMO, the advantage of this solution is that it expresses more clearly your intent and has better performance than reiterating the collection with Skip(x * 3).Take(3) in the inner loop.
<div class="carousel slide>
#foreach (var group in Model.Select((x, index) => new { element = x, index }).GroupBy(x => x.index / 3, x => x.element))
{
<div class="item #if( group.Key == 0) {<text>active</text>}">
#foreach(var well in group)
{
<div class="span4">
<!-- some html here -->
</div>
}
</div>
}
</div>
I would even change the model type to ILookup<int, TElement> and perform the grouping in the controller.

Resources