knockout 3 level mapping, 3rd level doesnt change - asp.net-mvc

I am new to KnockoutJS and I am trying to create a 3-level model binding, for a master-detail ASP.NET MVC view.
here is the screen i am trying to implement this behavior on:
I have the following ViewModel design
public class CreateReservationViewModel
{
public string Title { get; set; }
public String LogoPath { get; set; }
public string StartDate { get; set; }
public string EndDate { get; set; }
public string StartTime { get; set; }
public string EndTime { get; set; }
public int TimeSpan { get; set; }
public int MinPersons { get; set; }
public int MaxPersons { get; set; }
public List<ReservationOptionViewModel> ReservationOptions { get; set; }
public string MessageToClient { get; set; }
public CreateReservationViewModel()
{
ReservationOptions = new List<ReservationOptionViewModel>();
}
}
public class ReservationOptionViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public int TypeId { get; set; }
public string TypeDescription { get; set; }
public string Info { get; set; }
public List<ValuesViewModel> ReservationOptionValues { get; set; }
public ReservationOptionViewModel()
{
ReservationOptionValues = new List<ValuesViewModel>();
}
}
public class ValuesViewModel
{
public int Id { get; set; }
public string ValueTitle { get; set; } // this value i cant seem to get to bind user input always defaults to whatever i set it in knockout
}
and here is my .js and .html https://jsfiddle.net/camLpdty/
Although I am able to successfully bind the first and second levels, 3rd level binding always picks up default values i.e: [({ Id: 0, ValueTitle: "this is read only and cant change"})] }
everything else seems to work...

From your jsfiddle it looks like the binding context for the Values column is a bit off. data-bind="value: Values.ValueTitle" - Here Values is an array but you're trying to bind a single input box to it. It doesn't know which element's ValueTitle to use so it's probably not bound to anything. You'll need to use a "foreach" or change how you're displaying that information
As a quick test you can try changing that last binding from:
<td>
<input class="form-control input-sm"
data-bind="value: ReservationOptionValues.ValueTitle" />
</td>
to
<td data-bind="foreach: ReservationOptionValues">
<input class="form-control input-sm" data-bind="value: ValueTitle" />
</td>
This will create an input box for every element in the Values array so you probably don't want to leave it like this, but it should highlight the knockout-context problem

Related

Grid MVC how to add the details section

