Generating an MVC3 RadioButton list in a loop statement - asp.net-mvc-3

A collegaue of mine created a model and here it is.
Model
[Serializable]
public class ModifyCollegeListModel
{
public List<SchoolModel> CollegeList { get; set; }
public List<SchoolListModel> SchoolList { get; set; }
public string Notes { get; set; }
public int QuestionnaireId { get; set; }
}
[Serializable]
public class SchoolModel
{
public Guid SchoolId { get; set; }
public string SchoolName { get; set; }
public string StateName { get; set; }
public int DisplayIndex { get; set; }
public int DetailId { get; set; }
public int CategoryId { get; set; }
public int? ApplicationStatusId { get; set; }
}
I intend to create a loop that will generate the radiobutton list for the ApplicationStatusId , something like this...
Razor Code
#foreach (SchoolModel justright in Model.CollegeList.Where(m => m.CategoryId == 3).OrderBy(m => m.SchoolName).ToList<SchoolModel>())
{
<tr class="#HtmlHelpers.WriteIf(eventCounter % 2 == 0, "even", "odd")">
<td class="school"><b>#justright.SchoolName</b></td>
<td class="location"><b>#justright.StateName</b></td>
<td><label>#Html.RadioButtonFor(x => justright.SchoolId, (int)BrightHorizons.CC.BusinessLogic.CollegeListApplicationStatusEnum.DidNotApply)</label></td>
<td><label>#Html.RadioButtonFor(x => justright.SchoolId, (int)BrightHorizons.CC.BusinessLogic.CollegeListApplicationStatusEnum.Accepted)</label></td>
<td><label>#Html.RadioButtonFor(x => justright.SchoolId, (int)BrightHorizons.CC.BusinessLogic.CollegeListApplicationStatusEnum.NotAccepted)</label></td>
</tr>
}
but what happens is that ALL radiobhuttons created has the same name so they are grouped as one giant radiobutton collection. not via the schoolID... scratches head
Can someone help me here and point me to the right direction on how i will be able to create radio buttons that are grouped per row?

I would do two things.
First up, I would remove the filtering logic from the view. What I mean is this part:
Model.CollegeList.Where(m => m.CategoryId == 3).OrderBy(m => m.SchoolName).ToList<SchoolModel>()
That sort of logic belongs in a service. Also it will make the view much cleaner.
Secondly, I think you'll need to use a for-loop so MVC binds everything back how you want:
for (int i = 0; i < Model.CollegeList.Count; i++) {
<tr class="#HtmlHelpers.WriteIf(eventCounter % 2 == 0, "even", "odd")">
<td class="school"><b>#CollegeList[i].SchoolName</b></td>
<td class="location"><b>#CollegeList[i].StateName</b></td>
<td><label>#Html.RadioButtonFor(x => x.CollegeList[i].SchoolId, (int)BrightHorizons.CC.BusinessLogic.CollegeListApplicationStatusEnum.DidNotApply)</label></td>
<td><label>#Html.RadioButtonFor(x => x.CollegeList[i].SchoolId, (int)BrightHorizons.CC.BusinessLogic.CollegeListApplicationStatusEnum.Accepted)</label></td>
<td><label>#Html.RadioButtonFor(x => x.CollegeList[i].SchoolId, (int)BrightHorizons.CC.BusinessLogic.CollegeListApplicationStatusEnum.NotAccepted)</label></td>
</tr>
}
You'll notice after using the for-loop, that the radiobutton names and ID's also contain their index in the CollegeList. For example:
<input id="CollegeList_0__SchoolId" name="CollegeList[0].SchoolId" type="radio" value="2">

Related

MVC 3 Post of Viewmodel with Completex IEnumerable

