MVC Razor, add if statement to foreach loop - asp.net-mvc

I'm trying to add data from my model to a table with razor. My problem is that i want an if statement to decide what class the tagg should be and i can't get this to work.
When i add the if i get the following error when i run the code
The foreach block is missing a closing "}" character
How should i add the if statement? This is my current code
#{
var counter = 0;
}
#foreach (var item in Model)
{
if(item.status == "Active") {
<tr>
}
else {
<tr class="danger">
}
<td>#counter</td>
<td>#item.FirstName #item.LastName</td>
<td>#item.Email</td>
<td>#item.PhoneNumber</td>
<td>Ändra</td>
<td>Inaktivera</td>
</tr>
counter++;
}

MVC should detect html tags and render those out, however it seem this doesnt always work.
In between the curly brackets, try adding a tag
eg:
{
<text>
your html
</text>
}
or
if you just adding the class try something like:
<tr #(item.status == "Active" ? String.Empty : "class=\"danger\"" )>

try below code.
#{
var counter = 0;
}
#foreach (var item in Model)
{
if(item.status == "Active") {
<text> <tr> </text>
}
else {
<text><tr class="danger"></text>
}
<td>#counter</td>
<td>#item.FirstName #item.LastName</td>
<td>#item.Email</td>
<td>#item.PhoneNumber</td>
<td>Ändra</td>
<td>Inaktivera</td>
</tr>
counter++;
}

MVC detect HTML tags. So it will not add if statement like that.
you can not use <text><text> also.
You need to check condition in <tr> tag itself. See given result below.
#{
var counter = 0;
}
<table>
#foreach (var item in Model)
{
<tr #(item.status=="Active" ? String.Empty : "class=\" danger\"")>
<td>#counter</td>
<td>#item.FirstName #item.LastName</td>
<td>#item.Email</td>
<td>#item.PhoneNumber</td>
<td>Ändra</td>
<td>Inaktivera</td>
</tr>
counter++;
}
</table>

You can add extension method that take bool or string depend on your needs
public static class HtmlHelpExtention
{
public static string IsChecked(this IHtmlHelper htmlHelper,bool IsCheck, string className)
{
return IsCheck? className:"";
}
}
and then use it in the view
<tr class="#Html.IsChecked(item.IsGift,"teal accent-3")">
using this method will give you the ability to use multiple classes

Love Pandey solution works for me, but only for one class name. For more than one class name browser interpret second name as separate attribute. My modification for it is as below:
#{
var counter = 0;
}
<table>
#foreach (var item in Model)
string className = item.status=="Active" ? String.Empty : "item second-class-name";
{
<tr class="#className">
<td>#counter</td>
<td>#item.FirstName #item.LastName</td>
<td>#item.Email</td>
<td>#item.PhoneNumber</td>
<td>Ändra</td>
<td>Inaktivera</td>
</tr>
counter++;
}
</table>

You cannot use tag twice in a code block.
If you encounter problem because this limitation, put the second textbox as a string and then display it using html helper.
#{
int loop=0;
string HtmlBlock = "<table><tr><td style='font-weight:bold'>Lorem Text</td></tr></table>";
}
#foreach(var itemz in Mode.List){
If(loop ==3){ Html.Raw(HtmlBlock ); }
<text>itemz.Name Itemz.NIP</text>
loop++;
}

Related

Adding a css class to every other item when looping through ViewModel in a razor view

