Connecting MVC Model View with associated Knockout.js ViewModel - asp.net-mvc

Background: I'm pretty new to MVC & Knockout.js but I am trying to get up to speed on these technologies. I am using MVC 5 with EF6 and Knockout.JS 3.2.
I have a Detail view that pulls a "VoteAnswer" object using MVC based on the ID passed in the URL:
For example I can go to the url MyDomain/VoteAnswers/Details/1 and it will pull the information from my database correctly (It pulls a VoteAnswer with the ID of 1) and display in my Details view. However I am trying to hook-up my Knockout.js "VoteAnswer" ViewModel to function the same way and am having trouble.
Here is my Details View: (Note the #Html.DisplayFor(model => model.VoteAnswerId) etc works and displays the data from my Database.
#model AM_SPA_TestSite.Models.VoteAnswer
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Details</title>
<script src="~/KnockoutViewModels/VoteAnswers.js"></script>
</head>
<body>
<div>
<h4>VoteAnswer</h4>
<hr />
<table>
<tr>
<td>Id</td>
<td data-bind="text: id"></td>
</tr>
<tr>
<td>Display Text</td>
<td data-bind="text: isActive"></td>
</tr>
<tr>
<td>IsActive</td>
<td data-bind="text: displayText"></td>
</tr>
</table>
<table>
<tr>
<td>Id</td>
<td><input type="text" data-bind="value: id" /></td>
</tr>
<tr>
<td>Display Text</td>
<td><input type="text" data-bind="value: displayText" /></td>
</tr>
<tr>
<td>IsActive</td>
<td><input type="text" data-bind="value: isActive" /></td>
</tr>
</table>
<table>
<tr>
<td>Id</td>
<td>#Html.DisplayFor(model => model.VoteAnswerId)</td>
</tr>
<tr>
<td>Display Text</td>
<td>#Html.DisplayFor(model => model.DisplayText)</td>
</tr>
<tr>
<td>IsActive</td>
<td>#Html.DisplayFor(model => model.IsActive)</td>
</tr>
</table>
</div>
Here is my Knockout.Js ViewModel
// VoteAnswer ViewModel
var VoteAnswerVM = {
id: ko.observable(),
displayText: ko.observable(),
isActive: ko.observable(),
SaveVoteAnswer: function () {
$.ajax({
url: '/VoteAnswers/Create',
type: 'post',
dataType: 'json',
data: ko.toJSON(this),
contentType: 'application/json',
success: function (result) {
},
error: function (err) {
if (err.responseText == "Creation Failed")
{ window.location.href = '/VoteAnswers/Index/'; }
else {
alert("Status:" + err.responseText);
window.location.href = '/VoteAnswers/Index/';;
}
},
complete: function () {
window.location.href = '/VoteAnswers/Index/';
}
});
}
};
//Go
$(document).ready(function () {
//initialize and create new VoteAnswerVM by URL value here?
ko.applyBindings(VoteAnswerVM);
});
I know what I am missing is initializing the ViewModel with the ID of 1, but I was thinking the MVC model already has the data and the knockout.js SHOULD map to that data without manually initializing by sending a request to the database again. What am I missing? thanks.
EDIT: Added solution below. I'm not sure I am settled on this approach but here it is. Updated the controller to ONLY return a view and not query the DB. (otherwise I would have two database calls for the same data.
// GET: VoteAnswers/Details/5
public ViewResult Details(int? id)
{
return View();
}
Added an API Controller that does query the DB.
// GET: api/VoteAnswers/5
[ResponseType(typeof(VoteAnswer))]
public async Task<IHttpActionResult> GetVoteAnswer(int id)
{
VoteAnswer voteAnswer = await db.VoteAnswers.FindAsync(id);
if (voteAnswer == null)
{
return NotFound();
}
return Ok(voteAnswer);
}
In my View (.cshtml file) I reference my knockout.js ModelView, View is Below:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Details</title>
<script src="~/KnockoutViewModels/VoteAnswers.js"></script>
</head>
<body>
<div>
<h4>VoteAnswer</h4>
<hr />
<table>
<tr>
<td>Id</td>
<td data-bind="text: VoteAnswerId"></td>
</tr>
<tr>
<td>Display Text</td>
<td data-bind="text: IsActive"></td>
</tr>
<tr>
<td>IsActive</td>
<td data-bind="text: DisplayText"></td>
</tr>
</table>
<table>
<tr>
<td>Id</td>
<td><input type="text" data-bind="value: VoteAnswerId" /></td>
</tr>
<tr>
<td>Display Text</td>
<td><input type="text" data-bind="value: DisplayText" /></td>
</tr>
<tr>
<td>IsActive</td>
<td><input type="text" data-bind="value: IsActive" /></td>
</tr>
</table>
</div>
<div id="error"></div>
</body>
</html>
Updated My ViewModel script to access the Database based on the URL ID.
// VoteAnswer ViewModel
var VoteAnswer = function () {
var self = this;
self.VoteAnswerId = ko.observable();
self.DisplayText = ko.observable();
self.IsActive = ko.observable();
self.SaveVoteAnswer = function () {
$.ajax({
url: '/VoteAnswers/Create',
type: 'post',
dataType: 'json',
data: ko.toJSON(this),
contentType: 'application/json',
success: function (result) {
},
error: function (err) {
if (err.responseText == "Creation Failed")
{ window.location.href = '/VoteAnswers/Index/'; }
else {
alert("Status:" + err.responseText);
window.location.href = '/VoteAnswers/Index/';;
}
},
complete: function () {
window.location.href = '/VoteAnswers/Index/';
}
});
}
self.load = function (id) {
if (id != 0) {
$.ajax({
url: '/api/VoteAnswers/' + id,
type: 'get',
data: ko.toJSON(this),
contentType: 'application/json',
success: function(data) {
self.VoteAnswerId = ko.observable(data.voteAnswerId);
self.DisplayText = ko.observable(data.displayText);
self.IsActive = ko.observable(data.isActive);
ko.applyBindings(self);
},
error: function(err) {
if (err.responseText == "Creation Failed") {
window.location.href = '/VoteAnswers/Index/';
} else {
$("#error").text("Status:" + err.responseText);
//window.location.href = '/VoteAnswers/Index/';;
}
},
complete: function() {
//window.location.href = '/VoteAnswers/Index/';
}
});
} else {
window.location.href = '/VoteAnswers/Index/';
}
}
};
function GetURLParameter() {
var sPageUrl = window.location.href;
var indexOfLastSlash = sPageUrl.lastIndexOf("/");
if (indexOfLastSlash > 0 && sPageUrl.length - 1 != indexOfLastSlash)
return sPageUrl.substring(indexOfLastSlash + 1);
else
return 0;
}
//Go
$(document).ready(function () {
//initialize and create new VoteAnswerVM by URL value here?
var viewModel = new VoteAnswer();
viewModel.load(GetURLParameter());
});

I hope I understood you correctly, if my answer is all wrong for your question let me know where I went wrong.
First thing to realize is, if you bind a KO observable to an input field, knockout will not look at the initial value of that input field and store it in the observable. It will do the opposite: it will look at the current value in the observable and store it in the input field's value. In your case, the observable are initialized without a value, which in JavaScript means the value undefined. So if you bind your observables to the fields you've filled with Razor/MVC viewmodel, you'll immediatly overwrite those values with the empty values stored in your observables.
There IS a way to fill your Knockout model with your data through Razor, but it involves inline JavaScript and is a bad practise for a number of reasons (I will elaborate on this on request).
The best way to do this is to separate your views from your data: Don't inject the MVC viewmodel into the view, but make a separate endpoint that returns JSON and return the data there (this endpoint will receive the ID parameter instead of the view). The JSON endpoint is called from JavaScript and can be used to fill your model with the correct values.
Upsides: separation of concerns, possibility to enable view caching for a more responsive frontend, no need to use razor syntax, or even worse, combine it with inline JS. All your binding of data to the UI will happen through Knockout. I learned this myself because we also started out using razor, but on the long run this solution wasn't feasible for a big project. We never regretted the switch to always getting the data from separate JSON endpoints.
If you are unsure on how to do this I can write some pseudocode to illustrate the idea.

These may be the possible solutions
1.) data_bind is wrongly used
<td><input type="text" data-bind="value: isActive" /></td> // which is wrong
<td><input type="text" data_bind="value: isActive" /></td> //data_bind is wrongly used
2.)
If still problem exists you may try this syntax
#Html.DisplayFor(model => model.IsActive, new { data_bind = "value:IsActive" });
If you find still something missing please provide some detail info .