I have been using Grid.Mvc to display some records for a while now, however as more and more properties have been added to the model, the grid has become ugly and too busy.
So I revisted the Grid.Mvc documentation pages and realised that in the demo provided:
http://gridmvc.azurewebsites.net/
They are rather neatly displaying full details of the record in a div on the right hand side of the page under the title Order details, meaning the actual grid does not get bogged down with the finer details.
I want to implement this to my project but I cannot work out how this works, there seems to be no documentation on the website that I can find that explains this, even though it seems to be used in every example they show. When I click view source I cannot see any source for how the data is being retrieved asynchronously per row.
How do I add a details section for each selected row as per the example linked?
edit: Object im passing back as JSON
public class MasterEmailViewModel
{
public int EmailId { get; set; }
public int MvpId { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
public string AddressLineOneOld { get; set; }
public string AddressLineTwoOld { get; set; }
public string AddressLineThreeOld { get; set; }
public string AddressLineFourOld { get; set; }
public string AddressLineFiveOld { get; set; }
public string PostcodeOld { get; set; }
public string AddressLineOneNew { get; set; }
public string AddressLineTwoNew { get; set; }
public string AddressLineThreeNew { get; set; }
public string AddressLineFourNew { get; set; }
public string AddressLineFiveNew { get; set; }
public string PostcodeNew { get; set; }
public bool IsChecked { get; set; }
public string Comment { get; set; }
//navigation prop
}
I have done the exact same thing with one of my projects.
The example is just using a jquery $post
View
<div class="row">
<div class="col-md-3 col-md-push-9">
<h4>Order details
</h4>
<div id="emailId">
<p class="muted">
Select order to display detailed infomation
</p>
</div>
<div id="firstNameId">
</div>
</div>
<div class="col-md-9 col-md-pull-3">
//Grid stuff
</div>
</div>
JS
$(function () {
// ordersGrid is what you called your Grid e.g. #Html.Grid(Model).Named("ordersGrid")
pageGrids.ordersGrid.onRowSelect(function (e) {
$.post("/Home/GetOrder?id=" + e.row.OrderID, function (data) {
if (data.Status <= 0) {
alert(data.Message);
return;
}
// Replace the data
$("#emailId").html(data.EmailId);
$('#firstNameId').html(data.FirstName);
});
});
});
Controller
[HttpPost]
public ActionResult GetOrder(int id)
{
// Get the data
return Json(data);
}

Kendo Grid edit popup complex model submitting

I’m trying to use complex models with Kendo grid edit popup. When submitting ALResults object properties are always null. It works fine when I’m not using Kendo. Is there a problem with kendo complex model submitting?
public class InitialApplicantLevel2Model
{
public InitialApplicantLevel2Model()
{
alResultsModel = new ALResults();
}
public int InitialApplicantLevel2ID { get; set; }
public string ApplicantName { get; set; }
public string ContactNumber { get; set; }
public string School { get; set; }
[Required(ErrorMessage="Ref No. required.")]
public int? EnquiryID { get; set; }
public ALResults alResultsModel { get; set; }
}
public class ALResults
{
public int ResultsID { get; set; }
public int InitialApplicantLevel2ID { get; set; }
public string Stream { get; set; }
public string Grading { get; set; }
public string IndexNo { get; set; }
public int? Year { get; set; }
public int? Attempt { get; set; }
public double? ZScore { get; set; }
public string Medium { get; set; }
}
#model SIMS.Models.StudentIntake.InitialApplicantLevel2Model
<tr>
<td>Year: </td>
<td>#Html.TextBoxFor(o=>o.alResultsModel.Year)</td>
<td>Index No: </td>
<td>#Html.TextBoxFor(o=>o.alResultsModel.IndexNo)</td>
<td>Medium: </td>
<td>#Html.TextBoxFor(o=>o.alResultsModel.Medium)</td>
</tr>
<tr>
<td>Stream: </td>
<td>#Html.TextBoxFor(o=>o.alResultsModel.Stream)</td>
<td>Attempt: </td>
<td>#Html.TextBoxFor(o=>o.alResultsModel.Attempt)</td>
<td>Zscore: </td>
<td>
#Html.TextBoxFor(o=>o.alResultsModel.ZScore)
</td>
</tr>
I found the answer here
Unfortunately Kendo UI doesn't support Class Composition / View Models
containing complex objects, your View Models need to be completely
flat to avoid unexpected behaviour.

Model returning date value as required although it is not null in table

I have a asp mvc 5 project using entity framework code-first. I created the controllers and views with scaffolding. Although i have not specified in the model that the Due date be [Required] and the item in the table has a value of NOT NULL.
Yet I am still getting this message on the POST
The DueDate field is required.
The model is really basic
public class Project
{
public int ProjectId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime StartDate { get; set; }
public DateTime DueDate { get; set; }
public int ClientId { get; set; }
public virtual Client Client { get; set; }
}
}
This is a Datetime2 datatype using EditorFor
<div class="input-group">
#Html.EditorFor(model => model.DueDate, new { #class = "form-control datepicker" })
<div class="input-group-addon">
<i class="entypo-calendar"></i>
</div>
</div>
It is also using jquery bootstrap datepicker, Although i doubt these has any effect on this functionality. Really confused how this field is a required field yet it has not been set as such in any area of the solution or database.
That's normal. DateTime is a value type meaning that it will always require a value. The model metadeata provider in ASP.NET MVC automatically adds the required attribute to non-nullable data types. You could use a nullable DateTime:
public class Project
{
public int ProjectId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime StartDate { get; set; }
public DateTime? DueDate { get; set; }
public int ClientId { get; set; }
public virtual Client Client { get; set; }
}