I have a complex class that is part of a property of a viewmodel. My viewmodel has a wine class property and a wine class has a ICollection property called CaseProductions. The CaseProduction class has several properties as well.
On the create GET event, the NewWineViewModel is instantiated, then it runs a GetCaseProductionDefaults with create a list of CaseProduction classes that have some default values, but are mostly empty.
Now, I originally used razor to do a foreach statement and just pop out my table the way I wanted it. But I've see around that doesn't work to bind this type of IEnumerable back to the viewmodel on POST. I've tried to use the below, but no dice.
EditorFor(m => m.Wine.CaseProductions)
I'm really looking for advise on what the best way to handle this is. Each wine will have a collection of caseproductions, and I want that to bind back to the wine within the viewmodel. Is their some way I can edit the ids of those elements in razor to make sure they bind? What's the best way to handle this one?
viewmodel:
public class NewWineViewModel
{
public Wine Wine { get; set; }
public VOAVIRequest VOAVIRequest { get; set; }
public bool IsRequest { get; set; }
public Dictionary<int, int> BottlesPerCase { get; set; }
public SelectList VarTypes { get; set; }
public SelectList Origins { get; set; }
public SelectList Apps { get; set; }
public SelectList Vintages { get; set; }
public SelectList Importers { get; set; }
}
case production class:
public class CaseProduction
{
public int CaseProductionID { get; set; }
public int WineID { get; set; }
public int CaseProductionSizeID { get; set; }
public int CaseCount { get; set; }
public int CountPerCase { get; set; }
public virtual CaseProductionSize CaseProductionSize { get; set; }
public virtual Wine Wine { get; set; }
}
getting default case productions:
public List<CaseProduction> GetCaseProductionDefaults(vfContext db)
{
//creates blank list of all formats
List<CaseProduction> cp = new List<CaseProduction>();
foreach (CaseProductionSize size in db.CaseProductionSizes)
{
int defaultBottlesPerCase = 1;
switch ((CaseProductionSizeEnum)size.CaseProductionSizeID)
{
case CaseProductionSizeEnum.s187ml:
defaultBottlesPerCase= 24;
break;
case CaseProductionSizeEnum.s375ml:
defaultBottlesPerCase = 12;
break;
case CaseProductionSizeEnum.s500ml:
defaultBottlesPerCase = 12;
break;
case CaseProductionSizeEnum.s750ml:
defaultBottlesPerCase = 12;
break;
default:
defaultBottlesPerCase = 1;
break;
}
cp.Add(new CaseProduction { CaseProductionSizeID = size.CaseProductionSizeID, CountPerCase = defaultBottlesPerCase, CaseProductionSize = size, WineID = this.Wine.WineID });
}
return cp;
}
my razor code for the case production table:
#foreach (vf2.Models.CaseProduction cp in Model.Wine.CaseProductions)
{
<tr>
<td>#cp.CaseProductionSize.Name
</td>
<td>#Html.Raw(#Html.TextBoxFor(m => m.Wine.CaseProductions.Where(c => c.CaseProductionSizeID == cp.CaseProductionSizeID).First().CaseCount, new { #class = "caseCount", id = "txt" + cp.CaseProductionSize.Name }).ToString().Replace("CaseCount","txt" + cp.CaseProductionSize.Name))
</td>
<td>
#Html.DropDownListFor(m => m.Wine.CaseProductions.Where(c => c.CaseProductionSizeID == cp.CaseProductionSizeID).First().CountPerCase, new SelectList(Model.BottlesPerCase, "Key", "Value", cp.CountPerCase), new { #class = "countPerCase", id = "ddl" + cp.CaseProductionSize.Name, name = "ddl" + cp.CaseProductionSize.Name})
</td>
<td class="totalBottleCalc">
</td>
</tr>
}
instantiation of my caseproduction collection:
public ActionResult Create(int ID = 0, int VintUpID = 0)
{
NewWineViewModel nw = new NewWineViewModel();
nw.Wine.CaseProductions = nw.GetCaseProductionDefaults(db);
nw.BottlesPerCase = nw.GetBottlesPerCase(db);
I believe the model binder isn't picking up on your CaseProduction objects because they don't look like a CaseProduction objects.
You have renamed CaseCount, your CaseProductionSize has no Id (nor does you CaseProduction, and it's missing several properties. In your loop you have to include all properties, and keep the names consistent with the names of your POCOs. Otherwise the model binder won't know what they are. You can put all the properties in hidden fields if you want.
You must instantiate your nested Lists and complex models in your parent models constructor. The default model binder will not instantiate child classes.
If you do that, then you can use the EditorFor(m => m.Wine.CaseProductions) should work, and you don't need the complex view code you are using.
If you want to customize how the CaseProduction is rendered, then you can create a CaseProduction.cshtml file in ~/Shared/EditorTemplates and it will use this definition to render each item in the collection (it will automatically iterate over the collection for you).
Also, you shouldn't do linq queries in your view. Your problem there is that it looks like you're passing your data entity directly to the view. This is bad design. You need to instead create a ViewModel that contains only the information needed to render the view. Then, you filter your data before you assign it to the View model.

Getting distinct fields from an indirectly related table

I am new to MVC3, so apologies if this is basic, but I couldn't work it out.
I have a view model that includes 3 tables, an 'Albums' table with 2 foreign keys (Artist ID and Label ID).
I have a 'Labels' controller and a Details method where I display the Label table fields. I am trying to get the distinct 'Artists' from the collection of Albums related to the Label.
At the moment, I can get the name of the artists, but one is generated for each album - I have included the code for this scenario below.
I have tried a bunch of different things, like including Artists in the collection and using the Distinct and group by functions, but to no avail. Not sure if it is doable this way, or whether, due to the indirect relationship between the tables, I need to use a different approach.
Any helps is much appreciated.
Controller:
public ActionResult Details(int id)
{
var viewModel = new LabelsDetailsVM();
viewModel.Lables = db.Labels
.Include(a => a.Albums)
.SingleOrDefault(x => x.LabelID == id);
return View(viewModel);
View:
#foreach (var artist in Model.Lables.Albums)
{
<tr>
<td>
#Html.DisplayFor(model => artist.Artist.ArtistName)
</td>
</tr>
}
View Model
public class LabelsDetailsVM
{
public Label Lables { get; set; }
public IEnumerable<Album> Albums { get; set; }
public IEnumerable<Artist> Artists { get; set; }
}
}
Here's an example that might get you on the right track:
view model:
public class LabelViewModel
{
public Label Label { get; set; }
public IEnumerable<Artist> Artists { get; set; }
}
Controller:
public class LabelController : Controller
{
public ActionResult Details(int id)
{
var label = db.Labels
.Include(l => l.Albums)
.SingleOrDefault(l => l.LabelID == id);
if (label == null)
{
return HttpNotFound();
}
var distinctArtists = label
.Albums
.Select(a => a.Artist)
.Distinct(ArtistComparer.Default);
var model = new LabelViewModel
{
Label = label,
Artists = distinctArtists
};
return View(model);
}
}
and the equality comparer used to distinguish between 2 artists used in the controller that could of course be adapted to match your requirements. In this example it considers that 2 artists represent the same entity if they have the same ID. But you could work with some other properties such as the name and so on, all depends on what you need:
public class ArtistComparer : IEqualityComparer<Artist>
{
protected ArtistComparer()
{
}
private static readonly IEqualityComparer<Artist> _default = new ArtistComparer();
public static IEqualityComparer<Artist> Default
{
get
{
return _default;
}
}
public bool Equals(Artist x, Artist y)
{
if (x != null && y != null)
{
return x.ArtistID.Equals(y.ArtistID);
}
return false;
}
public int GetHashCode(Artist obj)
{
return obj.ArtistID.GetHashCode();
}
}
View:
#model LabelViewModel
<h3>#Html.DisplayFor(x => x.Label.LabelName)</h3>
<div>Artists</div>
<table>
<thead>
<tr>
<th>artist name</th>
</tr>
</thead>
<tbody>
#foreach (var artist in Model.Artists)
{
<tr>
<td>#artist.ArtistName</td>
</tr>
}
</tbody>
</table>

How to access IQueryable in Razor?

I’m building MVC3 “code first” application and to display data on my page I’m using the following:
Controller Code:
public ViewResult Index()
{
dynamic assignedT = from t in db.AssignedTasks
join a in db.Approvers on t.ApproverID equals a.ID
join r in db.Requestors on t.RequestorID equals r.ID
select new {Approver =a.FirstName + " " +a.LastName,
Requestor=r.FirstName + " " + r.LastName,
Title = t.Title,
t.RequestedDate,
t.CompletedDate, t.Description,
Status =(int)t.InternalStatut
};
return View(assignedT);
}
and on the page:
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<table>
#foreach (var item in Model) {
<tr>
<td>#item.GetType().GetProperty("Title").GetValue(item, null)
</td>
</tr>
}
</table>
I know that using dynamic is not the best way.
How to achieve same functionality using strongly-typed view?
Thank you
You can define the following class:
public class MyViewModel
{
string Approver { get; set; }
string Requestor { get; set; }
string Title { get; set; }
DateTime RequestedDate { get; set; }
DateTime CompletedDate { get; set; }
string Description { get; set; }
int Status { get; set; }
}
If you then change select new in your Linq query to select new MyViewModel then you have a strongly typed viewmodel (and dynamic to var).

Use knockout.js for 4 cascading dropdowns based on a hierarchy of objects

I am trying to get four cascading dropdowns using knockout.js:
Search Criteria
Sub Criteria
Value
State
I was able to get the first cascade going (but not the others due to databinding issues) by using code from the following link:
http://blogs.msdn.com/b/thebeebs/archive/2011/12/01/price-calculator.aspx
The data for these dropdowns is being returned to my razor viewpage as an IEnumrable of SearchCriterion from an MVC view using ViewBag.CriteriaData variable. The code for my classes is as follows:
public class SearchCriterion
{
public string Text { get; set; }
public string Value { get; set; }
public List<SubCriterion> SubCriteria { get; set; }
}
public class SubCriterion
{
public string SearchCriterionValue { get; set; }
public string Text { get; set; }
public string Value { get; set; }
public List<ColumnValue> ColumnValues { get; set; }
}
public class ColumnValue
{
public string SearchCriterionValue { get; set; }
public string SubCriterionValue { get; set; }
public string Text { get; set; }
public string Value { get; set; }
public IEnumerable<StateValue> StateValues { get; set; }
}
public class StateValue
{
public string SearchCriterionValue { get; set; }
public string SubCriterionValue { get; set; }
public string ColumnValue { get; set; }
public IEnumerable<int> InputStateIds { get; set; }
public IEnumerable<int> OutputStateIds { get; set; }
public int SelectedInputStateId { get; set; }
public int SelectedOutputStateId { get; set; }
public string Text { get; set; }
public string Value { get; set; }
}
The issues I am facing are in the following portions of the .cshtml code:
What do I specify in this template for the other two dropdowns. e.g. the third dropdown needs to be bound to ColumnValue.Value (ColumnValue is part of SubCriterion)
<script id='criteriaRowTemplate' type='text/html'>
<tr>
<td><select data-bind='options: criteriaData, optionsText: "Text", optionsCaption: "Search Criterion", value: SearchCriterion' /></td>
<td><select data-bind='visible: SearchCriterion, options: SearchCriterion() ? SearchCriterion().SubCriteria : null, optionsText: "Text", optionsCaption: "Sub Criterion", value: SubCriterion' /></td>
<td><select data-bind='visible: SubCriterion, options: SubCriterion() ? SubCriterion().ColumnValues : null, optionsText: "Text", optionsCaption: "Column Value", value: ColumnValue'/></td>
<td><select data-bind='visible: ColumnValue, options: ColumnValue() ? ColumnValue().StateValues : null, optionsText: "Text", optionsCaption: "State", value: StateValue'/></td>
<td><button data-bind='click: function() { viewModel.removeLine($data) }'>Remove</button></td>
</tr>
</script>
Is this correct?
var CriteriaLine = function() {
this.SearchCriterion = ko.observable();
this.SubCriterion = ko.observable();
this.ColumnValue = ko.observable();
this.StateValue = ko.observable();
// Whenever the Search Criteria changes, reset the Sub Criteria selection
this.SearchCriterion.subscribe(function() { this.SubCriterion(undefined); }.bind(this));
this.SubCriterion.subscribe(function() { this.ColumnValue(undefined); }.bind(this));
this.ColumnValue.subscribe(function() { this.StateValue(undefined); }.bind(this));
};
How do I map the complete C# object with the Javascript object? It works if we just have the first two dropdowns:
// Create a Javascript object object with the same property names as the C# object
var dataToSearch = $.map(this.lines(), function (line) { return line.StateValue() ? line.StateValue() : undefined; });
var SearchObject = new function () {
this.StateValues = dataToSearch;
};
// Convert the object to JSON
var searchCriteria = JSON.stringify(SearchObject);
Does anything need to change here for the binding?
// Apply the data from the server to the variable
var criteriaData = #Html.Raw(#Json.Encode(ViewBag.CriteriaData));
var viewModel = new Criteria();
ko.applyBindings(viewModel, document.getElementById("criteriaDiv"));
EDIT:
I am now able to populate the cascading dropdowns (updated code above). Now I have 4 columns, each column having one of the dropdowns. I also have 1...n number of rows being added dynamically by using Knockoutjs. So, the user can now select values from these dropdowns and add more rows of dropdowns if he wants. The only thing remaining is to return the values that the user selects for the dropdowns to the controller(point 3 above). I am not sure how I can do that. Any help would be appreciated.
EDIT 2:
Added working code for Item # 3 and modified the ColumnValue and StateValue classes.
I'm not sure I fully understand your question, but I'm going to take a whack at it anyway :). I think you're looking for a way to "validate" if it is in fact time to allow the next drop down to be active?
If so, you could approach it from a standpoint of Computed Observables. Basically, you would bind each of your dropdowns to a computed value which is derived from the previous dependencies.
Let me write fiddle and I'll show you :)
OK, give this a shot...sorry for the delay...http://jsfiddle.net/farina/ZNBcM/3/
I update the answer, Hope, it will help new Comers.
Methods for Binding Hierarchical Dropdowns using Knockout JS in MVC
Here you can find the good example .

