I have just started to discover FluentHml and I'm stuck with the CheckBoxList Helper.
Here is the code
<ul>
<%=this.CheckBoxList(m=>m.Filter)
.Options(criteria.Choices, x => x.Code, x => x.DisplayText)
.Selected(Model.Filter)
.Label(criteria.Label).ItemFormat("<li> {0} </li>")
%>
</ul>
So, I have a checkboxlist based on "criteria.Choices" which is typed as List<ChoiceViewModel>.
Here is the code of a ChoiceViewModel
public class ChoiceViewModel
{
// Some stuff
public string Code { get{ return _code; } }
public string Label { get { return _label; }}
public string DisplayText { get { return _displayText;}
}
}
And my problem is :
I want to disable the checkbox under a condition.
Let's say that if the Code doesn't start with an "A", I want to disable the checkbox
How can I achieve that ?
Thanks,
Hasan
CheckboxList does not provide that. You can do it with CheckBox in loop. Something like this:
<label>criteria.Label</label>
<%foreach (var choice in criteria.Choices) {%>
<li>
<%=this.CheckBox(m => m.Filter)
.Value(choice.Code)
.Checked(choice == Model.Filter)
.Label(choice.Code.DisplayText)
.Disabled(choice.Code.StartsWith("A")%>
</li>
<%}%>
Related
I'm working on an ASP.NET MVC app. In this app, I'm am working to create a checkbox list on top of a custom object. The custom object looks like this:
public class ListItem<TId, TLabel, TIsChecked>
{
private TId id;
private TLabel label;
private TIsChecked isChecked;
public ListItem(TId id, TLabel label, TIsChecked isChecked)
{
this.id = id;
this.label = label;
this.isChecked = isChecked;
}
public TId Id
{
get { return id; }
}
public TLabel Label
{
get { return label; }
}
public TIsChecked IsChecked
{
get { return isChecked; }
}
}
I have a model that looks like this:
public class MyModel
{
public IEnumerable<ListItem<Guid, string, bool>> Options { get; set; }
}
I then have a controller, with an action called Profile, that looks like this:
[HttpGet]
public ActionResult Profile()
{
var model = new MyModel();
return View(model);
}
[HttpPost]
public ActionResult Profile(MyModel model)
{
return View(model);
}
I am rendering my checkbox options in my Razor view like this:
#foreach (var option in Model.Options)
{
<div class="form-group">
<div class="checkbox">
<label>
<input name="Options" value="#option.Id" type="checkbox" #((option.IsChecked == true) ? "checked" : string.Empty)> #option.Label
</label>
</div>
</div>
}
The options render fine. But, when I save the form, it looks like only the first selected checkbox ID is returned. I'm not sure how to a) properly render my HTML for a checkbox list and b) get the values to properly post back to the server. I've seen other examples in blog posts, but, my view will have significantly more HTML. For that reason, I'm trying to figure out "how" checkbox values are rendered on the client-side and posted back to the server in ASP.NET MVC.
First you have to do change in your model to use List<T> instead of IEnumerable<T>:
public List<ListItem<Guid, string, bool>> Options { get; set; }
Secondly, you do not have setters for your properties, so data would not be posted back, add setters:
public TId Id
{
get { return id; }
set { id = value;}
}
public TLabel Label
{
get { return label; }
set { label = value;}
}
public TIsChecked IsChecked
{
get { return isChecked; }
set { isChecked = value;}
}
and at last you simply need to use strongly typed html helper and use for loop so that indexing can bind data for post:
#for(int i=0; i< Model.Options.Count; i++)
{
<div class="form-group">
<div class="checkbox">
<label>
#Html.CheckBoxFor(model => model.Options[i].IsChecked) #Model.Options[i].Label
</label>
#Html.HiddenFor(model=> model.Options[i].Id)
#Html.HiddenFor(model=> model.Options[i].Label)
</div>
</div>
}
This will create check-boxes with correct names so that they can be binded on post.
You might want to have a look on How List binding works
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
I'm building a questionnaire. The questionnaire have multiple sections, each section has multiple questions, and each question can have one to many answers. Each question can be a different type (radio buttons, checkbox, text...).
I put my tables in the model and loop through the sections table to display sections, loop through questions to display questions, loop through answerOptions to populate answers:
<fieldset>
<legend>Fields</legend>
<%foreach (var s in Model.Sections)
{ %>
<h3><%=s.SCTN_TXT %></h3>
<% var QuestsInSect = Model.GetQuestionsBySectionID(s.SCTN_ID);%>
<%foreach (var q in QuestsInSect){%>
<h4><%=q.QSTN_TXT %><%=q.QSTN_ID.ToString() %></h4>
<% var answers = Model.GetAnswerOptionByQuestionID(q.QSTN_ID); %>
<%if (q.QSTN_TYP_ID>= 3)
{%>
<%:Html.TextBox(q.QSTN_ID.ToString())%>
<%}
else if (q.QSTN_TYP_ID == 1)
{ %>
<%var answerOptions = Model.GetDropDownListAnswerOptionByQuestionID(q.QSTN_ID);%>
<%:Html.DropDownList(q.QSTN_ID.ToString(), answerOptions)%>
<%}
else
{ %>
<% foreach (var ao in answers)
{ %>
<br />
<%:Html.CheckBox(q.QSTN_ID.ToString())%>
<%=ao.ANS_VAL%>
<% }
}
}
} %>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
In my controller I loop through collection.Allkeys to figure out the answer for each question:
[HttpPost]
public ActionResult Create(FormCollection collection)
{
try
{
// TODO: Add insert logic here
List<ASSMNT_RESP> arList = new List<ASSMNT_RESP>();
foreach (string key in collection.AllKeys)
{
QSTN q = _model.GetQuestionByQuestionID(int.Parse(key));
IEnumerable<ANS_OPTN> aos = _model.GetAnswerOptionByQuestionID(int.Parse(key));
ASSMNT_RESP ar = new ASSMNT_RESP();
ar.QSTN_ID = int.Parse(key);
ar.ASSMNT_ID = 1;
if (q.QSTN_TYP_ID == 1)//dropdown
{
//do something
}
else if (q.QSTN_TYP_ID == 2)//checkboxlist
{
//do something
}
else
{
//do something
}
//_model.AddAssessmentResponse(ar);
System.Diagnostics.Trace.WriteLine(key + "---"+ collection[key]);
}
//_model.Save();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
It works, but I just don't think it's a very good design. It seems like I have too much logic in the view. I would like to move the logic in the view and controller to the model. Can you recommend an easier/cleaner way to do this?
Thanks.
I'm not an expert on MVC, but I think you'd get a lot of benefit out of using a strongly-typed view based on a custom model class that you build. The properties exposed by such a model can be nested (i.e., the top-level model can consist of properties each of which are also custom classes). Conceptually, something like:
public class MyTopLevelModel
{
public MySubModel1 SubModel1 { get; set; }
public MySubModel2 SubModel2 { get; set; }
}
public class MySubModel1
{
public string AProperty { get; set; }
public int AnotherProperty { get; set; }
}
You can include collections in the class definitions, too. And then you can decorate the individual properties with with attributes specifying whether a particular property is required, a range of permissible values, etc.
It's a big subject, though, and this only scratches the surface. FWIW, I've gotten a LOT out of Steven Sanderson's Pro ASP.NET MVC2 Framework book.
Make your viewmodel more explicit.
public class ViewModel
{
public IList<SectionViewModel> Sections {get;set;}
}
public class SectionViewModel
{
public IList<QuestionViewModel> Questions {get;set;}
}
public class QuestionViewModel
{
public IList<AnswerViewModel> Answers {get;set;}
}
In your view you can then do something like this (I'm using razor):
#foreach(var section in Model.Sections)
{
<h3>#section.Name</h3>
foreach(var question in section.Questions)
{
<h4>#question.Name</h4>
foreach(var question in section.Questions)
{
#Html.EditorFor(x=> question.Answers)
}
}
}
Then create an EditorTemplate for your AnswerViewModel.
I have a model with a List<string> property. I want to present several select lists that bind to that property.
For example, supposed my model is named Favories, and I let the user select several favorite colors.
public class Favorites
{
public List<string> FavoriteColors { get; set;}
}
I tried binding using the indexes to the collection, but I ran into problems, most likely because FavoriteColors was empty. Here's the code that doesn't work (null exception):
#Html.DropDownListFor(m => m.FavoriteColors[0], ColorSelectList, "Select a color (required)")
#Html.DropDownListFor(m => m.FavoriteColors[1], ColorSelectList, "Select a color (optional)")
#Html.DropDownListFor(m => m.FavoriteColors[2], ColorSelectList, "Select a color (optional)")
I realize I could fix this a couple ways.
Populate FavoriteColors with 3 empty values. But this doesn't feel right since my model would have invalid data (empty values) that I'd have to workaround in a bunch of other places in my code.
Change my model so that I have 3 string properties (e.g. FavoriteColor1, FavoriteColor2, FavoriteColor3). Binding would be easier, but I'd still have to work around that deisgn with a bunch of code.
Is there a better way?
Here is a simple solution that will work for you using Razor and standard DropDownListFor<T> helpers.
All based on example data and files which you can change to suit your needs:
HomeController.cs
public class HomeController : Controller
{
public class FavoriteColorModel
{
public List<string> FavoriteColors { get; set; }
}
public ActionResult Index()
{
ViewBag.ColorList = new[]
{
"Blue",
"Red",
"Green",
"Orange"
};
var favoriteColors = new FavoriteColorModel()
{
FavoriteColors = new List<string>()
{
"Color1",
"Color2",
"Color3"
}
};
return View(favoriteColors);
}
[HttpPost]
public ActionResult Save(FavoriteColorModel model)
{
TempData["SelectedColors"] = model.FavoriteColors;
return RedirectToAction("Index");
}
}
Index.cshtml
#model MvcApplication4.Controllers.HomeController.FavoriteColorModel
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
#if (TempData["SelectedColors"] != null)
{
<text>
You have selected the following colors:<br />
<ul>
#foreach (var color in TempData["SelectedColors"] as List<string>)
{
<li style="color:#color">#color</li>
}
</ul>
</text>
}
#using (Html.BeginForm("Save", "Home"))
{
#: Favorite colors:
for (var index = 0; index < Model.FavoriteColors.Count; index++)
{
#Html.DropDownListFor(model => model.FavoriteColors[index], new SelectList(ViewBag.ColorList))
}
<br />
<input type="submit" value="Save" />
}
Your selected colors will appear in a list element upon submit. You can control the amount of colors you wish to save by adding items to the Color1, Color2 array etc.
Peace.
This is how I would do it because I dont like the way MVC handles drop down lists.
foreach (var ColorArray in m.FavoriteColors)
{ %>
<select name='colors'>
<% foreach (var color in ColorArray)
{%>
<option><%= color.ToString() %></option>
<% { %>
</select>
}%>
You could hard code it to just the indexes you want but you get the general idea. Also this would allow you to put in null checks whereever you like to handle those cases selectively.
Today I decided to give MVC a go, and although I really like the idea, I found it fairly difficult to transition from ASP.NET and grasp some basic concepts, like using foreach instead of nested repeaters.
It took me good few hours to come up with this solution, but it doesn't seem quite right. Could someone please explain what's wrong with this code, and what the right way to do it is. Here is my solution:
Essentially it's a survey that consists of several questions, each of which has several answers. I have tables in db, which are represented as strongly typed entities. The controller looks like this:
public ActionResult Details(int id)
{
return View(new Models.Entities().Questions.Where(r => r.PROMId == id));
}
and corresponding view like this:
<% foreach (var question in Model) { %>
<h3>Question <%: Array.IndexOf(Model.ToArray(), question) + 1 %></h3>
<p><%: question.QuestionPart1 %></p>
<p><%: question.QuestionPart2 %></p>
<% var answers = new Surveys_MVC.Models.Entities().Answers.Where(r => r.QuestionId == question.QuestionId); %>
<% foreach (var answer in answers) { %>
<input type="radio" /><%: answer.Text %>
<% } %>
<% } %>
All feedback appreciated.
As far as using for loops for the nested repeater behavior, I think that's the best way to do this in MVC. But I would suggest you use dedicated ViewModels.
ViewModel:
public class RadioQuestionListViewModel
{
public IEnumerable<RadioQuestionViewModel> Questions {get;set;}
}
public class RadioQuestionViewModel
{
public int QuestionNumber {get;set;}
public string InputName {get;set;}
public string QuestionPart1 {get;set;}
public string QuestionPart2 {get;set;}
public IEnumerable<RadioAnswerViewModel> PossibleAnswers {get;set;}
}
public class RadioAnswerViewModel
{
public int AnswerId {get;set;}
public string Text {get;set;}
}
Controller:
public ActionResult Details(int id)
{
var model = GetRadioQuestionListModelById(id);
return View(model);
}
View:
<% foreach (var question in Model) { %>
<h3>Question <%: question.QuestionNumber %></h3>
<p><%: question.QuestionPart1 %></p>
<p><%: question.QuestionPart2 %></p>
<% foreach (var answer in question.PossibleAnswers) { %>
<%: Html.RadioButton(question.InputName, answer.AnswerId) %>
<%: answer.Text %>
<% } %>
<% } %>
This approach has a few advantages:
It prevents your view code from depending on your data access classes. The view code should only be responsible for deciding how the desired view model gets rendered to HTML.
It keeps non-display-related logic out of your view code. If you later decide to page your questions, and are now showing questions 11-20 instead of 1-whatever, you can use the exact same view, because the controller took care of figuring out the question numbers to display.
It makes it easier to avoid doing a Array.IndexOf(Model.ToArray(), question) and a database roundtrip inside a for loop, which can become pretty costly if you have more than a few questions on the page.
And of course your radio buttons need to have a input name and value associated with them, or you'll have no way to retrieve this information when the form is submitted. By making the controller decide how the input name gets generated, you make it more obvious how the Details method corresponds to your SaveAnswers method.
Here's a possible implementation of GetRadioQuestionListModelById:
public RadioQuestionListViewModel GetRadioQuestionListModelById(int id)
{
// Make sure my context gets disposed as soon as I'm done with it.
using(var context = new Models.Entities())
{
// Pull all the questions and answers out in a single round-trip
var questions = context.Questions
.Where(r => r.PROMId == id)
.Select(r => new RadioQuestionViewModel
{
QuestionPart1 = r.q.QuestionPart1,
QuestionPart2 = r.q.QuestionPart2,
PossibleAnswers = r.a.Select(
a => new RadioAnswerViewModel
{
AnswerId = a.AnswerId,
Text = a.Text
})
})
.ToList();
}
// Populate question number and name
for(int i = 0; i < questions.Count; i++)
{
var q = questions[i];
q.QuestionNumber = i;
q.InputName = "Question_" + i;
}
return new RadioQuestionListViewModel{Questions = questions};
}
I don't know if it is better, but you can create a helper to do this for you:
public static void Repeater<T>(this HtmlHelper html, IEnumerable<T> items, string cssClass, string altCssClass, string cssLast, Action<T, string> render)
{
if (items == null)
return;
var i = 0;
foreach (var item in items)
{
i++;
if (i == items.Count())
render(item, cssLast);
else
render(item, (i % 2 == 0) ? cssClass : altCssClass);
}
}
Then you can call it like so:
<%Html.Repeater(Model, "css", "altCss", "lastCss", (question, css) => { %>
<h3>Question <%: Array.IndexOf(Model.ToArray(), question) + 1 %></h3>
<p><%: question.QuestionPart1 %></p>
<p><%: question.QuestionPart2 %></p>
<% var answers = new Surveys_MVC.Models.Entities().Answers.Where(r => r.QuestionId == question.QuestionId); %>
<% foreach (var answer in answers) { %>
<input type="radio" /><%: answer.Text %>
<% } %>
<% }); %>
This has a lot of power and the above is just a general example. You can read more here http://haacked.com/archive/2008/05/03/code-based-repeater-for-asp.net-mvc.aspx