Model count = null

I'm rewriting this question:
I have 2 models. Entry and Topic.
public class Entry
{
public int EntryId { get; set; }
public int UserId { get; set; }
public int TopicId { get; set; }
public String EntryQuestion { get; set; }
public String EntryAnswer { get; set; }
public int EntryReview { get; set; }
public String QuestionValidationURL { get; set; }
public virtual ICollection<Topic> TopicList { get; set; }
}
public class Topic
{
public int TopicId { get; set; }
public String TopicName { get; set; }
}
I followed an example on ASP.Net/MVC to set up my models this way.
What I would like to do is for every entry item I have a TopicId, but then I'd like to convert that to a TopicName by accessing my TopicList.
My question is, how do I load TopicList?
In the examples I'm following I'm seeing something about LazyLoading and EagerLoading, but it doesn't seem to be working.
I tried doing the following from my Entry controller:
db.Entries.Include(x => x.TopicList).Load();
But that still gives me a TopicList of 0 (which is better than null)
How can I do this?
In my view I'm binding to the Entries like this:
#model IEnumerable<projectInterview.Models.Entry>
I would like to access the TopicList here:
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.TopicId)
</td>
...
</tr>
I'd like to use the TopicId in this loop and display the TopicName that is part of the object in the collection.
I'm assuming you're following an Entity Framework example. You're trying to create a one-to-many relationship, as far as I can tell, although I'm unsure about which end is which.
In the general case, to establish a one-to-many relationship, you have to do something like this:
public class One
{
[Key]
public int Id { get; set; }
public virtual ICollection<Many> Many { get; set; }
}
public class Many
{
[Key]
public int Id { get; set; }
[ForeignKey("One")]
public int OneId { get; set; }
public virtual One One { get; set; }
}
If what you're trying to do is have one Entry relating to many Topic objects, then you're almost there but you're lacking something.
For the ICollection<Topic> to actually contain anything, the (many) Topic objects need to have a foreign key to the (one) Entry. (It also doesn't hurt to explicitly mark the primary key on both sides, rather than relying on the EF conventions.)
public class Topic
{
[Key]
public int TopicId { get; set; }
public String TopicName { get; set; }
[ForeignKey("Entry")]
public int EntryId { get; set; }
public virtual Entry Entry { get; set; }
}
public class Entry
{
[Key]
public int EntryId { get; set; }
public int UserId { get; set; }
public int TopicId { get; set; }
public String EntryQuestion { get; set; }
public String EntryAnswer { get; set; }
public int EntryReview { get; set; }
public String QuestionValidationURL { get; set; }
public virtual ICollection<Topic> TopicList { get; set; }
}
Now TopicList should be an actual and populated collection, without the need to do an Include.
If, on the other hand, you want one Topic relating to many Entry objects, then you have it a little backwards. The correct way would be:
public class Topic
{
[Key]
public int TopicId { get; set; }
public String TopicName { get; set; }
public virtual ICollection <Entry> Entries { get; set; }
}
public class Entry
{
[Key]
public int EntryId { get; set; }
public int UserId { get; set; }
public String EntryQuestion { get; set; }
public String EntryAnswer { get; set; }
public int EntryReview { get; set; }
public String QuestionValidationURL { get; set; }
[ForeignKey("Topic")]
public int TopicId { get; set; }
public virtual Topic Topic { get; set; }
}
In this case, you may or may not use db.Entries.Include(x => x.Topic) depending on whether you want them loaded all at once or one-by-one on demand. Regardless of what you choose, the following expression should return the proper value:
myEntry.Topic.TopicName
If I understand you correctly you have added the list of Topics to the Entry just to get the name of the topic when displaying the entry. The best way to do this is to actually have a Topic property in your entry model. So your model would look like this:
public class Entry
{
public int EntryId { get; set; }
public int UserId { get; set; }
public int TopicId { get; set; }
public String EntryQuestion { get; set; }
public String EntryAnswer { get; set; }
public int EntryReview { get; set; }
public String QuestionValidationURL { get; set; }
//Change this.....
public virtual Topic Topic { get; set; }
}
Then in your view you would use (assuming the Model is an IEnumerable):
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => modelItem.Topic.TopicName )
</td>
...
</tr>
This link has a great example of how to do this:
http://weblogs.asp.net/manavi/archive/2011/03/28/associations-in-ef-4-1-code-first-part-2-complex-types.aspx
In my opinion problem is with casting. In view you have IEnumerable<projectInterview.Models.Entry> while Topics is ICollection<Topic>, which is a collection of different type
Topics = null means there are no Topics in the list to iterate over. How do you fill them? Your view expects IEnumerable how do you cast your topics to the entries?
Based on the original question I've added a small working example, maybe it helps you to find your bug.
Controller:
public class TestController : Controller
{
public ActionResult Index()
{
var viewModel = new ViewModel()
{
Topics = new List<Topic>()
};
viewModel.Topics.Add(new Topic() { header = "test" });
viewModel.Topics.Add(new Topic() { header = "test2" });
return View(viewModel);
}
}
Model:
public class ViewModel
{
public virtual ICollection<Topic> Topics { get; set; }
public int getCount()
{
return Topics.Count;
}
}
public class Topic
{
public string header { get; set; }
}
View:
#model testProject.Models.ViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Model.getCount()
#foreach(var item in Model.Topics)
{
<div>#item.header</div>
}
Output:
Index
2
test
test2
It seems that you are not initializing your Topics anywhere in the code. If the collection is null it means it is not initialized. If you instantiate it with
ICollection<Topic> Topics = new List<Topic>();
Once initialized you should receive zero when calling Topics.Count. If you do not make a call to a database it will stay zero.
In your case check whether you are instantiating the Topics.