Related

Update razor collection via knockout

I have this UI
And my goal is to update table after clicking Update button based on Id and Description parameters
I have following code
Html
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Id</th>
<th>Description</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Products)
{
<tr>
<td>#item.Id</td>
<td>#item.Description</td>
</tr>
}
</tbody>
</table>
<label>Id</label>
<input data-bind="value: productId" type="text" class="form-control" />
<br />
<label>Description</label>
<input data-bind="value: productDescription" type="text" class="form-control" />
<br />
<button class="btn btn-primary" data-bind="click: update">Update</button>
JavaScript
<script type='text/javascript'>
// Plain javascript alternative to jQuery.ready.
(function () {
// Convert server model to json object.
var json = {"products":[{"id":1,"description":"Product A"},{"id":2,"description":"Product B"},{"id":3,"description":"Product C"}],"categoryName":"chocolates"};
function viewModel()
{
var index;
// Store default this to self, to be able use self in subfunctions.
var self = this;
// Product array from server.
self.products = json.products;
// Automatic refreshed parameter in UI.
self.categoryName = ko.observable(json.categoryName);
// Update form parameters.
self.productId = null;
self.productDescription = null;
// Update function.
self.update = function () {
// Get products collection index.
index = self.products.findIndex(function (product) {
return product.Id == self.productId
});
// Throws -1, index was not found !
alert(index);
// Assign new value.
// self.products[index].description(self.productDescription);
};
}
// Apply viewModel to UI
ko.applyBindings(new viewModel());
})();
</script>
I need help with two things
Update knockout viewmodel (self.products collection based on Id and Description inputs in viewModel.Update function)
Update table (I don't know how to bind the rows, when using razor foreach)
EDIT:
When I change Razor loop with Knockout foreach
<tbody data-bind="foreach: products">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: description"></td>
</tr>
</tbody>
it does not work either :/ I tried some changes with observables
<script type='text/javascript'>
// Plain javascript alternative to jQuery.ready.
(function () {
// Convert server model to json object.
var json = {"products":[{"id":1,"description":"Product A"},{"id":2,"description":"Product B"},{"id":3,"description":"Product C"}],"categoryName":"chocolates"};
function viewModel()
{
var index;
// Store default this to self, to be able use self in subfunctions.
var self = this;
// Product array from server.
self.products = ko.observable(json.products);
// Automatic refreshed parameter in UI.
self.categoryName = ko.observable(json.categoryName);
// Update form parameters.
self.productId = ko.observable();
self.productDescription = ko.observable();
// Update function.
self.update = function () {
self.products()[self.productId()-1].description = self.productDescription()
};
}
// Apply viewModel to UI
ko.applyBindings(new viewModel());
})();
</script>
I do not get an error, but the value is not changed in the html table.
Your knockout code looks mostly correct but your UI isn't updating because the values that you're changing aren't observable. If you want the changes to the items to reflect in the table you need to make each field that will update an observable instead of having a single observable property for the entire json object.
The following code will only update if the entire object reference changes. IE if you fill self.products with a new object. It will not update if you change a property on the existing object.
// Product array from server.
self.products = ko.observable(json.products);
You'll need to change your code to individually apply observable properties to your json object when it comes from the server.
// Product array from server.
self.products = ko.observableArray([]); //Changed to array type
for(var i = 0; i < json.products.length; i++){
self.products.push({
id: json.products[i].id,
description: ko.observable(json.products[i].description)
});
}
And then you'll have to update the value using the function style getter/setter.
// Update function.
self.update = function () {
self.products()[Number(self.productId())-1].description(self.productDescription());
};
// Convert server model to json object.
var json = {"products":[{"id":1,"description":"Product A"},{"id":2,"description":"Product B"},{"id":3,"description":"Product C"}],"categoryName":"chocolates"};
function viewModel()
{
var index;
// Store default this to self, to be able use self in subfunctions.
var self = this;
// Product array from server.
self.products = ko.observableArray([]); //Changed to array type
for(var i = 0; i < json.products.length; i++){
self.products.push({
id: json.products[i].id,
description: ko.observable(json.products[i].description)
});
}
// Automatic refreshed parameter in UI.
self.categoryName = ko.observable(json.categoryName);
// Update form parameters.
self.productId = ko.observable();
self.productDescription = ko.observable();
// Update function.
// Update function.
self.update = function () {
self.products()[Number(self.productId())-1].description(self.productDescription());
};
}
// Apply viewModel to UI
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Id</th>
<th>Description</th>
</tr>
</thead>
<tbody data-bind="foreach: products">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: description"></td>
</tr>
</tbody>
</table>
<br/>
<label>Id: </label>
<input data-bind="value: productId" type="text" class="form-control" />
<br />
<label>Description: </label>
<input data-bind="value: productDescription" type="text" class="form-control" />
<br />
<button class="btn btn-primary" data-bind="click: update">Update</button>

checkbox value always showing null value in mvc

I am always getting null value through checkbox in mvc. If the checkbox is checked or uncheck it contain null value only.
Here is my code,
View Page
#model IEnumerable<SchoolWebApplication.Models.EventMaster>
<table id="tblEvent" class="table" cellpadding="0" cellspacing="0">
<tr>
<th style="width:100px; display:none">Event Id</th>
<th style="width:150px">Event</th>
<th style="width:150px">Active</th>
</tr>
#if(Model != null)
{
foreach (SchoolWebApplication.Models.EventMaster eventMaster in Model)
{
<tr>
<td class="EventID" style="display:none">
<span>#eventMaster.EventID</span>
</td>
<td class="Event">
<span style="color:darkgreen">#eventMaster.Event</span>
<input type="text" value="#eventMaster.Event" style="display:none; color:darkgreen" />
</td>
<td class="IsActive">
<span style="color:darkgreen">#eventMaster.IsActive</span>
#if (#eventMaster.IsActive == true)
{
<input type="checkbox" value="#eventMaster.IsActive" style="display:none; color:darkgreen" checked="checked" name="abc"/>
}
else
{
<input type="checkbox" value="#eventMaster.IsActive" style="display:none; color:darkgreen" name="abc"/>
}
</td>
<td>
<a class="Edit" href="javascript:;">Edit</a>
<a class="Update" href="javascript:;" style="display:none">Update</a>
<a class="Cancel" href="javascript:;" style="display:none">Cancel</a>
</td>
</tr>
}
}
</table>
<script type="text/javascript">
function AppendRow(row, EventID, Event, IsActive) {
//Bind EventID.
$(".EventID", row).find("span").html(EventID);
//Bind Event.
$(".Event", row).find("span").html(Event);
$(".Event", row).find("input").val(Event);
//Bind IsActive.
$(".IsActive", row).find("span").html(IsActive);
$(".IsActive", row).find("input").val(IsActive);
$("#tblEvent").append(row);
};
//Edit event handler.
$("body").on("click", "#tblEvent .Edit", function () {
var row = $(this).closest("tr");
$("td", row).each(function () {
if ($(this).find("input").length >= 0) {
$(this).find("input").show();
$(this).find("span").hide();
}
});
row.find(".Update").show();
row.find(".Cancel").show();
$(this).hide();
});
//Update event handler.
$("body").on("click", "#tblEvent .Update", function () {
var row = $(this).closest("tr");
$("td", row).each(function () {
if ($(this).find("input").length >= 0) {
var span = $(this).find("span");
var input = $(this).find("input");
span.html(input.val());
span.show();
input.hide();
}
});
row.find(".Edit").show();
row.find(".Cancel").hide();
$(this).hide();
var event = {};
event.EventID = row.find(".EventID").find("span").html();
event.Event = row.find(".Event").find("span").html();
event.IsActive = row.find(".IsActive").find("span").html();
$.ajax({
type: "POST",
url: "/Event/Update",
data: JSON.stringify({ eventMaster: event }),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (response) {
alert(response.IsActive);
}
});
});
</script>
Controller
try
{
EventMaster updatedEvent = (from c in entities.eventMaster
where c.EventID == eventMaster.EventID
select c).FirstOrDefault();
updatedEvent.Event = eventMaster.Event;
updatedEvent.IsActive = eventMaster.IsActive;
entities.SaveChanges();
return new EmptyResult();
}
catch (Exception ex)
{
return View();
}
Now, in table there is a three field EventID, Event and Active. In active there is a checkbox containing at update time.
Now, the issue is coming that if the checkbox is check or not check it is containing null value only.
So thats why at the fetch time it showing uncheck only.
Thank You.
Asking for the .val of a checkbox will get you the contents (if any) of the value attribute on the input element - this will not change when the user checks the box.
To check if a checkbox is checked in jQuery you should use something like:
if (input.is(":checked")){}
At the moment, you're storing the current value of .IsActive in the span and the value of the checkbox, and then when the update method runs, just grabbing that same value and putting it into the span - resulting in not updating anything.
Looking further at your code though - you should confirm what your method is actually posting back to the server - looking at it you are passing raw HTML into some parameters on the object:
event.IsActive = row.find(".IsActive").find("span").html();
At best, event.IsActive will be the string "True" (or False), rather than an actual boolean that your model is expecting. You would be better off changing that line to something like:
event.IsActive = row.find(".IsActive").find("input").is(":checked");
And then confirm what is being sent to the server in the network tab of your browser.

Can not save data 2nd time knockout mvc

I am new in knockout and asp.net mvc both.
I am trying to Insert update delete data in database with knockout. My knockout model is
function City(data) {
this.CityId = ko.observable(data.CityId);
this.CityName = ko.observable(data.CityName);
}
function CityViewModel() {
var self = this;
self.Citys = ko.observableArray([]);
self.CityId = ko.observable();
self.CityName = ko.observable();
self.selectedCity = ko.observable();
// self.City = ko.observable();
selectCity = function (item) {
self.selectedCity(item);
}
//load
loadCitys = function () {
$.getJSON("/Admin/GetCitys", {}, function (result) {
var mappedCitys = ko.utils.arrayMap(result.Data, function (item) {
return new City(item);
});
self.Citys([]);
self.Citys.push.apply(self.Citys, mappedCitys);
});
}
//edit
EditCity = function (item) {
//what need to do here
// is it possible to fill the hidden fild and the text box ??
}
//save
SaveCity = function (item) {
City = new City(item);
$.ajax({
type: "POST",
url: "/Admin/SaveCity",
data: ko.toJSON({ City: City }),
contentType: "application/json",
success: function (result) {
if (result.Edit) {
City.CityId = result.Success;
City.CityName = item.CityName;
self.Citys.push(City);
toastr.success('City Information Save Successfully', 'Success');
}
else if (result.Edit == false) {
toastr.success('City Information Update Successfully', 'Success');
}
else {
toastr.error('There is an error please try again later', 'Errror');
}
}
});
}
//delete
DeleteCity = function (City) {
$.ajax("/Admin/DeleteCity", {
data: ko.toJSON({ CityId: City.CityId }),
type: "POST", contentType: "application/json",
success: function (result) {
if (result.Success) {
self.Citys.remove(City);
toastr.success('City Remove Successfully', 'Success');
}
else {
alert("Error..");
}
}
});
}
}
(function () {
ko.applyBindings(new CityViewModel, document.getElementById("Citys"));
loadCitys();
});
And my Html codes are
<table class="table table-striped">
<thead>
<tr>
<th>City Id</th>
<th>City Name</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: $root.Citys">
<tr data-bind="click: selectCity">
<td><span data-bind="text:CityId"></span></td>
<td><span data-bind="text:CityName"></span></td>
<td><button data-bind="click: EditCity" class="btn btn-primary">Edit</button></td>
<td><button data-bind="click: DeleteCity" class="btn btn-danger">Delete</button></td>
</tr>
</tbody>
</table>
<fieldset>
<legend>Add new / Edit City</legend>
<label>City name</label>
<input type="hidden" data-bind="value: CityId" />
<input type="text" data-bind="value: CityName" placeholder="Type city name…">
<button type="submit" data-bind="click: SaveCity" class="btn">Submit</button>
</fieldset>
With this codes I can get data form database display them successfully in my view file,
I delete the data from database, and I also can Insert data to database but here is a problem I can save data only 1st time when I change the textbox value (without page refresh) and try to save city information then it say (in Firebug on my javascript code):
TypeError: City is not a constructor
City = new City(item);
My question is what have I done wrong in this codes, and I am trying to fill the textbox and the hidden field when edit button click, how can I do this?
Thanks in advance.
There are a number of faults with your javascript, including:
The methods on your viewmodel, such as SaveCity, DeleteCity, EditCity are all being defined without the 'this/self' prefixes, therefore they are being added to the global namespace.
In your SaveCity method, your are assigning a new instance of the City class to a variable called 'City', therefore destroying the City class. It will work the first time, but any other attempts to create an instance of a City will yield an exception.
Anyway, this should be a working version of your script and HTML without the ajax stuff. You will need to adapt that yourself. I have also created a working JsFiddle here..
function City(data) {
this.CityId = ko.observable(data.CityId);
this.CityName = ko.observable(data.CityName);
}
function CityViewModel() {
var self = this;
self.Citys = ko.observableArray([]);
self.SelectedCity = ko.observable();
self.EditingCity = ko.observable(new City({CityId: null, CityName: ''}));
self.EditCity = function(city){
self.EditingCity(new City(ko.toJSON(city)));
};
//load
self.loadCitys = function () {
self.Citys().push(new City({CityId: '1245', CityName: 'Los Angeles'}));
self.Citys().push(new City({CityId: '45678', CityName: 'San Diego'}));
};
//save
self.SaveCity = function () {
var city = self.EditingCity();
if(city.CityId()){
var cityIndex;
for(var i = 0; i < self.Citys().length; i++) {
if(self.Citys()[i].CityId() === city.CityId()) {
cityIndex = i;
break;
}
}
self.Citys[cityIndex] = city;
}
else{
city.CityId(Math.floor((Math.random()*1000000)+1));
self.Citys.push(city);
}
self.EditingCity(new City({CityId: null, CityName: ''}));
}
//delete
self.DeleteCity = function (city) {
self.Citys.remove(city);
};
}
var viewModel = new CityViewModel();
viewModel.loadCitys();
ko.applyBindings(viewModel, document.getElementById("Citys"));
HTML
<table class="table table-striped">
<thead>
<tr>
<th>City Id</th>
<th>City Name</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: Citys">
<tr data-bind="click: $root.SelectedCity">
<td><span data-bind="text:CityId"></span></td>
<td><span data-bind="text:CityName"></span></td>
<td><button data-bind="click: $root.EditCity" class="btn btn-primary">Edit</button></td>
<td><button data-bind="click: $root.DeleteCity" class="btn btn-danger">Delete</button></td>
</tr>
</tbody>
</table>
<fieldset data-bind='with:EditingCity'>
<legend>Add new / Edit City</legend>
<label>City name</label>
<input type="hidden" data-bind="value: CityId" />
<input type="text" data-bind="value: CityName" placeholder="Type city name" />
<button type="submit" data-bind="click: $root.SaveCity" class="btn">Submit</button>
</fieldset>

Jquery form post Issues

I am using asp.net MVC and I am having an issue posting a form using jquery.
It is not posting to the url I am telling it to.
If I use firebug, it shows the post happening but it is posting to the index of the controller everytime. I cannot figure out why. I have verified the url of the action I am trying to post but I can't figure out why it is always posting to the index of the controller....Note: the view in which the form is found IS the index view. so Basically it is posting to it's own action rather than the one in the url i am telling it to. Any help would be great. thanks!
here is my form
<form action='' id="descriptionForm">
<%=Html.Hidden("claimNumber", ViewData["claimNumber"])%>
<%=Html.Hidden("partNumber", ViewData["partNumber"])%>
<%=Html.Hidden("qty", ViewData["qty"])%>
<table>
<tr>
<td style="text-align: right">
Category:
</td>
<td>
<%=Html.DropDownList("problemCategory", (IEnumerable<SelectListItem>)ViewData["problemSelect"], "-Select-")%>
</td>
</tr>
<tr>
<td style="text-align: right">
Details:
</td>
<td>
<select id="problemDetails">
</select>
</td>
</tr>
<tr>
<td style="text-align: right">
Dealer Notes:
</td>
<td>
<%=Html.TextArea("dealerNotes", "", 3, 40, null)%>
</td>
</tr>
</table>
<div style="position: absolute; bottom: 8px; right: 8px">
<input type="button" id="itemDetailsCancel" value="Cancel" />
<input type="submit" id="itemDetailsSubmit" value="Save" />
</div>
</form>
<a href='<%=ResolveUrl("~/Warranty/WarrantyClaims/CompleteAddLineItemToClaim/") %>'
id="CompleteLineItemUrl"></a>
Here is my Javascript
$("#descriptionForm").submit(function () {
var completeurl = $("#CompleteLineItemUrl").attr('href');
var data = $(this).serialize();
$.post({
type:'POST',
url: completeurl,
data: data,
success: function (result) {
alert("done");
}
});
return false
});
and just for good measure here is the controller action I am trying to post to(though it doesn't do much yet)
[HttpPost]
public ActionResult CompleteAddLineItemToClaim(string claimNumber, string partNumber, string qty, string problemCategory, string problemDetails, string dealerNotes)
{
var result = new { result = "done" };
return Json(result, JsonRequestBehavior.AllowGet);
}
Update:
updated javascript
$(function(){
$('#descriptionForm').submit(function () {
var completeurl = $('#CompleteLineItemUrl').attr('href');
var data = $(this).serialize();
$.ajax({
type: 'POST',
url: completeurl,
data: data,
success: function (result) {
alert('done');
}
});
return false;
});
});
Is the form itself loaded by an ajax call?
If so you need to use the live() function of jquery.
Make sure you have wrapped your javascript in a document.ready before subscribing for any events. Also you have a missing ; when returning false at the end of your method.
But your real problem is that you want to use $.ajax instead of $.post. So what actually happens is that you are getting a javascript error because of wrongly using the $.post function and the .submit handler never has time to return false and cancel the default submission of the form and the browser happily proceeds into POSTing to the action of the form (which is empty and default to the action that rendered this form).
So to sum up:
$(function() {
$('#descriptionForm').submit(function () {
var completeurl = $('#CompleteLineItemUrl').attr('href');
var data = $(this).serialize();
$.ajax({
type: 'POST',
url: completeurl,
data: data,
success: function (result) {
alert('done');
}
});
return false;
});
});
Or if you wanted to use $.post:
$(function() {
$('#descriptionForm').submit(function () {
var completeurl = $('#CompleteLineItemUrl').attr('href');
var data = $(this).serialize();
$.post(completeurl, data, function (result) {
alert('done');
});
return false;
});
});
Also instead of generating links à la classic WebForms way:
In ASP.NET MVC you use HTML helpers in order to ensure that link urls are conform to your routes:
<%= Html.ActionLink(
"Link text",
"CompleteAddLineItemToClaim",
"WarrantyClaims",
new { area = "Warranty" },
new { id = "CompleteLineItemUrl" }
) %>

Post an array of complex objects with JSON, JQuery to ASP.NET MVC Controller

I know this issue has been touched on before, e.g. here:
How to post an array of complex objects with JSON, jQuery to ASP.NET MVC Controller?
But the solutions doesn't seem to fit my problem.
Here is my HTML. The number of rows are variable:
<table id="workPlanTable">
<tr>
<th>
Begin
</th>
<th>
End
</th>
</tr>
<tr itemId="1">
<td><input class="begin" id="begin_1" name="begin_1" type="text" value="5:30" /></td>
<td><input class="end" id="end_1" name="end_1" type="text" value="11:30" /></td>
</tr>
<tr itemId="3">
<td><input class="begin" id="begin_3" name="begin_3" type="text" value="5:30" /></td>
<td><input class="end" id="end_3" name="end_3" type="text" value="7:30" /></td>
</tr>
</table>
The JS builds an array of objects and posts them to a control method:
<script type="text/javascript">
$(function() {
submitForm = function() {
var items = new Array();
$("#workPlanTable tr").each(function(i) {
var end = $(this).find(".end").val();
var begin = $(this).find(".begin").val();
var item = {
"Begin": begin,
"End": end
};
items.push(item);
});
var postData = {
myItems: items
};
$.ajax({
url: '~/WorkPlan/AjaxUpdate',
type: 'POST',
dataType: 'json',
data: postData,
contentType: 'application/json; charset=utf-8',
success: function(result) {
alert(result.Result);
}
});
}
})
</script>
Each row represent a WorkPlanItem that.
My goal is to post them all to my controller to update them.
I can't seem to figure out how to access the array in my controller method (AjaxUpdate).
You can serialize the form as Vikas posted, or you could use a stringify function if you'd prefer to walk the page (as you are currently doing) and use the postData array.
On the controller, you'll need to handle the json string. You can use the System.Web.Script.Serialization.JavaScriptSerializer class to deserialize it. If you have an object that maps to the data you're passing, you can use the Deserialize method. If you don't, you can still use DeserializeObject, however that gives you a Dictionary<string, string> collection that you'll have to walk through to get your data. Not exactly fun (trust me), but it works.

Resources