In a razor view I am trying to display quotes. When I loop through the list of quotes in the ViewModel I need to be able to add a class of blockquote-reverse to every other item in the list.
Please see below:
#model IEnumerable<CustomerViewModel>
#foreach (var customer in Model)
{
#* if index is odd *#
<blockquote>
<p>#customer.Name</p>
<footer>#customer.Quote</cite></footer>
</blockquote>
#* if index is even *#
<blockquote class="blockquote-reverse">
<p>#customer.Name</p>
<footer>#customer.Quote</cite></footer>
</blockquote>
}
Introduce a variable for tracking the index of a record, and use mod operator (%) to choose correct CSS class:
#{int i = 0;}
#foreach (var customer in Model)
{
var className = i % 2 == 0 ? "blockquote-reverse" : null;
i++;
<blockquote class="#className">
<p>#customer.Name</p>
<footer><cite>#customer.Quote</cite></footer>
</blockquote>
}
NOTE: You have a closing tag </cite> without an opening tag in your code, I added opening tag to my answer.
Add a variable for an indexer so you can use the % operator
#{ var index = 0; }
#foreach (var customer in Model)
{
if (index % 2 == 0)
{
.... // without class name
}
else
{
.... // with class name
}
index++;
}
Side note: You can also consider using css to style the elements using a :nth-child(2n) selector (for an example, refer this answer)

Writing conditional HTML with razor

Depending on my record, I'd like to change the style of the table row in my current iteration.
The below example doesn't work, but how would I go about doing this correctly?
foreach (var item in Model)
{
#{
if (item.DataState != DataState.Active)
{
<tr style="background-color: red">
}
else
<tr>
}
<td>#item.Name</td>
</tr>
}
So effectively, I'd like to dynamically render the <tr> element differently based on the DataState of my model.
Here's a shorter approach:
#foreach (var item in Model)
{
<tr #(item.DataState != DataState.Active ? "style=background-color:red" : "")>
<td>#item.Name</td>
</tr>
}
Edit: Code fixed
There are multiple way you can write condition.
Option 1:
#foreach (var item in Model)
{
if (item.DataState != DataState.Active)
{
<tr style="background-color: red">
<td>#item.Name</td>
</tr>
}
else
{
<tr>
<td>#item.Name</td>
</tr>
}
}
Option 2:
#foreach (var item in Model)
{
<tr style="#( item.DataState != DataState.Active ? "background-color: red;" : "" )">
<td>#item.Name</td>
</tr>
}
Define the attributes in a variable. The razor parser will omit the attribute if its value is null
#foreach (var item in Model)
{
var attributes = item.DataState == DataState.Active ? null : "background-color: red";
<tr style=#attributes>
<td>#item.Name</td>
</tr>
}
Not sure what kind of error you faces right now, but i gess your problem is that Razor don't understand all cases of tricky html render.
You can force him to render html where you need with #: symbol for single string or <text> tag like this:
#foreach (var item in Model)
{
if (item.DataState != DataState.Active)
{
#: <tr style="background-color: red">
}
else
{
<text><tr></text>
}
<td>#item.Name</td>
</tr>
}
You're probably going to get compile errors if you start splitting the <tr> tag up in the view and I'd not duplicate large chunks of code. I would do the following:
#foreach(var item in Model) {
string strStyle=String.Empty;
if(item.DataState!=DataState.Active) { strStyle = "background-color:red;" }
<text>
<tr style="#strStyle">
<td>#item.Name</td>
</tr>
</text>
}
#(item.DataState != DataState.Active ? "style=" + "background-color:red" : "")
#(!string.IsNullOrWhiteSpace(Model?.District?.Name) ? Html.Raw($"<span class=\"location\">{Model?.District?.Name}</span>") : string.Empty)

Why is my ViewData list null? MVC 4

