I am currently facing a problem with validation after dynamically adding content.
I have a view strongly typed to a model (Order). This Order can have many items. The model looks something like the following:
public class Order
{
[Key]
[HiddenInput]
public int id { get; set; }
[Display(Name = "Order Number")]
public string number { get; set; }
[Display(Name = "Order Date")]
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime date { get; set; }
[Required(ErrorMessage = "Beneficiary is required.")]
[Display(Name = "Beneficiary")]
public int beneficiary_id { get; set; }
[Display(Name = "Beneficiary")]
public Beneficiary beneficiary { get; set; }
[Display(Name = "Items")]
public List<Item> items { get; set; }
[Display(Name = "Payment Method")]
public List<PaymentMethod> payment_methods { get; set; }
}
I enter the order information and also the items for that specific order. I tried a couple of ways to add content dynamically and finally went with Steven Sanderson's way.
In my view, I have the regular Order information and then the items, where my model looks something like this:
#model trackmeMvc.Models.Model.Order
#{
ViewBag.Title = "Create";
Html.EnableClientValidation();
Html.EnableUnobtrusiveJavaScript();
}
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/MicrosoftAjax.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/MicrosoftMvcAjax.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/MicrosoftMvcValidation.js")" type="text/javascript"></script>
#using (Html.BeginForm("Create", "Order", FormMethod.Post, new { #id = "create_order" }))
{
#Html.ValidationSummary(true, "Order creation was unsuccessful. Please correct the errors and try again.")
<div class="editor-label">
#Html.LabelFor(m => m.date)<req>*</req>
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.date, new { #id = "order_date" })<br />
#Html.ValidationMessageFor(m => m.date)
</div>
...
<script type="text/javascript">
$(document).ready(function () {
$("#addItem").click(function () {
var formData = $("#main_div").closest("form").serializeArray();
$.ajax({
url: "/IPO/BlankItemRow",
type: "POST",
//data: formData,
cache: false,
success: function (html) {
$("#editorRows").append(html);
//$.validator.uobtrusive.parseDynamicContent("form *");
//$("#editorRows").removeData("validator");
//$("#editorRows").removeData("unobtrusiveValidation");
//$.validator.unobtrusive.parse("#editorRows");
//$.validator.unobtrusive.parse("#create_ipo");
//$.validator.unobtrusive.parseDynamicContent($(this).first().closest("form"));
//$.validator.unobtrusive.parse($("#new_ipo_item"));
//$.validator.unobtrusive.parseElement($("#editorRows").find(".editRow:last").children().find("select"));
//$("#editorRows").find(".editRow:last").find("select").each(function () {
//alert($(this).attr("id"));
//$.validator.unobtrusive.parseElement($(this));
//$.validator.unobtrusive.parseDynamicContent($(this));
//$.validator.unobtrusive.parseDynamicContent($(this).attr("name"));
//});
//$("#editorRows").children().find(".editRows:last").find("*").each(function () {
// alert($(this).attr('id'));
//$.validator.unobtrusive.parseDynamicContent('input');
//});
//var form = $(this).closest("form").attr("id");
//$(form).removeData("validator");
//$(form).removeData("unobtrusiveValidation");
//$.validator.unobtrusive.parse(form);
}
});
return false;
});
});
</script>
Those are some of the things I tried, and nothing works.
I got the parseDynamicContent from Applying unobtrusive jquery validation to dynamic content in ASP.Net MVC. I tried it in every scenario I could think of, but still no luck.
I also tried the regular parse, removing validation from the form then applying it again, but still the newly added elements are not validated:
<div id="editorRows">
#foreach (var item in Model.items)
{
#Html.Partial("_NewItem", item)
}
</div>
... and my partial view would look something like this:
#model trackmeMvc.Models.Model.Item
#{
Layout = "";
Html.EnableClientValidation(true);
if (this.ViewContext.FormContext == null)
{
this.ViewContext.FormContext = new FormContext();
}
}
<div class="editRow">
#using (Html.BeginCollectionItem("order_items"))
{
#Html.DropDownListFor(m => m.item_id, #items, "None", new { #style = "width:205px;", #id = "ddlItems", #class="ddlItem", #name="ddlItemList" })
#Html.ValidationMessageFor(m => m.item_id)
...
}
</div>
So what's happening is, I have one empty item sent from the controller to the view by default, to show one empty row. That item is validated, but whatever comes after when I click add item, another row appears, from that partial, but I can't get it to validate. I tried to put the validation in the partial view, (before the document ready in the main form), and everything I read I applied, and it always ends up the same: validating the first row, and not the others. I tried the validation of Steven Sanderson done for that purpose - still no luck - even the validation for partials, found at this link
and the page that follows which is specific to partial validation...
What should I do to get this validation working?
Ok, I am going to start over with a new answer here.
Before you call $.validator.unobtrusive.parse, remove the original validator and unobtrusive validation from the form like so:
var form = $("#main_div").closest("form");
form.removeData('validator');
form.removeData('unobtrusiveValidation');
$.validator.unobtrusive.parse(form);
This same answer is documented here.
What worked for me was to re-apply the validator after the call to load the partial view. In my case, I'm using $.post().then() but you could do something similar with a .always() callback of an AJAX call.
$.post(url, model, function (data) {
//load the partial view
$("#Partial").html(data);
}).then(function () {
$("form").each(function () { $.data($(this)[0], 'validator', false); });
$.validator.unobtrusive.parse("form");
});
Related
Bounty
It's been awhile and I still have a couple outstanding questions. I hope by adding a bounty maybe these questions will get answered.
How do you use html helpers with knockout.js
Why was document ready needed to make it work(see first edit for more information)
How do I do something like this if I am using the knockout mapping with my view models? As I do not have a function due to the mapping.
function AppViewModel() {
// ... leave firstName, lastName, and fullName unchanged here ...
this.capitalizeLastName = function() {
var currentVal = this.lastName(); // Read the current value
this.lastName(currentVal.toUpperCase()); // Write back a modified value
};
I want to use plugins for instance I want to be able to rollback observables as if a user cancels a request I want to be able to go back to the last value. From my research this seems to be achieved by people making plugins like editables
How do I use something like that if I am using mapping? I really don’t want to go to a method where I have in my view manual mapping were I map each MVC viewMode field to a KO model field as I want as little inline javascript as possible and that just seems like double the work and that’s why I like that mapping.
I am concerned that to make this work easy (by using mapping) I will lose a lot of KO power but on the other hand I am concerned that manual mapping will just be a lot of work and will make my views contain too much information and might become in the future harder to maintain(say if I remove a property in the MVC model I have to move it also in the KO viewmodel)
Original Post
I am using asp.net mvc 3 and I looking into knockout as it looks pretty cool but I am having a hard time figuring out how it works with asp.net mvc especially view models.
For me right now I do something like this
public class CourseVM
{
public int CourseId { get; set; }
[Required(ErrorMessage = "Course name is required")]
[StringLength(40, ErrorMessage = "Course name cannot be this long.")]
public string CourseName{ get; set; }
public List<StudentVm> StudentViewModels { get; set; }
}
I would have a Vm that has some basic properties like CourseName and it will have some simple validation on top of it. The Vm model might contain other view models in it as well if needed.
I would then pass this Vm to the View were I would use html helpers to help me display it to the user.
#Html.TextBoxFor(x => x.CourseName)
I might have some foreach loops or something to get the data out of the collection of Student View Models.
Then when I would submit the form I would use jquery and serialize array and send it to a controller action method that would bind it back to the viewmodel.
With knockout.js it is all different as you now got viewmodels for it and from all the examples I seen they don't use html helpers.
How do you use these 2 features of MVC with knockout.js?
I found this video and it briefly(last few minutes of the video # 18:48) goes into a way to use viewmodels by basically having an inline script that has the knockout.js viewmodel that gets assigned the values in the ViewModel.
Is this the only way to do it? How about in my example with having a collection of viewmodels in it? Do I have to have a foreach loop or something to extract all the values out and assign it into knockout?
As for html helpers the video says nothing about them.
These are the 2 areas that confuses the heck out of me as not many people seem to talk about it and it leaves me confused of how the initial values and everything is getting to the view when ever example is just some hard-coded value example.
Edit
I am trying what Darin Dimitrov has suggested and this seems to work(I had to make some changes to his code though). Not sure why I had to use document ready but somehow everything was not ready without it.
#model MvcApplication1.Models.Test
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Index</title>
<script src="../../Scripts/jquery-1.5.1.js" type="text/javascript"></script>
<script src="../../Scripts/knockout-2.1.0.js" type="text/javascript"></script>
<script src="../../Scripts/knockout.mapping-latest.js" type="text/javascript"></script>
<script type="text/javascript">
$(function()
{
var model = #Html.Raw(Json.Encode(Model));
// Activates knockout.js
ko.applyBindings(model);
});
</script>
</head>
<body>
<div>
<p>First name: <strong data-bind="text: FirstName"></strong></p>
<p>Last name: <strong data-bind="text: LastName"></strong></p>
#Model.FirstName , #Model.LastName
</div>
</body>
</html>
I had to wrap it around a jquery document ready to make it work.
I also get this warning. Not sure what it is all about.
Warning 1 Conditional compilation is turned off -> #Html.Raw
So I have a starting point I guess at least will update when I done some more playing around and how this works.
I am trying to go through the interactive tutorials but use the a ViewModel instead.
Not sure how to tackle these parts yet
function AppViewModel() {
this.firstName = ko.observable("Bert");
this.lastName = ko.observable("Bertington");
}
or
function AppViewModel() {
// ... leave firstName, lastName, and fullName unchanged here ...
this.capitalizeLastName = function() {
var currentVal = this.lastName(); // Read the current value
this.lastName(currentVal.toUpperCase()); // Write back a modified value
};
Edit 2
I been able to figure out the first problem. No clue about the second problem. Yet though. Anyone got any ideas?
#model MvcApplication1.Models.Test
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Index</title>
<script src="../../Scripts/jquery-1.5.1.js" type="text/javascript"></script>
<script src="../../Scripts/knockout-2.1.0.js" type="text/javascript"></script>
<script src="../../Scripts/knockout.mapping-latest.js" type="text/javascript"></script>
<script type="text/javascript">
$(function()
{
var model = #Html.Raw(Json.Encode(Model));
var viewModel = ko.mapping.fromJS(model);
ko.applyBindings(viewModel);
});
</script>
</head>
<body>
<div>
#*grab values from the view model directly*#
<p>First name: <strong data-bind="text: FirstName"></strong></p>
<p>Last name: <strong data-bind="text: LastName"></strong></p>
#*grab values from my second view model that I made*#
<p>SomeOtherValue <strong data-bind="text: Test2.SomeOtherValue"></strong></p>
<p>Another <strong data-bind="text: Test2.Another"></strong></p>
#*allow changes to all the values that should be then sync the above values.*#
<p>First name: <input data-bind="value: FirstName" /></p>
<p>Last name: <input data-bind="value: LastName" /></p>
<p>SomeOtherValue <input data-bind="value: Test2.SomeOtherValue" /></p>
<p>Another <input data-bind="value: Test2.Another" /></p>
#* seeing if I can do it with p tags and see if they all update.*#
<p data-bind="foreach: Test3">
<strong data-bind="text: Test3Value"></strong>
</p>
#*took my 3rd view model that is in a collection and output all values as a textbox*#
<table>
<thead><tr>
<th>Test3</th>
</tr></thead>
<tbody data-bind="foreach: Test3">
<tr>
<td>
<strong data-bind="text: Test3Value"></strong>
<input type="text" data-bind="value: Test3Value"/>
</td>
</tr>
</tbody>
</table>
Controller
public ActionResult Index()
{
Test2 test2 = new Test2
{
Another = "test",
SomeOtherValue = "test2"
};
Test vm = new Test
{
FirstName = "Bob",
LastName = "N/A",
Test2 = test2,
};
for (int i = 0; i < 10; i++)
{
Test3 test3 = new Test3
{
Test3Value = i.ToString()
};
vm.Test3.Add(test3);
}
return View(vm);
}
I think I have summarized all your questions, if I missed something please let me know (If you could summarize up all your questions in one place would be nice =))
Note. Compatibility with the ko.editable plug-in added
Download the full code
How do you use html helpers with knockout.js
This is easy:
#Html.TextBoxFor(model => model.CourseId, new { data_bind = "value: CourseId" })
Where:
value: CourseId indicates that you are binding the value property of the input control with the CourseId property from your model and your script model
The result is:
<input data-bind="value: CourseId" data-val="true" data-val-number="The field CourseId must be a number." data-val-required="The CourseId field is required." id="CourseId" name="CourseId" type="text" value="12" />
Why was document ready needed to make it work(see first edit for more information)
I do not understand yet why you need to use the ready event to serialize the model, but it seems that it is simply required (Not to worry about it though)
How do I do something like this if I am using the knockout mapping with my view models? As I do not have a function due to the mapping.
If I understand correctly you need to append a new method to the KO model, well that's easy merging models
For more info, in the section -Mapping from different sources-
function viewModel() {
this.addStudent = function () {
alert("de");
};
};
$(function () {
var jsonModel = '#Html.Raw(JsonConvert.SerializeObject(this.Model))';
var mvcModel = ko.mapping.fromJSON(jsonModel);
var myViewModel = new viewModel();
var g = ko.mapping.fromJS(myViewModel, mvcModel);
ko.applyBindings(g);
});
About the warning you were receiveing
Warning 1 Conditional compilation is turned off -> #Html.Raw
You need to use quotes
Compatibility with the ko.editable plug-in
I thought it was going to be more complex, but it turns out that the integration is really easy, in order to make your model editable just add the following line: (remember that in this case I am using a mixed model, from server and adding extension in client and the editable simply works... it's great):
ko.editable(g);
ko.applyBindings(g);
From here you just need to play with your bindings using the extensions added by the plug-in, for example, I have a button to start editing my fields like this and in this button I start the edit process:
this.editMode = function () {
this.isInEditMode(!this.isInEditMode());
this.beginEdit();
};
Then I have commit and cancel buttons with the following code:
this.executeCommit = function () {
this.commit();
this.isInEditMode(false);
};
this.executeRollback = function () {
if (this.hasChanges()) {
if (confirm("Are you sure you want to discard the changes?")) {
this.rollback();
this.isInEditMode(false);
}
}
else {
this.rollback();
this.isInEditMode(false);
}
};
And finally, I have one field to indicate whether the fields are in edit mode or not, this is just to bind the enable property.
this.isInEditMode = ko.observable(false);
About your array question
I might have some foreach loops or something to get the data out of the collection of Student View Models.
Then when I would submit the form I would use jquery and serialize array and send it to a controller action method that would bind it back to the viewmodel.
You can do the same with KO, in the following example, I will create the following output:
Basically here, you have two lists, created using Helpers and binded with KO, they have a dblClick event binded that when fired, remove the selected item from the current list and add it to the other list, when you post to the Controller, the content of each list is sent as JSON data and re-attached to the server model
Nuggets:
Newtonsoft
jQuery
knockoutjs
Knockout.Mapping
External scripts.
Controller code
[HttpGet]
public ActionResult Index()
{
var m = new CourseVM { CourseId = 12, CourseName = ".Net" };
m.StudentViewModels.Add(new StudentVm { ID = 545, Name = "Name from server", Lastname = "last name from server" });
return View(m);
}
[HttpPost]
public ActionResult Index(CourseVM model)
{
if (!string.IsNullOrWhiteSpace(model.StudentsSerialized))
{
model.StudentViewModels = JsonConvert.DeserializeObject<List<StudentVm>>(model.StudentsSerialized);
model.StudentsSerialized = string.Empty;
}
if (!string.IsNullOrWhiteSpace(model.SelectedStudentsSerialized))
{
model.SelectedStudents = JsonConvert.DeserializeObject<List<StudentVm>>(model.SelectedStudentsSerialized);
model.SelectedStudentsSerialized = string.Empty;
}
return View(model);
}
Model
public class CourseVM
{
public CourseVM()
{
this.StudentViewModels = new List<StudentVm>();
this.SelectedStudents = new List<StudentVm>();
}
public int CourseId { get; set; }
[Required(ErrorMessage = "Course name is required")]
[StringLength(100, ErrorMessage = "Course name cannot be this long.")]
public string CourseName { get; set; }
public List<StudentVm> StudentViewModels { get; set; }
public List<StudentVm> SelectedStudents { get; set; }
public string StudentsSerialized { get; set; }
public string SelectedStudentsSerialized { get; set; }
}
public class StudentVm
{
public int ID { get; set; }
public string Name { get; set; }
public string Lastname { get; set; }
}
CSHTML page
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>CourseVM</legend>
<div>
<div class="editor-label">
#Html.LabelFor(model => model.CourseId)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.CourseId, new { data_bind = "enable: isInEditMode, value: CourseId" })
#Html.ValidationMessageFor(model => model.CourseId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.CourseName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.CourseName, new { data_bind = "enable: isInEditMode, value: CourseName" })
#Html.ValidationMessageFor(model => model.CourseName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.StudentViewModels);
</div>
<div class="editor-field">
#Html.ListBoxFor(
model => model.StudentViewModels,
new SelectList(this.Model.StudentViewModels, "ID", "Name"),
new
{
style = "width: 37%;",
data_bind = "enable: isInEditMode, options: StudentViewModels, optionsText: 'Name', value: leftStudentSelected, event: { dblclick: moveFromLeftToRight }"
}
)
#Html.ListBoxFor(
model => model.SelectedStudents,
new SelectList(this.Model.SelectedStudents, "ID", "Name"),
new
{
style = "width: 37%;",
data_bind = "enable: isInEditMode, options: SelectedStudents, optionsText: 'Name', value: rightStudentSelected, event: { dblclick: moveFromRightToLeft }"
}
)
</div>
#Html.HiddenFor(model => model.CourseId, new { data_bind="value: CourseId" })
#Html.HiddenFor(model => model.CourseName, new { data_bind="value: CourseName" })
#Html.HiddenFor(model => model.StudentsSerialized, new { data_bind = "value: StudentsSerialized" })
#Html.HiddenFor(model => model.SelectedStudentsSerialized, new { data_bind = "value: SelectedStudentsSerialized" })
</div>
<p>
<input type="submit" value="Save" data-bind="enable: !isInEditMode()" />
<button data-bind="enable: !isInEditMode(), click: editMode">Edit mode</button><br />
<div>
<button data-bind="enable: isInEditMode, click: addStudent">Add Student</button>
<button data-bind="enable: hasChanges, click: executeCommit">Commit</button>
<button data-bind="enable: isInEditMode, click: executeRollback">Cancel</button>
</div>
</p>
</fieldset>
}
Scripts
<script src="#Url.Content("~/Scripts/jquery-1.7.2.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/knockout-2.1.0.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/knockout.mapping-latest.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/ko.editables.js")" type="text/javascript"></script>
<script type="text/javascript">
var g = null;
function ViewModel() {
this.addStudent = function () {
this.StudentViewModels.push(new Student(25, "my name" + new Date(), "my last name"));
this.serializeLists();
};
this.serializeLists = function () {
this.StudentsSerialized(ko.toJSON(this.StudentViewModels));
this.SelectedStudentsSerialized(ko.toJSON(this.SelectedStudents));
};
this.leftStudentSelected = ko.observable();
this.rightStudentSelected = ko.observable();
this.moveFromLeftToRight = function () {
this.SelectedStudents.push(this.leftStudentSelected());
this.StudentViewModels.remove(this.leftStudentSelected());
this.serializeLists();
};
this.moveFromRightToLeft = function () {
this.StudentViewModels.push(this.rightStudentSelected());
this.SelectedStudents.remove(this.rightStudentSelected());
this.serializeLists();
};
this.isInEditMode = ko.observable(false);
this.executeCommit = function () {
this.commit();
this.isInEditMode(false);
};
this.executeRollback = function () {
if (this.hasChanges()) {
if (confirm("Are you sure you want to discard the changes?")) {
this.rollback();
this.isInEditMode(false);
}
}
else {
this.rollback();
this.isInEditMode(false);
}
};
this.editMode = function () {
this.isInEditMode(!this.isInEditMode());
this.beginEdit();
};
}
function Student(id, name, lastName) {
this.ID = id;
this.Name = name;
this.LastName = lastName;
}
$(function () {
var jsonModel = '#Html.Raw(JsonConvert.SerializeObject(this.Model))';
var mvcModel = ko.mapping.fromJSON(jsonModel);
var myViewModel = new ViewModel();
g = ko.mapping.fromJS(myViewModel, mvcModel);
g.StudentsSerialized(ko.toJSON(g.StudentViewModels));
g.SelectedStudentsSerialized(ko.toJSON(g.SelectedStudents));
ko.editable(g);
ko.applyBindings(g);
});
</script>
Note: I just added these lines:
#Html.HiddenFor(model => model.CourseId, new { data_bind="value: CourseId" })
#Html.HiddenFor(model => model.CourseName, new { data_bind="value: CourseName" })
Because when I submit the form my fields are disabled, so the values were not transmitted to the server, that's why I added a couple of hidden fields to do the trick
You could serialize your ASP.NET MVC view model into a javascript variable:
#model CourseVM
<script type="text/javascript">
var model = #Html.Raw(Json.Encode(Model));
// go ahead and use the model javascript variable to bind with ko
</script>
There are lots of examples in the knockout documentation that you could go through.
To achieve the additional computed properties after server mapping you will need to further enhance your viewmodels on the client side.
For example:
var viewModel = ko.mapping.fromJS(model);
viewModel.capitalizedName = ko.computed(function() {...}, viewModel);
So everytime you map from raw JSON you would need to reapply the computed properties.
Additionally the mapping plugin provides the ability to incrementally update a viewmodel as opposed to recreating it every time you go back and forth (use an additional parameter in fromJS):
// Every time data is received from the server:
ko.mapping.fromJS(data, viewModel);
And that executes an incremental data update on your model of just properties that are mapped. You can read more about that in the mapping documentation
You mentioned in the comments on Darin's answer the FluentJSON package. I'm the author of that, but its use case is more specific than ko.mapping. I would generally only use it if your viewmodels are one way (ie. server -> client) and then data is posted back in some different format (or not at all). Or if your javascript viewmodel needs to be in a substantially different format from your server model.
I am using a WebGrid, which i bind to a List of objects containing information about deliveries. I want to be able to filter said WebGrid using a DropDownList containing Customers. When I select a Customer in the DropDownList the change-method sends an Ajax call which is supposed to get the new items for the WebGrid.
The call is successful, but nothing happens. The WebGrid doesn't change at all. I even tried sending an Ajax call identical to the ones sent when sorting the list. But nothing happens.
What am I doing wrong here?
ViewModel:
public class DeliveriesViewModel : PageViewModel<DeliveriesPage>
{
public DeliveriesViewModel(DeliveriesPage currentPage) : base(currentPage)
{
DeliveryItems = new List<DeliveryItem>();
}
public List<DeliveryItem> DeliveryItems { get; set; }
public List<SelectListItem> Customers { get; set; }
}
Controller:
public ActionResult Index(DeliveriesPage currentPage, string customer)
{
var model = new DeliveriesViewModel(currentPage);
model.Customers = _deliveryService.GetCustomers();
model.DeliveryItems = customer == null ? _deliveryService.GetDeliveryItems() : _deliveryService.GetDeliveryItems(customer);
return View(model);
}
View:
#model DeliveriesViewModel
<h1>#Model.CurrentPage.PageName</h1>
#Html.DropDownList("customerDropDown", Model.Customers)
#Html.Partial("_grid", Model)
<script type="text/javascript">
$("#customerDropDown").change(function () {
$.get("?Customer="+$("#customerDropDown").val());
});
</script>
_grid partial View:
#model DeliveriesViewModel
#{
var grid = new WebGrid(Model.DeliveryItems, canPage:true, canSort: true, ajaxUpdateContainerId:"container-grid");
}
<div id="container-grid">
#grid.GetHtml(
columns: grid.Columns(
grid.Column("DeliveryId"),
grid.Column("CustomerName"),
grid.Column("ShipNumber"),
grid.Column("ShipName"),
grid.Column("Product"),
grid.Column("PlannedWeight"),
grid.Column("TotalWeight"),
grid.Column("ShipStatus"),
grid.Column("TransportTo"),
grid.Column("TransportFrom"),
grid.Column("RevDate"),
grid.Column("ShipStemDept"),
grid.Column("ShipRealDept"),
grid.Column("ShipStemArr"),
grid.Column("ShipRealArr"),
grid.Column("TranspMonth"),
grid.Column("TranspYear")
))
</div>
$.get("?Customer="+$("#customerDropDown").val()); sends an AJAX call to the server and that's about it. You haven't subscribed to the success callback in order to update your DOM. So it is not surprising that nothing happens.
So try like this:
<script type="text/javascript">
$('#customerDropDown').change(function () {
var url = '#Url.Action("index")';
$.get(url, { customer: $(this).val() }, function(result) {
$('#container-grid').html(result);
});
});
</script>
Notice how I have used the UrlHelper to calculate the correct url to your controller action, I have then passed the selected value of the dropdown as second parameter to the $.get method and last but not least I have subscribed to the success callback of the ajax request and updated the #container-grid div with the results returned by the controller action.
Also since you are calling this action with AJAX, you should return only a PartialView from it and not an entire View. This partial view should contain your grid. Otherwise you will end up with duplicate layout injected into the div.
Model
public class EmployerTestResultsModel
{
[Display(Name = "Employer List")]
public IEnumerable<SelectListItem> EmployerList { get; set; }
[Required]
public string SelectedEmployerId { get; set; }
public List<EmployerTestResultsModel> EmployerGrid { get; set; }
public Int64 FileId { get; set; }
[Display(Name = "File Name")]
public string FileName { get; set; }
[DataType(DataType.Date)]
public DateTime Date { get; set; }
[Display(Name = "Scheme Id")]
public string SchemeId { get; set; }
public string Status { get; set; }
[Display(Name = "Validation Error Report")]
public string ValidationErrorReport { get; set; }
}
controller
[HttpGet]
public ActionResult EmployerTestResults()
{
EmployerTestResultsModel model = new EmployerTestResultsModel();
ViewBag.HideSection = true;
model.EmployerList = (from d in _context.Employers
select new System.Web.Mvc.SelectListItem
{
Text = d.EmployerName,
Value = d.EmployerId
});
model.EmployerGrid = (from efd in _context.EmployerFileDatas
// join efhd in _context.EmployerFileHeaderDetails on efd.FileDataIdentityKey equals efhd.FileDataIdentityKey
orderby efd.EmployerId , efd.Timestamp
select new EmployerTestResultsModel
{
FileId = efd.FileDataIdentityKey,
FileName = efd.FileName,
Date = efd.Timestamp,
//SchemeId = efhd.SchemeId,
Status = efd.ValidationStatus,
ValidationErrorReport = "View"
}).ToList();
return View("EmployerTestResults", model);
}
View:
#model EFITestHarness.Models.EmployerTestResultsModel
#using System.Web.Helpers;
#{
ViewBag.Title = "EmployerTestResults";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<script src="~/Scripts/jquery-1.7.1.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
#using (Html.BeginForm("EmployerTestResults", "Home", FormMethod.Post, new { #class = "form-horizontal" }))
{
<div class="text-danger" style="font-size:large;">
#Html.ValidationSummary(true)
</div>
<div class="form-group ">
#Html.LabelFor(s => s.EmployerList, null, new { #class = "col-md-2 control-label" })
<div class="col-md-3">
#Html.DropDownListFor(s => s.SelectedEmployerId, Model.EmployerList, "----All----", new { style = "width:250px", id = "ddl", #class = "dropdown1" })
#Html.ValidationMessageFor(s => s.EmployerList, null, new { #class = "text-danger" })
</div>
</div>
<div id="EmployeeViewGrid">
#Html.Partial("~/Views/EmployerView.cshtml", Model.EmployerGrid)
</div>
}
<script type="text/javascript">
$('#ddl').change(function (e) {
var employer = $('#ddl').val();
$.get('#Url.Action("Filter")', { id: employer }, function (result) {
$('#EmployeeViewGrid').html(result);
});
e.preventDefault();
});
</script>
Controller:
[HttpGet]
public ActionResult Filter(string id)
{
EmployerTestResultsModel model = new EmployerTestResultsModel();
List<EmployerTestResultsModel> objEmployerDetails = new List<EmployerTestResultsModel>();
objEmployerDetails = _repository.getEmployerDetails(id);
model.EmployerGrid = objEmployerDetails;
return PartialView("~/Views/EmployerView.cshtml", model.EmployerGrid);
}
partial View:
#model IEnumerable<EFITestHarness.Models.EmployerTestResultsModel>
#using System.Web.Helpers;
#{
ViewBag.Title = "EmployerTestResultsModel";
//Layout = "~/Views/Shared/_Layout.cshtml";
}
<script src="~/Scripts/jquery-1.7.1.js"></script>
<div id="gridposition" style="overflow: scroll; height: 300px; overflow-x: hidden;">
#{
var grid = new WebGrid(Model, canPage: true, rowsPerPage: 5, selectionFieldName: "selectedRow", ajaxUpdateContainerId: "gridposition"); grid.Pager(WebGridPagerModes.NextPrevious);
#grid.GetHtml(tableStyle: "webGrid",
footerStyle: "foot",
headerStyle: "webGridHeader",
alternatingRowStyle: "webGridAlt",
htmlAttributes: new { id = "positionGrid" },
selectedRowStyle: "select",
fillEmptyRows: true,
columns: grid.Columns(
grid.Column("FileName"), //the model fields to display
grid.Column("Date"),
grid.Column("SchemeId"),
grid.Column("Status"),
grid.Column("ValidationErrorReport", format: (item => Html.ActionLink((string)(#item.ValidationErrorReport).ToString(), "EmployerValidationResults", new { FileId = #item.FileId, #style = "color:blue;" })))
))
}
</div>
This is my CreateUnit.cshtml where I set the templateId into the hidden input field with the id 'TemplateId'. When I confirm the dialog the asp.net mvc controller 'UnitController' and its action method 'Create' is executed which should pass the UnitViewModel. Inside the UnitViewModel the model binder has set the name property to the value I entered. But the templateId from the hidden field is missing.
Why this?
#model ITMS.Web.Models.UnitViewModel
#*Remote Validation*#
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
var templateId = $('#MyDialog').data('templateIdKey');
$('#TemplateId').text(templateId);
});
</script>
#using (Html.BeginForm("Create", "Unit"))
{
#Html.ValidationSummary(false)
<p class="editor-label">#Html.LabelFor(model => model.Name)</p>
<p class="editor-field">#Html.EditorFor(model => model.Name)</p>
<p class="editor-field">#Html.ValidationMessageFor(model => model.Name)</p>
#Html.HiddenFor(x => x.TemplateId)
}
[HttpPost]
public ActionResult Create(UnitViewModel unitViewModel)
{
if (ModelState.IsValid && !_dataProvider.UnitExists(unitViewModel.Name, unitViewModel.TemplateId))
{
Unit unit = Mapper.Map<UnitViewModel, Unit>(unitViewModel);
_dataProvider.AddUnit(unit);
return new JsonNetResult(new { success = true });
}
ModelState.AddModelError("Name", "This name already exists.");
return PartialView(unitViewModel);
}
public class UnitViewModel
{
[Required(ErrorMessage = "Name must not be empty.")]
[StringLength(30, ErrorMessage = "Enter max. 30 chars for a name.")]
[Remote("UnitExists", "Unit", ErrorMessage = "This name already exists.",AdditionalFields="TemplateId")]
[JsonProperty("title")]
public string Name { get; set; }
public Nullable<int> ParentId { get; set; }
[HiddenInput(DisplayValue = false)]
public int TemplateId { get; set; }
}
Instead of #Html.HiddenFor(x => x.TemplateId) in your view, use #Html.EditorFor(x => x.TemplateId) instead. The HTML will be rendered as a hidden input field.
Instead of:
$('#TemplateId').text(templateId);
I wrote this and now the TemplateId is sent with the form :)
$("[name='TemplateId']").val(templateId);
stupid from me the input field has no text ...
Bounty
It's been awhile and I still have a couple outstanding questions. I hope by adding a bounty maybe these questions will get answered.
How do you use html helpers with knockout.js
Why was document ready needed to make it work(see first edit for more information)
How do I do something like this if I am using the knockout mapping with my view models? As I do not have a function due to the mapping.
function AppViewModel() {
// ... leave firstName, lastName, and fullName unchanged here ...
this.capitalizeLastName = function() {
var currentVal = this.lastName(); // Read the current value
this.lastName(currentVal.toUpperCase()); // Write back a modified value
};
I want to use plugins for instance I want to be able to rollback observables as if a user cancels a request I want to be able to go back to the last value. From my research this seems to be achieved by people making plugins like editables
How do I use something like that if I am using mapping? I really don’t want to go to a method where I have in my view manual mapping were I map each MVC viewMode field to a KO model field as I want as little inline javascript as possible and that just seems like double the work and that’s why I like that mapping.
I am concerned that to make this work easy (by using mapping) I will lose a lot of KO power but on the other hand I am concerned that manual mapping will just be a lot of work and will make my views contain too much information and might become in the future harder to maintain(say if I remove a property in the MVC model I have to move it also in the KO viewmodel)
Original Post
I am using asp.net mvc 3 and I looking into knockout as it looks pretty cool but I am having a hard time figuring out how it works with asp.net mvc especially view models.
For me right now I do something like this
public class CourseVM
{
public int CourseId { get; set; }
[Required(ErrorMessage = "Course name is required")]
[StringLength(40, ErrorMessage = "Course name cannot be this long.")]
public string CourseName{ get; set; }
public List<StudentVm> StudentViewModels { get; set; }
}
I would have a Vm that has some basic properties like CourseName and it will have some simple validation on top of it. The Vm model might contain other view models in it as well if needed.
I would then pass this Vm to the View were I would use html helpers to help me display it to the user.
#Html.TextBoxFor(x => x.CourseName)
I might have some foreach loops or something to get the data out of the collection of Student View Models.
Then when I would submit the form I would use jquery and serialize array and send it to a controller action method that would bind it back to the viewmodel.
With knockout.js it is all different as you now got viewmodels for it and from all the examples I seen they don't use html helpers.
How do you use these 2 features of MVC with knockout.js?
I found this video and it briefly(last few minutes of the video # 18:48) goes into a way to use viewmodels by basically having an inline script that has the knockout.js viewmodel that gets assigned the values in the ViewModel.
Is this the only way to do it? How about in my example with having a collection of viewmodels in it? Do I have to have a foreach loop or something to extract all the values out and assign it into knockout?
As for html helpers the video says nothing about them.
These are the 2 areas that confuses the heck out of me as not many people seem to talk about it and it leaves me confused of how the initial values and everything is getting to the view when ever example is just some hard-coded value example.
Edit
I am trying what Darin Dimitrov has suggested and this seems to work(I had to make some changes to his code though). Not sure why I had to use document ready but somehow everything was not ready without it.
#model MvcApplication1.Models.Test
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Index</title>
<script src="../../Scripts/jquery-1.5.1.js" type="text/javascript"></script>
<script src="../../Scripts/knockout-2.1.0.js" type="text/javascript"></script>
<script src="../../Scripts/knockout.mapping-latest.js" type="text/javascript"></script>
<script type="text/javascript">
$(function()
{
var model = #Html.Raw(Json.Encode(Model));
// Activates knockout.js
ko.applyBindings(model);
});
</script>
</head>
<body>
<div>
<p>First name: <strong data-bind="text: FirstName"></strong></p>
<p>Last name: <strong data-bind="text: LastName"></strong></p>
#Model.FirstName , #Model.LastName
</div>
</body>
</html>
I had to wrap it around a jquery document ready to make it work.
I also get this warning. Not sure what it is all about.
Warning 1 Conditional compilation is turned off -> #Html.Raw
So I have a starting point I guess at least will update when I done some more playing around and how this works.
I am trying to go through the interactive tutorials but use the a ViewModel instead.
Not sure how to tackle these parts yet
function AppViewModel() {
this.firstName = ko.observable("Bert");
this.lastName = ko.observable("Bertington");
}
or
function AppViewModel() {
// ... leave firstName, lastName, and fullName unchanged here ...
this.capitalizeLastName = function() {
var currentVal = this.lastName(); // Read the current value
this.lastName(currentVal.toUpperCase()); // Write back a modified value
};
Edit 2
I been able to figure out the first problem. No clue about the second problem. Yet though. Anyone got any ideas?
#model MvcApplication1.Models.Test
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Index</title>
<script src="../../Scripts/jquery-1.5.1.js" type="text/javascript"></script>
<script src="../../Scripts/knockout-2.1.0.js" type="text/javascript"></script>
<script src="../../Scripts/knockout.mapping-latest.js" type="text/javascript"></script>
<script type="text/javascript">
$(function()
{
var model = #Html.Raw(Json.Encode(Model));
var viewModel = ko.mapping.fromJS(model);
ko.applyBindings(viewModel);
});
</script>
</head>
<body>
<div>
#*grab values from the view model directly*#
<p>First name: <strong data-bind="text: FirstName"></strong></p>
<p>Last name: <strong data-bind="text: LastName"></strong></p>
#*grab values from my second view model that I made*#
<p>SomeOtherValue <strong data-bind="text: Test2.SomeOtherValue"></strong></p>
<p>Another <strong data-bind="text: Test2.Another"></strong></p>
#*allow changes to all the values that should be then sync the above values.*#
<p>First name: <input data-bind="value: FirstName" /></p>
<p>Last name: <input data-bind="value: LastName" /></p>
<p>SomeOtherValue <input data-bind="value: Test2.SomeOtherValue" /></p>
<p>Another <input data-bind="value: Test2.Another" /></p>
#* seeing if I can do it with p tags and see if they all update.*#
<p data-bind="foreach: Test3">
<strong data-bind="text: Test3Value"></strong>
</p>
#*took my 3rd view model that is in a collection and output all values as a textbox*#
<table>
<thead><tr>
<th>Test3</th>
</tr></thead>
<tbody data-bind="foreach: Test3">
<tr>
<td>
<strong data-bind="text: Test3Value"></strong>
<input type="text" data-bind="value: Test3Value"/>
</td>
</tr>
</tbody>
</table>
Controller
public ActionResult Index()
{
Test2 test2 = new Test2
{
Another = "test",
SomeOtherValue = "test2"
};
Test vm = new Test
{
FirstName = "Bob",
LastName = "N/A",
Test2 = test2,
};
for (int i = 0; i < 10; i++)
{
Test3 test3 = new Test3
{
Test3Value = i.ToString()
};
vm.Test3.Add(test3);
}
return View(vm);
}
I think I have summarized all your questions, if I missed something please let me know (If you could summarize up all your questions in one place would be nice =))
Note. Compatibility with the ko.editable plug-in added
Download the full code
How do you use html helpers with knockout.js
This is easy:
#Html.TextBoxFor(model => model.CourseId, new { data_bind = "value: CourseId" })
Where:
value: CourseId indicates that you are binding the value property of the input control with the CourseId property from your model and your script model
The result is:
<input data-bind="value: CourseId" data-val="true" data-val-number="The field CourseId must be a number." data-val-required="The CourseId field is required." id="CourseId" name="CourseId" type="text" value="12" />
Why was document ready needed to make it work(see first edit for more information)
I do not understand yet why you need to use the ready event to serialize the model, but it seems that it is simply required (Not to worry about it though)
How do I do something like this if I am using the knockout mapping with my view models? As I do not have a function due to the mapping.
If I understand correctly you need to append a new method to the KO model, well that's easy merging models
For more info, in the section -Mapping from different sources-
function viewModel() {
this.addStudent = function () {
alert("de");
};
};
$(function () {
var jsonModel = '#Html.Raw(JsonConvert.SerializeObject(this.Model))';
var mvcModel = ko.mapping.fromJSON(jsonModel);
var myViewModel = new viewModel();
var g = ko.mapping.fromJS(myViewModel, mvcModel);
ko.applyBindings(g);
});
About the warning you were receiveing
Warning 1 Conditional compilation is turned off -> #Html.Raw
You need to use quotes
Compatibility with the ko.editable plug-in
I thought it was going to be more complex, but it turns out that the integration is really easy, in order to make your model editable just add the following line: (remember that in this case I am using a mixed model, from server and adding extension in client and the editable simply works... it's great):
ko.editable(g);
ko.applyBindings(g);
From here you just need to play with your bindings using the extensions added by the plug-in, for example, I have a button to start editing my fields like this and in this button I start the edit process:
this.editMode = function () {
this.isInEditMode(!this.isInEditMode());
this.beginEdit();
};
Then I have commit and cancel buttons with the following code:
this.executeCommit = function () {
this.commit();
this.isInEditMode(false);
};
this.executeRollback = function () {
if (this.hasChanges()) {
if (confirm("Are you sure you want to discard the changes?")) {
this.rollback();
this.isInEditMode(false);
}
}
else {
this.rollback();
this.isInEditMode(false);
}
};
And finally, I have one field to indicate whether the fields are in edit mode or not, this is just to bind the enable property.
this.isInEditMode = ko.observable(false);
About your array question
I might have some foreach loops or something to get the data out of the collection of Student View Models.
Then when I would submit the form I would use jquery and serialize array and send it to a controller action method that would bind it back to the viewmodel.
You can do the same with KO, in the following example, I will create the following output:
Basically here, you have two lists, created using Helpers and binded with KO, they have a dblClick event binded that when fired, remove the selected item from the current list and add it to the other list, when you post to the Controller, the content of each list is sent as JSON data and re-attached to the server model
Nuggets:
Newtonsoft
jQuery
knockoutjs
Knockout.Mapping
External scripts.
Controller code
[HttpGet]
public ActionResult Index()
{
var m = new CourseVM { CourseId = 12, CourseName = ".Net" };
m.StudentViewModels.Add(new StudentVm { ID = 545, Name = "Name from server", Lastname = "last name from server" });
return View(m);
}
[HttpPost]
public ActionResult Index(CourseVM model)
{
if (!string.IsNullOrWhiteSpace(model.StudentsSerialized))
{
model.StudentViewModels = JsonConvert.DeserializeObject<List<StudentVm>>(model.StudentsSerialized);
model.StudentsSerialized = string.Empty;
}
if (!string.IsNullOrWhiteSpace(model.SelectedStudentsSerialized))
{
model.SelectedStudents = JsonConvert.DeserializeObject<List<StudentVm>>(model.SelectedStudentsSerialized);
model.SelectedStudentsSerialized = string.Empty;
}
return View(model);
}
Model
public class CourseVM
{
public CourseVM()
{
this.StudentViewModels = new List<StudentVm>();
this.SelectedStudents = new List<StudentVm>();
}
public int CourseId { get; set; }
[Required(ErrorMessage = "Course name is required")]
[StringLength(100, ErrorMessage = "Course name cannot be this long.")]
public string CourseName { get; set; }
public List<StudentVm> StudentViewModels { get; set; }
public List<StudentVm> SelectedStudents { get; set; }
public string StudentsSerialized { get; set; }
public string SelectedStudentsSerialized { get; set; }
}
public class StudentVm
{
public int ID { get; set; }
public string Name { get; set; }
public string Lastname { get; set; }
}
CSHTML page
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>CourseVM</legend>
<div>
<div class="editor-label">
#Html.LabelFor(model => model.CourseId)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.CourseId, new { data_bind = "enable: isInEditMode, value: CourseId" })
#Html.ValidationMessageFor(model => model.CourseId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.CourseName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.CourseName, new { data_bind = "enable: isInEditMode, value: CourseName" })
#Html.ValidationMessageFor(model => model.CourseName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.StudentViewModels);
</div>
<div class="editor-field">
#Html.ListBoxFor(
model => model.StudentViewModels,
new SelectList(this.Model.StudentViewModels, "ID", "Name"),
new
{
style = "width: 37%;",
data_bind = "enable: isInEditMode, options: StudentViewModels, optionsText: 'Name', value: leftStudentSelected, event: { dblclick: moveFromLeftToRight }"
}
)
#Html.ListBoxFor(
model => model.SelectedStudents,
new SelectList(this.Model.SelectedStudents, "ID", "Name"),
new
{
style = "width: 37%;",
data_bind = "enable: isInEditMode, options: SelectedStudents, optionsText: 'Name', value: rightStudentSelected, event: { dblclick: moveFromRightToLeft }"
}
)
</div>
#Html.HiddenFor(model => model.CourseId, new { data_bind="value: CourseId" })
#Html.HiddenFor(model => model.CourseName, new { data_bind="value: CourseName" })
#Html.HiddenFor(model => model.StudentsSerialized, new { data_bind = "value: StudentsSerialized" })
#Html.HiddenFor(model => model.SelectedStudentsSerialized, new { data_bind = "value: SelectedStudentsSerialized" })
</div>
<p>
<input type="submit" value="Save" data-bind="enable: !isInEditMode()" />
<button data-bind="enable: !isInEditMode(), click: editMode">Edit mode</button><br />
<div>
<button data-bind="enable: isInEditMode, click: addStudent">Add Student</button>
<button data-bind="enable: hasChanges, click: executeCommit">Commit</button>
<button data-bind="enable: isInEditMode, click: executeRollback">Cancel</button>
</div>
</p>
</fieldset>
}
Scripts
<script src="#Url.Content("~/Scripts/jquery-1.7.2.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/knockout-2.1.0.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/knockout.mapping-latest.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/ko.editables.js")" type="text/javascript"></script>
<script type="text/javascript">
var g = null;
function ViewModel() {
this.addStudent = function () {
this.StudentViewModels.push(new Student(25, "my name" + new Date(), "my last name"));
this.serializeLists();
};
this.serializeLists = function () {
this.StudentsSerialized(ko.toJSON(this.StudentViewModels));
this.SelectedStudentsSerialized(ko.toJSON(this.SelectedStudents));
};
this.leftStudentSelected = ko.observable();
this.rightStudentSelected = ko.observable();
this.moveFromLeftToRight = function () {
this.SelectedStudents.push(this.leftStudentSelected());
this.StudentViewModels.remove(this.leftStudentSelected());
this.serializeLists();
};
this.moveFromRightToLeft = function () {
this.StudentViewModels.push(this.rightStudentSelected());
this.SelectedStudents.remove(this.rightStudentSelected());
this.serializeLists();
};
this.isInEditMode = ko.observable(false);
this.executeCommit = function () {
this.commit();
this.isInEditMode(false);
};
this.executeRollback = function () {
if (this.hasChanges()) {
if (confirm("Are you sure you want to discard the changes?")) {
this.rollback();
this.isInEditMode(false);
}
}
else {
this.rollback();
this.isInEditMode(false);
}
};
this.editMode = function () {
this.isInEditMode(!this.isInEditMode());
this.beginEdit();
};
}
function Student(id, name, lastName) {
this.ID = id;
this.Name = name;
this.LastName = lastName;
}
$(function () {
var jsonModel = '#Html.Raw(JsonConvert.SerializeObject(this.Model))';
var mvcModel = ko.mapping.fromJSON(jsonModel);
var myViewModel = new ViewModel();
g = ko.mapping.fromJS(myViewModel, mvcModel);
g.StudentsSerialized(ko.toJSON(g.StudentViewModels));
g.SelectedStudentsSerialized(ko.toJSON(g.SelectedStudents));
ko.editable(g);
ko.applyBindings(g);
});
</script>
Note: I just added these lines:
#Html.HiddenFor(model => model.CourseId, new { data_bind="value: CourseId" })
#Html.HiddenFor(model => model.CourseName, new { data_bind="value: CourseName" })
Because when I submit the form my fields are disabled, so the values were not transmitted to the server, that's why I added a couple of hidden fields to do the trick
You could serialize your ASP.NET MVC view model into a javascript variable:
#model CourseVM
<script type="text/javascript">
var model = #Html.Raw(Json.Encode(Model));
// go ahead and use the model javascript variable to bind with ko
</script>
There are lots of examples in the knockout documentation that you could go through.
To achieve the additional computed properties after server mapping you will need to further enhance your viewmodels on the client side.
For example:
var viewModel = ko.mapping.fromJS(model);
viewModel.capitalizedName = ko.computed(function() {...}, viewModel);
So everytime you map from raw JSON you would need to reapply the computed properties.
Additionally the mapping plugin provides the ability to incrementally update a viewmodel as opposed to recreating it every time you go back and forth (use an additional parameter in fromJS):
// Every time data is received from the server:
ko.mapping.fromJS(data, viewModel);
And that executes an incremental data update on your model of just properties that are mapped. You can read more about that in the mapping documentation
You mentioned in the comments on Darin's answer the FluentJSON package. I'm the author of that, but its use case is more specific than ko.mapping. I would generally only use it if your viewmodels are one way (ie. server -> client) and then data is posted back in some different format (or not at all). Or if your javascript viewmodel needs to be in a substantially different format from your server model.
I have a very simple partial view and I am using ajaxform. In my partial view I have one sigle textArea and a Submit button.
The problem is whatever I write into the text area, It does not send the data to controller, rather it sends a text which is = "Comment".
If I dont write anything, the validation works perfect.
The Viewmodel:
public class NoteCommentViewModel
{
public Int32 Id { get; set; }
[Required(ErrorMessage="Hey, if you dont wanna write then why pressing the button !!")]
public string Comment { get; set; }
public DateTime CommentDate { get; set; }
public long UserId { get; set; }
public double Rating { get; set; }
public Guid NoteId { get; set; }
}
Controller:
//
//GET:/Notes/Comment/
public ActionResult Comment(string id)
{
ViewBag.NoteId = id;
var m = new NoteCommentViewModel()
{
NoteId = new Guid(id),
UserId = Convert.ToInt64(Session["LoginUserId"].ToString()),
//Comment=""
};
return PartialView(m);
}
//
//POST:/Notes/Comment
[HttpPost]
public ActionResult Comment(NoteCommentViewModel nvm)
{
NoteRatingComments comment = new NoteRatingComments();
comment.Comment = nvm.Comment; // Here I always have "Comment", regardless whatever I write in the page.
comment.EntryDate = DateTime.Now;
comment.NoteId = nvm.NoteId;
comment.UserId = nvm.UserId;
comment.Rating = 3.00;
dc.NoteRatingComments.AddObject(comment);
dc.SaveChanges();
return Content(Boolean.TrueString);
}
The view:
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Ajax.BeginForm("Comment", "Notes", null, new AjaxOptions
{
UpdateTargetId = "Comment-message",
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
OnSuccess = "commentSuccess"
}, new { id = "commentForm" }))
{
<div style="margin-top:20px;">
<div id="commentSuccess"></div>
<div class="comentPic">
#{
long UserId = Convert.ToInt64(Session["LoginUserId"].ToString());
string Fullname = Session["LoginUserFullName"].ToString();
}
<img src='https://graph.facebook.com/#UserId/picture' height="100px" width="100px"/>
</div>
<div class="commentText">
#Html.HiddenFor(m => m.UserId)
#Html.HiddenFor(m=>m.NoteId)
#Html.TextAreaFor(m => m.Comment, new { style = "width:600px;height:120px;" })
<br />
#Html.ValidationMessageFor(m => m.Comment)
<div style="text-align:right;">
<input type="submit" value="Comment" name="comment" class="btn"/>
</div>
</div>
<div class="clear"></div>
</div>
}
Here is the screen shot of the error...for better understanding. I am writing "data" in the view but in the controller I am getting "Comment"..Where is it coming from??
WOuld be great if someone can help me to identify the problem...!!
The problem is that your submit button's name attribute is the same as the Comment textarea name attribute.
To resolve this you need to change the submit button's name to something else than "comment" or remove the name attribute from your submit button, so change:
<input type="submit" value="Comment" name="comment" class="btn"/>
To
<input type="submit" value="Comment" class="btn"/>
Because the Ajax.BeginForm uses the the jQuery .serializeArray() method which - because your submit button has a name and this input triggers the submit - also sends the submit button's value "Comment" to the server.
I am not sure what exactly your problem is. But the below code should work.
public class NoteCommentViewModel
{
public Int32 Id { get; set; }
[Required(ErrorMessage=" Your Error message")]
[DataType(DataType.MultilineText)]
public string Comment { get; set; }
//other properties
}
And in your View, Use it like this
#Html.EditorFor(m => m.Comment)
actually your code is very confusing. in your view u didnt use Model and seems you use m as your model and as i know this is completely wrong.i dont know your view is rendering but wherever you use m=>m.sth the second m must be nothin.instead you must define your #model NoteCommentViewModel at first of cshtml file and then use Model instead of second m