I have to admit, the transition from Web Forms to MVC is literally blowing my mind. I simply can not seem to wrap my head around how MVC functions as it compares to Web Forms, and I continue to hit roadblock after roadblock when trying to perform seemingly simple tasks. Couple this with a rudimentary knowledge of javascript/jquery, and I'm left scratching my head most days.
I have been working to transition a custom CMS that my company built from Web Forms to MVC. This CMS allows for the creation of pages and the manipulation of the site navigation, which resulted in the creation of a page model, along with related views and controllers.
While the client can edit 99% of the site, some elements are static and are loaded on demand on specific pages. For example, the Contact page exists within the CMS, but the site injects the actual contact form on demand.
When build in Web Forms, this contact form was simply a User Control, with its own logic for handing the postback. I thought the logical transition here would be the creation of a Partial View, but I have run into ridiculous amounts of difficulty in accomplishing this task. While I have been able to create the model, view and controller for this contact form, I simply can't seem to make it work correctly.
After literally 3 days of scouring Stack Overflow for advice, I completed nearly everything to make this work. However, I can't figure out how to perform a seemingly simple redirect from the Partial View to save my life. I have tried numerous approaches, including the one described at Doug.Instance, but haven't had any luck. Upon post, the partial view returns as an entire view, even though the controller uses 'return PartialView'. If I post it again, it returns as it originally displayed, as a partial view. In addition, my Redirect variable is not updating upon success, AND the OnSuccess javascript is not firing.
Below is my code. Please help me, because I'm running out of hair...
PAGE VIEW (SHORTENED FOR BREVITY)
...
#if (Model.ID == 8)
{
//LOAD CONTACT FORM
#Html.Action("Contact","ContactForm")
}...
CONTACT FORM PARTIAL VIEW (SHORTENED FOR BREVITY)
#model NCOWW.Models.ContactForm
<script src="#Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<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 type="text/javascript">
// Labels over the inputs.
window.addEvent("load", function () {
var myForm = document.id('contactForm');
myForm.getElements('[type=text], textarea').each(function (el) {
new OverText(el);
});
});
function FormComplete() {
if ($("#Redirect").val() != '') {
document.location = $("#Redirect").val();
}
}
</script>
#using (Ajax.BeginForm(new AjaxOptions { UpdateTargetId = "contactForm", OnSuccess = "FormComplete" }))
{
#Html.ValidationSummary(true)
<div id="contactForm">
#Html.HiddenFor(model => model.Redirect)
#Html.TextBoxFor(model => model.Name, new { tabindex = 1, #class = "half", Title = #Html.DisplayNameFor(model => model.Name) })
#Html.ValidationMessageFor(model => model.Name)
#Html.TextAreaFor(model => model.Comments, new { tabindex = 13, Title = #Html.DisplayNameFor(model => model.Comments) })
#Html.ValidationMessageFor(model => model.Comments)
<br />
<input type="submit" value="Submit" name="Submit" class="button" />
</div>
}
CONTACT FORM CONTROLLER (SHORTENED FOR BREVITY)
public ActionResult Contact(ContactForm c)
{
try
{
MailMessage message = new MailMessage();
message.IsBodyHtml = true;
message.From = new MailAddress(c.Email);
message.Body += "<b>Name:</b> " + c.FullName + "<br/><br/>";
message.Body += "<b>Questions/Comments:</b><br> " + c.Comments;
SmtpClient client = new SmtpClient();
client.Send(message);
c.Redirect = "/formsuccess";
return PartialView("contactForm", c);
}
catch
{
return PartialView();
}
}
the issue you have is common with ajax.
what you need to note is when ajax renders response it does not know about javascript you have returned.
you need to have some way of registering your javascript in order to get it triggered.
Now how to do this?
I have created one of ways, that works for me:
have javascript function on page that has reload.
function RedirectToLocation(url) {
//your javascript event here
}
your ajax call, I have ajax action link but begin form is almost the same
the part you are interested is AjaxOptions.
And event OnSuccess. this will get triggered when call is successful.
Example for link:
#Ajax.ActionLink("Comments", "AddComment", "Task", new { id =
Model.Task.TaskId, TaskStatus = (int)State.Closed },
new AjaxOptions { HttpMethod = "Get", UpdateTargetId = "showResults", OnSuccess = "RedirectToLocation" }, null)
And here you can read about ajax options more
Just point out issue in your code example for your action result
try
{
MailMessage message = new MailMessage();
message.IsBodyHtml = true;
message.From = new MailAddress(c.Email);
message.Body += "<b>Name:</b> " + c.FullName + "<br/><br/>";
message.Body += "<b>Questions/Comments:</b><br> " + c.Comments;
SmtpClient client = new SmtpClient();
client.Send(message);
c.Redirect = "/formsuccess";
return PartialView("contactForm", c);
}
catch
{
return PartialView(); <-- will render what, better will be if it renderes error mesasge partial view
}
try it by adding Action name in your PARTIAL VIEW
#using (Ajax.BeginForm("Action Name",new AjaxOptions { UpdateTargetId =
"contactForm", OnSuccess = "FormComplete" })) {
I was able to get this to work once I started rethinking about what I was actually trying to achieve. Instead of returning PartialView("contactForm", c);, I ended up returning Json(c);. I then parsed the returned Json and redirected from there. The final working code is as follows.
PAGE VIEW (SHORTENED FOR BREVITY)
...
#if (Model.ID == 8)
{
//LOAD CONTACT FORM
#Html.Action("Contact","ContactForm")
}...
CONTACT FORM PARTIAL VIEW (SHORTENED FOR BREVITY)
#model NCOWW.Models.ContactForm
<script src="#Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<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 type="text/javascript">
// Labels over the inputs.
window.addEvent("load", function () {
var myForm = document.id('contactForm');
myForm.getElements('[type=text], textarea').each(function (el) {
new OverText(el);
});
});
function FormComplete(result) {
if (result.Redirect != null && result.Redirect != '') {
document.location = result.Redirect;
}
}
</script>
#using (Ajax.BeginForm(new AjaxOptions { UpdateTargetId = "contactForm", OnSuccess = "FormComplete" }))
{
#Html.ValidationSummary(true)
<div id="contactForm">
#Html.HiddenFor(model => model.Redirect)
#Html.TextBoxFor(model => model.Name, new { tabindex = 1, #class = "half", Title = #Html.DisplayNameFor(model => model.Name) })
#Html.ValidationMessageFor(model => model.Name)
#Html.TextAreaFor(model => model.Comments, new { tabindex = 13, Title = #Html.DisplayNameFor(model => model.Comments) })
#Html.ValidationMessageFor(model => model.Comments)
<br />
<input type="submit" value="Submit" name="Submit" class="button" />
</div>
}
CONTACT FORM CONTROLLER (SHORTENED FOR BREVITY)
public ActionResult Contact(ContactForm c)
{
try
{
MailMessage message = new MailMessage();
message.IsBodyHtml = true;
message.From = new MailAddress(c.Email);
message.Body += "<b>Name:</b> " + c.FullName + "<br/><br/>";
message.Body += "<b>Questions/Comments:</b><br> " + c.Comments;
SmtpClient client = new SmtpClient();
client.Send(message);
c.Redirect = "/formsuccess";
return Json(c);
}
catch
{
//ERROR CATCH CODE
}
}
Related
I have a sort of Master-Detail Edit form and I'm trying to follow this post: Using Ajax... to get the partial view to postback.
My Edit form has a partial view that has a list of sub items, and another partial create view in it to add new items. I'd like the partial create view to post back and update the list without refreshing the whole page if possible.
Here's what I have so far:
MyController.cs -
public ActionResult Edit(int? id)
{
//...
ViewBag.CustomFormId = id;
using (var _db = new MkpContext())
{
//...
return View(profileEdit);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(CustomForm editForm)
{
//...
if (!ModelState.IsValid) return View(editForm);
using (var _db = new MkpContext())
{
var form = _db.CustomForms.Find(editForm.CustomFormId);
//...
_db.Entry(form).State = EntityState.Modified;
_db.SaveChanges(User.ProfileId);
return RedirectToAction("Index");
}
}
public ActionResult _CustomFieldList(int id)
{
ViewBag.CustomFormId = id;
using (var _db = new MkpContext())
{
var formCustomFields = (from cf in _db.CustomFields
where cf.CustomFormId == id
select cf);
return PartialView(formCustomFields.ToList());
}
}
// Nested in _CustomFieldList
public ActionResult _CustomFieldCreate(int id)
{
var newField = new CustomField
{
CustomFormId = id
};
return PartialView(newField);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult _CustomFieldCreate(CustomField addField)
{
ViewBag.CustomFormId = addField.CustomFormId;
if (ModelState.IsValid)
{
using (var _db = new MkpContext())
{
_db.CustomFields.Add(addField);
_db.SaveChanges();
}
var newField = new CustomField
{
CustomFormId = addField.CustomFormId
};
return PartialView(newField); // Probably need to change this somehow
}
return PartialView(addField);
}
And the views:
Edit.cshtml -
#model PublicationSystem.Model.CustomForm
#{
ViewBag.Title = "Edit Custom Form";
Layout = "~/Views/Shared/_LayoutSmBanner.cshtml";
}
<div class="form-horizontal">
<div class="row">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#* Fields for this form *#
}
<div id="CustomFields" class="col-md-6">
#Html.Action("_CustomFieldCreate", new { id = ViewBag.CustomFormId })
</div>
</div>
</div>
<script>
$(function () {
$("#createFieldForm").on("submit", function (e) {
e.preventDefault(); //This prevent the regular form submit
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result) {
$("#CustomFields").html(result);
}
});
return false;
});
});
</script>
_CustomFieldCreate.cshtml -
#model PublicationSystem.Model.CustomField
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div id="result"></div>
<div class="form-horizontal">
<h4>CustomField</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model =>model.CustomFormId)
<div class="row">
#* Fields for the form *#
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div id="customFieldList">
#Html.Action("_CustomFieldList", new { id = ViewBag.CustomFormId })
</div>
_CustomFieldList.cshtml
#model System.Collections.Generic.IEnumerable<PublicationSystem.Model.CustomField>
<table class="table">
#* List table code *#
</table>
Edit: I rewrote the pages so that the list is part of the create partial view. What happens now is, if you enter data for _CustomFieldCreate and press submit, the first time, it refreshes just that view (including the nested list view). However the second time, it redirects to the view, probably because the first refresh didn't rebind the javascript to the submit button. Also, the Create view doesn't clear out the fields, but persists the originally entered data.
You will need a form in your partial view whose submit action binds to a javascript function that posts to your controller.
For example if your form id is MyForm:
$('#MyForm').on('submit', function (e) {
e.preventDefault(); //This prevent the regular form submit
$.ajax({
url: $(this).action, // This will submit the post to whatever action your form goes to
type: "POST", // This tells it that it is a post
data: $(this).serialize(), // This sends the data in the form to the controller
success: function (data) {
// do some javascript on success
},
error: function (xhr, ajaxOptions, thrownError) {
// do some javascript on error
}
});
});
This javascript overrides the default form submit and does an ajax post to your controller and then returns with success or error where you can do anything you want.
Here is some jquery ajax documentation:
http://api.jquery.com/jquery.ajax/
You should look into using AJAX. That should accomplish what I think you are describing. You'll want to create a javascript function that handles the submit event on the form, then post the form data to some create action in your MVC app using AJAX. If you are using jQuery, the library makes it pretty simple.
http://api.jquery.com/jquery.ajax/
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've got a simple example here. Basically a form which when submitted will reload itself via an ajax request. The problem is when this happens, the unobtrusive javascript no longer works. I assume I could add the validate and unobtrusive files in the html i get back from the ajax call, but there must be an easier way to re-wire the validators, no?
Notice I'm hijacking my submit button in order to do an AJAX request which will replace the form in the dom, from the html which is returned from the ajax request.
Model:
public class Foo
{
public int Bar { get; set; }
}
Controller:
public class FooController : Controller
{
public ActionResult Index()
{
return View(new Foo{});
}
[HttpPost]
public ActionResult Form(Foo model)
{
return View(model);
}
[HttpPost]
public ActionResult Index(Foo model)
{
return View();
}
}
Index.cshtml
#model PartialPostBackValidation.Models.Foo
#{
ViewBag.Title = "Index";
}
<h2>#Html.ActionLink("Index", "Index")</h2>
<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>
$(function () {
$("body").on("click", ".ajax-submit", function () {
var form = $(this).parents("form");
$.post(
form.attr("action"),
form.serialize(),
function (html) {
form.replaceWith(html);
}
);
return false;
});
});
</script>
#{Html.RenderPartial("Form");}
Form.cshtml
#model PartialPostBackValidation.Models.Foo
#{
ViewBag.Title = "Index";
Layout = null;
}
#using (Html.BeginForm("Form", "Foo")) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Foo</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Bar)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Bar)
#Html.ValidationMessageFor(model => model.Bar)
</div>
<p>
<input type="submit" value="Create" class="ajax-submit" />
</p>
</fieldset>
}
To get the validation to work you simply have to re-enable it on the form once content is loaded dynamically:
$('#form-id').removeData('validator');
$('#form-id').removeData('unobtrusiveValidation');
$.validator.unobtrusive.parse('#form-id'); <<<<<< Just having this could be enough but some people complain that without removingData first it doesn’t always work.
p.s. Ofcourse you're going to need to add an id attribute to your #using (Html.BeginForm("Form", "Foo"))
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 would like to upload a file without refreshing all the page. I know that uploading does not support classic ajax form. The trick is to use a classic form and to ajaxify it.
Here is my view:
#model .....
#using (Html.BeginForm("UploadImage", "Admin", FormMethod.Post, new { enctype = "multipart/form-data", id = "uploadform" }))
{
<div class="editor-label"> #Html.LabelFor(model => model.Image) </div>
<div class="editor-field"> <input type="file" name="file" id="file" /> </div>
<button type="submit">
<span>Upload</span>
</button>
}
Here is my javascript:
$(document).ready(function() {
$('#uploadform').ajaxForm(function () {
alert("Thank you for your comment!");
});
});
Here is my controller:
[HttpPost]
public ActionResult UploadImage(HttpPostedFileBase file)
{
// Add code here...
return RedirectToAction("EditProject");
}
When I submit a file, the action in the controller receive the information. BUT next nothing happened. The message 'Thank you for your comment!' is never displayed. I would like to display a message if the upload is successful of failed.
Thanks.
The UploadImage method in your Controller redirects to another action. You should return JSON with a message to be displayed.
public ActionResult UploadImage(HttpPostedFileBase file)
{
//processing code...
return Json(new { message = "Thank you..." });
}
And then in the View just alert the returned message.