I have two models, question and answer. I want to insert a list of answers thru ViewModel to a question but it seems in my post method my list is getting null. That might be a bad implementation as well, because I am returning a model of my question back when I post something and I guess my List is just getting null. How could I fix this?
Edit: I remade the controller and the view based on comments you gave me: Thats how it looks now, but seems my Answer List to be Empty again.
ViewModel:
public class ViewModel
{
public IEnumerable<Answer> Answers { get; set; }
public Question Question { get; set; }
}
Controller:
[Authorize]
public ActionResult Create()
{
ViewModel vm = new ViewModel();
ViewBag.BelongToTest = new SelectList(db.Tests, "TestId" , "TestTitle").FirstOrDefault();
vm.Question = new Question { Question_Text = String.Empty };
vm.Answers = new List<Answer> { new Answer { CorrectOrNot = false, AnswerText = "", OpenAnswerText = "" } };
return View(vm);
}
//
// POST: /Question/Create
[HttpPost]
[Authorize]
public ActionResult Create(ViewModel vm)
{
if (ModelState.IsValid)
{
vm.Question.BelongToTest = (from t in db.Tests
join m in db.Members on t.AddedByUser equals m.MemberId
where m.UserID == WebSecurity.CurrentUserId &&
t.AddedByUser == m.MemberId
orderby t.TestId descending
select t.TestId).FirstOrDefault();
db.Questions.Add(vm.Question);
db.SaveChanges();
if (vm.Answers != null)
{
foreach (var i in vm.Answers)
{
i.BelongToQuestion = vm.Question.QuestionId;
db.Answers.Add(i);
}
}
db.SaveChanges();
ViewBag.Message = "Data successfully saved!";
ModelState.Clear();
}
ViewBag.BelongToTest = new SelectList(db.Tests, "TestId", "TestTitle", vm.Question.BelongToTest);
vm.Question = new Question { Question_Text = String.Empty };
vm.Answers = new List<Answer> { new Answer { CorrectOrNot = false, AnswerText = "", OpenAnswerText = "" } };
return View("Create" , vm);
}
View:
#model MvcTestApplication.Models.ViewModel
#using MvcTestApplication.Models
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm("Create", "Question", FormMethod.Post)) {
<h2>Create</h2>
<table>
<tr>
<th>Question Name</th>
</tr>
<tr>
<td>#Html.EditorFor(model=>model.Question.Question_Text)</td>
</tr>
</table>
<table id="dataTable">
<tr>
<th>Correct?</th>
<th>Answer text</th>
<th>Open Answer</th>
</tr>
#foreach(var i in Model.Answers)
{
<tr>
<td>#Html.CheckBoxFor(model=>i.CorrectOrNot)</td>
<td>#Html.EditorFor(model=>i.AnswerText)</td>
<td>#Html.EditorFor(model=>i.OpenAnswerText)</td>
</tr>
}
</table>
<input type="button" id="addNew" value="Add Answer"/>
<input type="submit" value="Create" />
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script lang="javascript">
$(document).ready(function () {
//1. Add new row
$("#addNew").click(function (e) {
e.preventDefault();
var $tableBody = $("#dataTable");
var $trLast = $tableBody.find("tr:last");
var $trNew = $trLast.clone();
var suffix = $trNew.find(':input:first').attr('name').match(/\d+/);
$trNew.find("td:last").html('Remove');
$.each($trNew.find(':input'), function (i, val) {
// Replaced Name
var oldN = $(this).attr('name');
var newN = oldN.replace('[' + suffix + ']', '[' + (parseInt(suffix) + 1) + ']');
$(this).attr('name', newN);
//Replaced value
var type = $(this).attr('type');
if (type.toLowerCase() == "text") {
$(this).attr('value', '');
}
// If you have another Type then replace with default value
$(this).removeClass("input-validation-error");
});
$trLast.after($trNew);
// Re-assign Validation
var form = $("form")
.removeData("validator")
.removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse(form);
});
// 2. Remove
$('a.remove').live("click", function (e) {
e.preventDefault();
$(this).parent().parent().remove();
});
});
</script>
}
For the ModelBinder to bind to a List the HTML form must be sequentially indexed.
Your
<td>#Html.CheckBoxFor(model=>a.CorrectOrNot)</td>
<td>#Html.EditorFor(model=>a.AnswerText)</td>
<td>#Html.EditorFor(model=>a.OpenAnswerText)</td>
is creating something that will be bound to an individual answer. You need to render HTML that will be bound to a List, something like
#for (int i = 0; i < ((List<Answer>)ViewData["Answers"]).Count; i++)
{
<tr>
<td>#Html.CheckBoxFor(model=>((List<Answer>)ViewData["Answers"])[i].CorrectOrNot)</td>
<td>#Html.EditorFor(model=>((List<Answer>)ViewData["Answers"])[i].AnswerText)</td>
<td>#Html.EditorFor(model=>((List<Answer>)ViewData["Answers"])[i].OpenAnswerText)</td>
</tr>
}
Also, this looks pretty awful casting ViewData all over the place. It would generally be better, if you plan to keep this approach creating a real view model. You could pass that model to the view and it could wrapper both question and answer collections.
EDIT:
You still need to have a sequential index against your list which your edited implementation is not supplying. Something like
#for (int i = 0; i < Model.Answers.Count; i++)
{
<tr>
<td>#Html.CheckBoxFor(model=> Model.Answers[i].CorrectOrNot)</td>
<td>#Html.EditorFor(model=> Model.Answers[i].AnswerText)</td>
<td>#Html.EditorFor(model=> Model.Answers[i].OpenAnswerText)</td>
</tr>
}
ViewData is relevant when going from the controller to the view. It won't post back.
You should relay on the (model / parameter) binding that will take care of passing List<Answer> answerList for you
ViewData is only to transfer the data between the view and controller. You can use session to transfer the data between the controller
Thanks for the comments. They really helped me out. It was all correct that you say but there was something that was missing. My IEnumerable in the ViewModel simply does not allow me to index my values, instead using IList helped me out to index everything as it is supposed to be and everything works.

