Use knockout.js for 4 cascading dropdowns based on a hierarchy of objects - asp.net-mvc-3

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 .

Related

kendo scheduler not return date on server side using mvc

I am having issue of using Kendo UI scheduler,
When I schedule task ,
Kendo UI start and end date not returning on server side.
Start and end date always return default date.
Here a Razor Code :
#model IEnumerable<Web.Models.PlantColor>
#{
ViewBag.Title = "Schedule View";
}
<h2>Schedule View</h2>
#(Html.Kendo().Scheduler<WorkScheduler.Web.Models.KendoSchedular>()
.Name("scheduler")
.Date(new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day))
.StartTime(new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 08, 00, 00))
.Height(600)
.Views(views =>
{
views.DayView();
views.WorkWeekView();
views.WeekView();
views.MonthView();
views.AgendaView();
})
.Resources(resource =>
{
resource.Add(m => m.PlantId)
.Title("Owner")
.DataTextField("Text")
.DataValueField("Value")
.DataColorField("Color")
.BindTo(Model);
})
.DataSource(d => d
.Model(m =>
{
m.Id(f => f.id);
})
.Read("ReadSchedule", "ScheduleView")
.Create("CreateSchedule", "ScheduleView")
.Destroy("Destroy", "ScheduleView")
.Update("Update", "ScheduleView")
)
)
Make sure that you have the start and end fields defined in your Model that you are posting back (and model inherits from ISchedulerEvent):
public class CalendarAppointmentViewModel : ISchedulerEvent
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Recurrence { get; set; }
public string StartTimezone { get; set; }
public string EndTimezone { get; set; }
private DateTime start;
public DateTime Start
{
get
{
return start;
}
set
{
start = value.ToUniversalTime();
}
}
private DateTime end;
public DateTime End
{
get
{
return end;
}
set
{
end = value.ToUniversalTime();
}
}
public string RecurrenceRule { get; set; }
public int? RecurrenceID { get; set; }
public string RecurrenceException { get; set; }
public bool IsAllDay { get; set; }
I can see this question is already accepted as best answer. Perhaps following might be helpful also for someone struggling to return Start/End dates from Editor of Kendo Scheduler.
I had same issue, and followed the solutions provided here, still no success. In my case defining the culture and creating a Model inherited from "ISchedulerEvent" was still returning default date to the server side in Create & Update events handler methods of the Controller.
For me problem was a missing .js file.
kendo.timezones.min.js
So for anyone who is in the same shoes like me, kindly look at your Scripts folder in the project, and if that .js file is missing.
And please follow these steps in setting up the project.

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.

Generating an MVC3 RadioButton list in a loop statement

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">

WebGrid grid.Columns Format Error

#{
var grid = new WebGrid(Model.Auctions, rowsPerPage: Model.PagingInfo.ItemsPerPage, defaultSort: "AddedDate");
}
#grid.GetHtml(
columns: grid.Columns(
**grid.Column(columnName: "", header: "Type", format: (auction) => AuctionListViewModel.GetAuctionType(auction)),**
grid.Column(columnName: "OwnerReference", header: "Owner reference")
)
);
public class AuctionListViewModel
{
public IEnumerable<Auction> Auctions { get; set; }
public IEnumerable<Item> Items { get; set; }
public PagingInfo PagingInfo { get; set; }
public string Title { get; set; }
public string Action { get; set; }
public static string GetAuctionType(Auction auction)
{
var type = string.Empty;
if (auction is LubAuction)
{
type = "Lowest unique wins";
}
else if (auction is EsfAuction)
{
type = "Highest wins";
}
return type;
}
}
With the above view code and model, get the following error on the line marked in bold, why is this?
The best overloaded method match for 'UI.Models.AuctionListViewModel.GetAuctionType(UI.AuctionService.Auction)' has some invalid arguments
In the grid.Column method's format parameter's parameter (in your case auction) you get the actual item (an Auction) but it's wrapped into a dynamic wrapper called WebGridRow.
You can use your properties on this wrapper and it delegates to the actual item e.g: auction.Title will work, but if you want to get the whole item (the Auction) you need to use the Value property of the WebGridRow.
format: auction =>
uctionListViewModel.GetAuctionType(((WebGridRow)auction).Value)
Due to the dynamic (weak) typing of the WebGrid helper you need a cast:
grid.Column(
columnName: "",
header: "Type",
format: (auction) => AuctionListViewModel.GetAuctionType((Auction)auction.Value)
)
I would recommend you using better grid helpers such as MvcContrib Grid and Telerik Grid which will give you strong typing and compile time safety.

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