Is it possible to say: if the image is on the left the next one should be on the right, repeat until done?
Pseudo code:
#foreach (var item in Model.Items)
{
if (previous class=imageLeft)
{
<div class="imageRight">Right Image</div>
<div>Left Content</div>
}
else
{
<div class="imageLeft">Left Image</div>
<div>Right Content</div>
}
}
Yes, of course. You're pseudo code is almost there. Just use a variable to hold the value and remember to update its value in each iteration:
#int c = 0;
#foreach (var item in Model.Items)
{
if (c == 0)
{
<div class="imageRight">Right Image</div>
<div>Left Content</div>
c = 1;
}
else
{
<div class="imageLeft">Left Image</div>
<div>Right Content</div>
c = 0;
}
}
alternatively, you can use #foreach (int i = 0; i < Model.Items.Count; i++) loop and then use i % 2 to get the even/odd row numbers and apply the class as you like.
Personally, I would create a helper for this. The helper keeps the code clean and makes inner loops much easier to handle. Starts to save you some time once your applications grow in size, especially if you copy a working example. Here's mine:
public class LooperHelper
{
private int Iterations { get; set; } = 0;
public void AddTick() {
Iterations++;
}
public int GetTicks()
{
return Iterations;
}
public bool DivisibleBy(int divisor)
{
return (Iterations % divisor == 0);
}
public string ConditionalOutput(string stringoutput, int divisor, bool reverse = false)
{
return ((Iterations % divisor == 0) == !reverse ? stringoutput : "");
}
}
This allows you to do nested conditional formatting based on the number of iterations:
var outerloophelper = new LooperHelper();
var innerloophelper = new LooperHelper();
foreach (var product in products)
{
outerloophelper.AddTick();
<div class="row #(outerloophelper.ConditionalOuput("alternative", 2, true))">
#foreach (var subproduct in product.SubProducts)
{
innerloophelper.AddTick();
<div class="4u#(innerloophelper.ConditionalOutput("$", 3)) 12u$(small)">
#subproduct.ToString()
</div>
}
</div>
}
In the above example, every second product is decorated with the class "alternative" (where the first is marked alternative as reverse is set true). The nested sub-product items are further decorated with the addition of a $ sign to the class to denote the change of the row for every third column. But you can do also other nifty things, such as making sure that a hr-tag is added in between, but not after the final looped element, like in the following example:
#if (!outerloophelper.DivisibleBy(products.Count())) { <hr />}
In short, when initialising the helper, it begins with 0 and positioning the AddTick() method within the code allows you to adjust whether iterations begin with a one or a zero.
GetTicks() is great if you want to e.g. number your results,
DivisibleBy() returns a boolean result that you can use it in your own logic, and
ConditionalOuput() will print out the text should the statement hold. Use reverse so the output is when the row is not divisible.
Related
I am working on displaying an amount of reviews after a rating, the code I wrote works, but I realized it ends up displaying the command for each review, so if there are two reviews I see "1 Review 1 Review." I've been working on this for a while and am still a bit new to MVC, C#, etc, and cannot seem to figure out how to change this to a for loop that will count the ReviewCount and display it as a single line with 1 or more "reviews" indicated. I tried spreading this out but then the local variable can't be called, and that's where I am stuck!
#foreach (var review in item.Campaign.Reviews)
{
int ReviewCount = 0;
++ReviewCount;
if (ReviewCount > 1)
{
#Html.Raw(ReviewCount)
#Html.Raw(" Reviews")
}
else
{
#Html.Raw(ReviewCount)
#Html.Raw(" Review")
}
}
If you just want to count reviews, just use the LINQ Count() method:
#{ int ReviewCount = item.Campaign.Reviews.Count(); }
#Html.Raw(ReviewCount)
#if (ReviewCount != 1) {
#Html.Raw(" Reviews")
}
else {
#Html.Raw(" Review")
}
Also, be DRY.
I have a model with 34 numbered properties in it as shown below
Public Class ViewModel
{
public string RatingCategory01 { get; set; }
public string RatingCategory02 { get; set; }
public string RatingCategory03 { get; set; }
//...and so on until category #34
}
Rather than code an input for each category in Razor Pages, I would like to use a loop to iterate through all the categories and generate the appropriate control groups. I have tried the code below:
<tbody>
#for (var i = 1; i < 35; i++)
{
string n;
#if (i > 0 && i < 10)
{
n = "RatingCategory0" + i.ToString();
}
else
{
n = "RatingCateogry" + i.ToString();
}
<tr>
<td>
<label asp-for="#string.Format("RatingCategory" + n)" class="control-label"></label>
</td>
<td>
<select asp-for="#string.Format("RatingCategory" + n)" asp-items="Model.CategoryRatingSelectList">
<option value="">Select</option>
</select>
</td>
<td>
<input asp-for="#string.Format("RemedialTime" + n)" class="form-control" />
</td>
</tr>
}
</tbody>
When I build the project and navigate to the page, I get this error:
InvalidOperationException: Templates can be used only with field
access, property access, single-dimension array index, or
single-parameter custom indexer expressions.
I'm not sure if I am on the right track here. I would really like to create a loop to generate these inputs so make future maintenance and changes easier. It's probably pretty obvious from my code/question that I am pretty new to this, so any help is appreciated.
EDIT TO ADD SOLUTION:
I used the solution provided by Ed Plunkett which I have checked below. I altered it a bit and ended up creating a new class called 'Rating' because I found that in practice I needed a more complex object. Inside my view is now
public List<Rating> Ratings = { get; set; }
In the controller, I use a loop to add as many empty ratings as I need to the list depending on the number I need.
for (var i = 0; i < 34; i++)
{
vm.Ratings.Add(new Rating());
}
Though this will likely be updated to use something other than a hard-coded number as the application evolves.
Finally, I used a loop in the view to create a group of controls for every Rating in my List. In this case it is a TableRow containing different controls in different columns:
#for (var i = 0; i < Model.Ratings.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(model => model.Ratings[i].Category)
</td>
<td>
<div class="form-group">
<select asp-for="Ratings[i].RatingValue" asp-items="Model.CategoryRatingSelectList">
<option value="">Select</option>
</select>
</div>
</td>
<td>
<input asp-for="Ratings[i].RemediationMinutes" class="form-control" />
</td>
</tr>
}
I've found that the data in this group of inputs can be bound as a List by simply including
List<Rating> Ratings
in the parameters on whichever method runs when the form is submitted.
This is what you want instead of those 34 properties and their implied 34 RemedialTime siblings:
public List<String> RatingCategory { get; set; } = new List<String>();
public List<String> RemedialTime { get; set; } = new List<String>();
If you have 34 of something and the names differ only by an index number, that's a collection, not 34 distinct properties with sequentially numbered names. Then you can enumerate the 34 items with a foreach loop, or index them individually as RatingCategory[0] through RatingCategory[33]. In C#, collection indexes start at zero, so the first one is 0 and the thirty-fourth one is 33. You get used to it.
You should also look up what String.Format() does. String.Format("Foo" + 1) is exactly the same as "Foo" + 1.
You could convert your model class to dictionary;
var viewModel = new ViewModel()
{
RatingCategory01 = "a",
RatingCategory02 = "b",
RatingCategory03 = "c"
};
var dictionaryModel = viewModel.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.ToDictionary(prop => prop.Name, prop => prop.GetValue(viewModel, null));
Then you can iterate the dictionary in the view.
Im hoping this will be an easy one..
I have a list of items in my MVC view, for the first 3 I want to display the items inside then for items 6-10, inside .
Example below:
#foreach (var Article in #Model.ContentList)
{
// for Items 1 - 3
<h1>#Article.Title</h1>
// for Items 4 - 7
<h2>#Article.Title</h2>
// for Items 7 +
<h3>#Article.Title</h3>
}
Whats the best way of doing this inside my view?
Use a for loop instead of foreach.
#for ( int i = 0; i < Model.ContentList.Count; i++ )
{
var Article = Model.ContentList[i];
if ( i < 3 ){
// for Items 1 - 3
<h1>#Article.Title</h1>
} else if ( i < 7 ){
// for Items 4 - 7
<h2>#Article.Title</h2>
} else {
// for Items 7 +
<h3>#Article.Title</h3>
}
}
I think a better answer would be to re-design your model.
Article class should contain in itself what sort of header it has, for example...
public class Article
{
public string Title { get; set; }
public string Type { get; set; } // for example
}
then within your view
#foreach (var Article in #Model.ContentList)
{
if(Article.Type == "Big")
{
<h1>#Article.Title</h1>
}
else if(Article.Type == "Medium")
{
<h2>#Article.Title</h2>
}
else if(Article.Type == "Small")
{
<h3>#Article.Title</h3>
}
}
I have a collection of items and would like to print them out with Razor. For every 12 elements it should create a new div. However, opening and closing tags seems to cause a lot of problems. This is my code.
<div class='grid_7'>
#foreach (var property in properties)
{
#if (counter % 12 == 0)
{
</div>
<div class='grid_7'>
}
#if (property.ShowInEditor)
{
<span> one property! #(property.Name) </span>
}
counter++
}
However, it is telling me that the first if and the first foreach are missing a closing {. I think that's because of the unclosed tags but, as you can see, I can't close the tag there.
How can I do this?
edit: removing # before the if cause even more problems making Razor belive it is text and not code.
Try adding "#:" without the quotes before the lines where you get this error and remove the # before if statements.
<div class='grid_7'>
#foreach (var property in properties)
{
if (counter % 12 == 0)
{
#:</div>
#:<div class='grid_7'>
}
if (property.ShowInEditor)
{
#:<span> one property! #property.Name </span>
}
counter++;
}
You'll need to modify your conditions a little to take into account the counter being zero at the start otherwise you'll get an empty div on first interation:
<div class='grid_7'>
#{
var counter = 0;
foreach (var property in properties)
{
if (counter > 0 && counter % 12 == 0)
{
#Html.Raw("</div><div class='grid_7'>")
}
if (property.ShowInEditor)
{
<span> one property! #(property.Name) </span>
}
counter++;
}
</div>
<div class='grid_7'>
#foreach (var property in properties)
{
if (count%7==0)
{
#Html.Raw("</div>");
#Html.Raw("<div class='grid_7'>");
}
else
{
//do something
}
I have a generic list of objects that's passed to a view. Currently, each list item is displayed to the user row-by-row:
#foreach (var result in Model.SearchResults)
{
result.Address.TownCity
// ...
}
So each list item is displayed in its own row. Aesthetically this doesn't look the best, as there's quite a lot of white space left over.
What I want to do is something like this:
row 1 | list item 1 | list item 2
row 2 | list item 3 | list item 4
and so on.....
Is this something I have to do server-side i.e. put half of my list into another list and in the view have two foreach loops - each one filling a column, row-by-row? Or is there anyway this can be done in the view using razor?
You can do this to group each set of rows into 'batches' and then loop through each item in the batch.
#{
var batches = Model.SearchResult
.Select((x, i) => new { x, i })
.GroupBy(p => (p.i / 2), p => p.x);
}
#foreach(var row in batches)
{
<span>Row #row.Key</span>
#foreach(var item in row)
{
<span>| #item.Address.TownCity</span>
}
}
Alternatively you can use this; it's simpler, though a bit less elegant
#{
var resultArray = Model.SearchResult.ToArray(); // only necessary if SearchResult is not a list or array
}
#for(var i = 0; i < resultArray.Length; i++)
{
var item = resultArray[i];
if (i % 2 == 0)
{
<span>Row #(i / 2)</span>
}
<span>| #item.Address.TownCity</span>
}
This is very easy to do in CSS without adding additional load and complexity to your code
See this fiddle: http://jsfiddle.net/taBec/
#foreach (var result in Model.SearchResults)
{
<div class="box">#result.Address.TownCity</div>
}
Then in CSS:
.box { width: 150px; float: left; height:25px}
Your app runs faster and your design is responsive and fluid. I hope this helps.