Model nested type is null when after modelbinding

I have a ViewModel as below:
public class CheckoutViewModel
{
public string ProductNumber { get; set; }
public string Name { get; set; }
public int Price { get; set; }
public Input UserInput;
public class Input
{
public string Email { get; set; }
public string Phone { get; set; }
}
}
And an action like this:
[HttpPost]
public ActionResult Index(CheckoutViewModel model)
{
// ...
return View();
}
And my model has bound as below:
#model GameUp.WebUI.ViewModels.CheckoutViewModel
#using (Html.BeginForm("Index", "Checkout", FormMethod.Post))
{
#Html.AntiForgeryToken()
<!-- some HTML -->
#Html.LabelFor(m => m.UserInput.Email)
#Html.TextBoxFor(m => m.UserInput.Email)
#Html.LabelFor(model => model.UserInput.Phone)
#Html.TextBoxFor(model => model.UserInput.Phone)
<button>Submit</button>
}
When I submit the form, the UserInput is null. I know ASP.NET MVC is able to bind nested types but in this code is not. Also I can get the Email and Phone values by:
var email = Request.Form["UserInput.Email"];
var phone = Request.Form["UserInput.Phone"];
Maybe I do something wrong! It's a simple model binding you can find everywhere in the web.
You forgot to put a setter in your UserInput, I don't think the setter is automatic. Anyway you can make it work by just putting a getter/setter in your UserInput and no need to do extra in your controller method:
public Input UserInput { get; set; }
Your complete model:
public class CheckoutViewModel
{
public string ProductNumber { get; set; }
public string Name { get; set; }
public int Price { get; set; }
public Input UserInput { get; set; }
public class Input
{
public string Email { get; set; }
public string Phone { get; set; }
}
}

Resources