Render Partial View from within a System.Web.Helpers.WebGrid

I am trying to render a Partial from within a System.Web.Helpers.WebGrid
my model class looks like this:
class GameInfo
{
public List<AppUser> Team1 { get; set; }
public List<AppUser> Team2 { get; set; }
// and more properties
}
class AppUser
{
public string PictureUrl { get; set; }
public string ProfileUrl { get; set; }
public long GamesWon { get; set; }
public long GamesLost { get; set; }
public int Points { get; set; }
// and more properties
}
I want my GridView to show a list of GameInfo's in my grid view.
What is turning out be to be tougher than expected is rendering the Teams (List).
To stay DRY I created a partial view to render a Team (_Team.cstml).
This is my razor code:
#if (Model != null)
{
var webgrid = new WebGrid(source: Model.Games,
rowsPerPage: 10);
<div id="grid">
#webgrid.GetHtml(
columns: webgrid.Columns(
webgrid.Column(header: "Score", format: #<text>#item.Score1/#item.Score1</text>),
webgrid.Column(header: "Team 1", format: (item) =>
{
return "hello sb"; // this line works!
//return Html.Partial("_Team", item.Team1); // this gives an error
})
)
)
</div>
}
Any idea how I can get this to work?
Thank you!
In case someone else runs into this, I managed to solve it this morning.
This works:
webgrid.Column(header: "Team 1", format: (item) =>
{
List<Cuarenta.Web.Models.AppUser> team = ((Cards.Cloud.WebRole.Admin.GameInfo)item.Value).Team1;
return Html.Partial("_Team", team);
})

Resources