MVC DisplayTemplate - change output for a given string value

I currently have a view rendering a display page for a list of Employee entities.
The values returned from the database for the Gender property are a string value of "M" or "F" for the corresponding gender. I would like to be able to show string "Male" or "Female" in the view from the corresponding property value.
I've added the following logic to the Index.cshtml which is working.
#foreach (var item in Model)
{
<tr>
//... various <td>'s
#if (item.Gender == "M")
{
<td>Male</td>
}
else if (item.Gender == "F")
{
<td>Female</td>
}
}
I'm trying to move this to a Display Template, but cant get it working.
I've added the following code to the Views\Shared\DisplayTemplates\Gender.cshtml:
#model System.String
#if (Model.Gender == "M")
{
<td>Male</td>
}
else if (Model.Gender == "F")
{
<td>Female</td>
}
What is the best way to get this working?
You can add a partial view and call it like this in main view:
#foreach (var item in Model)
{
// other tds here
#Html.Partial("_Gender",item.Gender)
}
Create Partial view with name _Gender in the View >> Shared folder:
#model String
#{
Layout = null;
}
#if (Model== "M")
{
<td>Male</td>
}
else if (Model == "F")
{
<td>Female</td>
}
// or
<td>#(Model == "M" ? "Male" : "Female") </td>
It can also handle it in main view without creating partial view.
It can handle it in main view like this:
#foreach (var item in Model)
{
<tr>
//... various <td>'s
<td>#(item.Gender == "M" ? "Male" : "Female") </td>
</tr>
}
If you want it to work with a Display Template then you need to do something like this:
#foreach (var item in Model)
{
#Html.DisplayFor(model => item.Gender)
}
and in the View Model attach the attribute:
[UIHint("Gender")]
public string Gender { get; set; }
UIHint tells MVC which Display template to use. Otherwise by convention MVC will look for one called String.chtml

MVC3 Razor - Formatting collection of strings

I have a collection of strings that are repeated in MVC3 Razor with the following code:
#if (Model.Publications != null)
{
<tr>
<th>Publications</th>
<td>
#foreach (var publication in #Model.Publications)
{
<text>#publication.Title</text>
}
</td>
</tr>
}
Now when I display this, all I get is:
Book1Book2Book3
But I really want is something like this:
Book1, Book2, Book3
Is there a simple way in MVC razor to achieve this without having to combine 'if' and 'foreach' statements?
#string.Join(",",Model.Publications.Select(p=>"<text>"+ p.Title+ "</text>"))
string.Join(", ", model.Publications.Select(pub => pub.Title).ToArray())
#if (Model.Publications != null)
{
<tr>
<th>Publications</th>
<td>
#var first = true
#foreach (var publication in #Model.Publications)
{
<text>#string.format("{0}{1}", first ? "" : ", ", publication.Title)</text>
#first = false;
}
</td>
</tr>
}

Resources