Knockout simple object mapping - asp.net-mvc

I have a simple MVC Model like this :
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MobileNumber { get; set; }
public string EmailAddress { get; set; }
}
I would like to map one object of this type inside a knockout viewmodel in order to populate it:
var UserViewModel = function () {
var self = this;
self.user = ko.mapping.fromJS({});
$.getJSON("/UserManagement/CreateEmptyUser", function (data) {
ko.mapping.fromJS(data, self.user);
});
self.createUser = function (data, eventArgs) {
var user = data;
$.ajax({
type: "post",
contentType: "application/json",
data: ko.toJSON(user),
url: "#Url.Action("CreateUser")",
success: function () {
window.location = "#Url.Action("AddNew")";
}
});
};
};
The problem I am having is that I have no clue how to map any single object. I've tried using
self.user = ko.mapping.fromJS([]);
$.getJSON("/UserManagement/CreateEmptyUser", function (data) {
ko.mapping.fromJS(data,{}, self.user);
});
which is being used for arrays and trying to extract the element at index 0, the other solution I thought would work is the one in the 2nd comment block. Everything I found on google led me to mapping entire arrays, but nothing towards simple object mapping. Is there a proper way to do this? I would like to keep the model separated and not manually describe its properties in javascript.
Thanks,
Alex Barac

I think your problem is that you are using the mapping from method to instantiate your view model, which will give it no properties, so when you try to map the data after your JSON call, there are no properties on self.user to populate. Either create your user object with the properties you want on there, or else use mapping.fromJS to create your viewmodel in the first place:
self.user = { FirstName: ko.observable(''), etc... }
and leave your mapping line as it is, or
self.user;
$.getJSON("/UserManagement/CreateEmptyUser", function (data) {
self.user = ko.mapping.fromJS(data);
}

I kept working and checking this and I found the solution I wanted at the beginning. The idea is using #Paul Manzotti 's suggestion,
self.user;
$.getJSON("/UserManagement/CreateEmptyUser", function (data) {
self.user = ko.mapping.fromJS(data);
}
but replacing the self.user; with self.user={}; and
self.user = ko.mapping.fromJS(data)
with
ko.mapping.fromJS(data,{},self.user);
If using this, there is no need to manually declare every object's property in the viewmodel. Hope this will be of use to those that stumble upon the same issue.

If your viewModel in question needs to be an observable itself, then I found that use the mapping to return an observable array as the OP mentioned, and then returning the first item in that array works. This is also useful for return one object in ajax that you then wish to push onto an existing observableArray.
I have a helper function for this:
koMappingUsingArray = function (jsObject, mapping) {
var observableObjectArray = ko.observableArray();
var objectArray = new Array(jsObject);
ko.mapping.fromJS(objectArray, mapping, observableObjectArray);
return observableObjectArray()[0];
};
and then call it such as:
self.myObject = ko.observable();
$.ajax({
type: "POST",
url: "....",
data: ko.mapping.toJSON(???),
contentType: "application/json; charset=utf-8",
success: function (returnedObject) {
self.myObject (koMappingUsingArray(returnedObject));
});

Related

Best way to cast server viewmodel to client's - Knockout

I'm looking the best way to get my server model mapped into the knockout viewmodel
for this purpose here I'm using the mapping plugin,
Id, Title, People were mapped to something similar to the groupViewModel I have here,
How can I be sure and force this mapping to always be casted exactly to a new groupViewModel, the real issue here is Id, Title, People are bound to the view, not the addPerson method,
Please share the best workaround on this, or any better way you know to make it thiis mapping precise and yet simple and clean, thanks.
here our viewModel contains Groups Array, how to cast this Groups array into GroupViewModel items , maybe the question could be answered this way.
var model = #Html.Raw(Json.Encode(Model));
$(function() {
var root = this;
var viewModel = ko.mapping.fromJS(model, root);
// there are Groups as expected but not the addPerson
// will cause undefined exception
var groupViewModel = function (item) {
var self = this;
self.Id = ko.observable(item.Id);
self.Title = ko.observable(item.Title);
self.People = ko.observableArray([]);
self.addPerson = function (name) {
console.log('addPerson Clicked');
}; // .bind(self)
}
ko.mapping.fromJS(viewModel);
ko.applyBindings(viewModel);
});
Edit - Updated based to the answer :
Server ViewModel
In Server we have this :
class ServerVm {
public int Id { get; set; }
public IList<GroupMap> Groups { get; set; } // groupViewModel
}
In the client we have :
var model = #Html.Raw(Json.Encode(Model));
$(function() {
var root = this;
var viewModel = ko.mapping.fromJS(model, root);
//viewModel.Groups = ko.observableArray([]);
// No need because it's same as server model
viewModel.addGroup = function (teamName) {
console.log('addGroup Clicked');
}; // .bind(self)
// addGroup is fine
// it's defined here and working fine, no special mapping needed here
// "model" contains something
var groupViewModel = function (item) {
var self = this;
self.Id = ko.observable(item.Id);
self.Title = ko.observable(item.Title);
self.People = ko.observableArray([]);
// self.addPerson - this is hidden in the "model"
self.addPerson = function (name) {
console.log('addPerson Clicked');
}; // .bind(self)
}
ko.mapping.fromJS(viewModel);
ko.applyBindings(viewModel);
});
Our problem is the self.addPerson located inside our nested collection which because the container(groupViewModel) isn't bound automatically to the GroupMap. everytime I create groupViewModel by hand it's ok cause I'm casting it myself, but this is not the real mapping solution, what's yours, thanks
You could use different overload of ko.mapping.fromJS method that takes 3 parameters, from documentation:
Specifying the update target
...The third parameter to ko.mapping.fromJS indicates the target.
ko.mapping.fromJS(data, {}, someObject);
So in your case you could update your view model definition as follows:
function ViewModel() {
var self = this;
this.addPerson = function(data) {
$.ajax({
url: ...+ self.Id,
contentType: 'application/json;charset=utf-8',
dataType: 'JSON',
type: "POST",
success: function (result) // result
{
console.log('Success');
var avm = new childViewModel(result,self); // another defined vm
self.People.push(avm);
}
});
}
}
ViewModel.prototype.init = function(data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
}
And to initialize it:
...
var model = #Html.Raw(Json.Encode(Model));
...
var vm = new ViewModel();
vm.init(model);
ko.applyBindings(vm);
See working demo.
Another approach, a bit shorter, is to map your model first and then add methods to it, like this:
var vm = ko.mapping.fromJS(model);
vm.addPerson = function(data) {
...
See another demo.
I like first approach more since function is defined inside view model and not added later.
Update:
So, after some clarification in comments and after question update, here is what should be done:
You should use mentioned ko.mapping.fromJS method inside that child object to map it's properties automatically.
You should use mapping object to tell the mapping plugin how to map your child object.
The child object view model:
function groupViewModel(data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
self.addPerson = function(personData) {
// here should be your ajax request, I'll use dummy data again
};
}
The mapping object:
var mapping = {
"Groups" : {
'create': function(options) {
return new groupViewModel(options.data);
}
}
};
And initialization:
var vm = ko.mapping.fromJS(model, mapping);
Here is updated demo.

How to pass many forms to controller

In my project I have multiple forms on same page which I want to pass in one go to controller.
How to achieve that?
<script>
function onSaveButtonClicked() {
var form = $("#SaveForm").serialize();
var form2 = $("#SaveForm").serialize();
$.ajax({
url: '#Url.Action("Filter","SearcherEngine")',
type: 'post',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify({ model: form, model2: form2 }),
cache: false,
success: function (result) {
alert(result);
},
error: function (xhr, ajaxOptions, thrownError) {
alert(thrownError);
}
});
};
[HttpPost]
public ActionResult Filter(MyViewMOdel model,MyViewModel2 model2)
{
}
What makes this complex is the different view models per form. As a result, you cannot simply use an array and map to a list of a common class.
Keep in mind that model binding is done based on the structure of the object passed (posted in this case). So what you need to do is create a model that can hold the structure of what you want to pass in c#, and then also create that same structure in javascript to pass in.
public class ViewModelSet
{
public MyViewModel model1 { get; set; }
public MyViewModel1 model2 { get; set; }
}
Now you need to recreate this structure with the exact same naming for properties in javascript
var form1 = ...;
var form2 = ...;
var modelSet = {};
modelSet.model1 = form1;
modelSet.model2 = form2;
Next pass this in (only the name of the parameter must match the name of the expected parameter server side)
data: { model : modelSet },
And expect this to be passed server side
public ActionResult FilterNCTS(ViewModelSet model)
{
//Now you should be able to have access to nested models
// TODO: work with model.model1
...
}
I joined both models in one, and wrapped partial views around one form.
class MyModel
{
...
}
#model MyModel;
#using(Html.BeginForm())
{
Html.RenderPartial("Path",Model);
Html.RenderPartial("Path",Model);
}

adding a new object to observable array in knockout mvc

I'm using knockout mapping to help map a serverside object into JSON. I have an object with numerous collections in it so I don't want to have to recreate and map each piece manually in javascript. So, I'm using knockout-mapping to do this for me.
I was having issues, so I decided to try it with a simple example, so here is what I have for an ASP.NET MVC application:
C# Model:
public class Vaccinations
{
public string Vaccination { get; set; }
public System.DateTime VaccinationDate { get; set; }
}
public class Dog
{
public string Name { get; set; }
public int Age { get; set; }
public Dog()
{
this.Vaccinations = new System.Collections.Generic.List<Vaccinations>();
}
public System.Collections.Generic.List<Vacinations> Vacinations { get; set; }
}
As you can see, each Dog has a list of vaccinations they may or may not have.
In my controller, I create and return a pre-populated Dog object:
public ActionResult Load()
{
Dog rambo = new Dog
{
Name = "Rambo",
Age = 5
};
rambo.Vacinations = new System.Collections.Generic.List<Vacinations> {
new Vacinations { Vacination = "Rabies", VacinationDate = DateTime.Now.AddYears(-1) },
new Vacinations { Vacination = "Mumps", VacinationDate = DateTime.Now.AddYears(-2) }
};
return Json(rambo, JsonRequestBehavior.AllowGet);
}
In my view (Index.cshtml), I have it set up to show the dog and a list of it's vaccinations. I want to allow the user to click on an Add Vaccination button to add a new line to the collection and allow them to enter the data.
Again, I'm using knockout.mapping to do the mapping for me. In the javascript section, this is what I have:
var ViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
self.isValid = ko.computed(function () {
return self.Name().length > 3;
});
// Operations
self.save = function () {
$.ajax({
url: "Dog/Save",
type: "post",
contentType: "application/json",
data: ko.mapping.toJSON(self),
success: function (response) {
alert(response.Status);
}
});
};
self.addVaccination = function () {
self.Vaccinations.push(new self.Vaccination()); // <--- This doesn't work and I know why, so how do I do this?
}
};
$(function () {
$.getJSON("Dog/Load", null, function (data) {
ko.applyBindings(new ViewModel(data));
});
});
My question revolves around the "addVaccination" function that I've added to the ViewModel object. How do I specify a new "Vaccination" object without having to "code" one in Javascript? That was the entire reason for me using knockout mapping so I don't have to do that. But, I don't see any other way around it.
Is there a way to access the base Vaccination object from the Vaccinations observable array so I can create a new one?
And then the final question is, how to I pass this back to my controller? I'm not sure if this will work or not.
You can't directly. But what you can do is define a Vaccination instance at the server side and return it as a the default instance.
So, you need to return the old data and the default instance.
public ActionResult Load()
{
...
var data = new {
defaultVacination = new Vacination(),
rambo = rambo,
};
return Json(data , JsonRequestBehavior.AllowGet);
}
And on the client side you receive the same data and the default instance.
var ViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data.rambo, {}, self);
var defaultInstance = data.defaultVacination;
...
self.addVaccination = function () {
// clone the default instance.
self.Vaccinations.push(ko.utils.extend({}, defaultInstance));
}
I hope it helps.

How to pass a object from ajax to the server in MVC?

I've got two models, there are.
public class CreateAssignmentViewModel {
...
public List<CreateAssignmentSelectedItem> SelectedItems { get; set; }
}
public class CreateAssignmentSelectedItem {
...
}
Now I've got a view where contains CreateAssignmentViewModel, as you can see above this class contains a property where is a List<CreateAssignmentSelectedItem>
#model Contoso.MvcApplication.Models.Assignment.CreateAssignmentViewModel
#{
ViewBag.Title = "Create Assignment";
...
}
#using (Html.BeginForm()) {
...
}
Inside of the Html.BeginForm, I've got a partial view. And in it I've got a button using ajax where updates the partial view.
Look the following events. Where says data: I do not know what to enter to access only the property SelectedItems
var addQuestionToAssignmentContent = function (questionId)
{
$.ajax({
url: "/Assignment/AddItemToAssignmentContent",
type: "post",
data: { model: $(this).serialize() /* HERE I DON'T KNOW TO ACCESS THE */, itemId: questionId },
success: function (response) {
var $target = $("#assignmentContent");
var $newHtml = response;
$target.replaceWith($newHtml);
}
});
};
public ActionResult AddItemToAssignmentContent(List<CreateAssignmentSelectedItem> model, string itemId)
{
...
PartialView(..);
}
How can I do to pass only the object in the method?
First, give your form an ID:
#using (Html.BeginForm("actionName", "controllerName", FormMethod.Post, new{id = "frmUpdate"})) {
Second, change your code to be like this:
var f = $("#frmUpdate");
$.ajax({
url: f.attr('action'),
type: f.attr('method'),
data: f.serialize(),
//etc..
I use this in most cases and it works just nice. The data should automatically be bound to the model you have in your update action. So, for example... if you have a #model of type MyModel then in the update action it should look something like this:
[HttpPost]
public ActionResult Update(MyModel updatedModel)
Sometimes I work with a front end guy and he might not adhere to pass in the correct model, he might change the form fields or whatever. In this case I just let him serialize the form and pass it to the action an any way he wants.
I then use the FormCollection object to get the data I need.
Your json call
var addQuestionToAssignmentContent = function (questionId)
{
$.ajax({
url: "/Assignment/AddItemToAssignmentContent",
type: "post",
data: { model: $(this).serialize() /* HERE I DON'T KNOW TO ACCESS THE */, itemId: questionId },
success: function (response) {
var $target = $("#assignmentContent");
var $newHtml = response;
$target.replaceWith($newHtml);
}
});
};
Get a form collection object
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddItemToAssignmentContent(FormCollection collection)
{
vars someValue = collection.GetValue("somefield").AttemptedValue;
}
But if I would have control of front-end as you do then as Matt suggested you should use an pass a model;

knockoutjs mapping from/to POCO object

Is there a way to map from/to a POCO and knockoutjs observable?
I have a Note class:
public class Note
{
public int ID { get; set; }
public string Date { get; set; }
public string Content { get; set; }
public string Category { get; set; }
public string Background { get; set; }
public string Color { get; set; }
}
and this is my javascript:
$(function () {
ko.applyBindings(new viewModel());
});
function note(date, content, category, color, background) {
this.date = date;
this.content = content;
this.category = category;
this.color = color;
this.background = background;
}
function viewModel () {
this.notes = ko.observableArray([]);
this.newNoteContent = ko.observable();
this.save = function (note) {
$.ajax({
url: '#Url.Action("AddNote")',
data: ko.toJSON({ nota: note }),
type: "post",
contentType: "json",
success: function(result) { }
});
}
var self = this;
$.ajax({
url: '#Url.Action("GetNotes")',
type: "get",
contentType: "json",
async: false,
success: function (data) {
var mappedNotes = $.map(data, function (item) {
return new note(item.Date, item.Content, item.Category, item.Color, item.Background);
});
self.notes(mappedNotes);
}
});
}
Ignore the fact that the save function is not used (to simplify the code here).
So, when I load the page I call the server and I retrieve a list of Note objects and I map it in javascript. Notice how ID is not mapped because I dont need it in my view.
So far so good, I see the notes on screen, but how I can save the notes back to the server?
I tried to convert the note (Im saving just the new note and not the entire collection) to JSON and send it to my controller but I don't know how to access to the note in the controller. I tried:
public string AddNote(string date, string content, string category, string background, string color)
{
// TODO
}
but is not working. I want to have something like:
public string AddNote(Note note) {}
(Btw, what's the best return for a method that just save data on DB? void?)
So, How I do this? I tried knockout.mapping plugin but it is quite confusing and I don't get it working for me.
Thank you.
ASP.NET MVC's model binder will look for properties that are case-sensitive. You need to pass your JSON object back to the server with the property names matching your poco object.
I usually do 1 of 2 things:
Make my javascript object property names capital (that way in JS, I know that this object will at some point be a DTO for the server)
function Note(date, content, category, color, background) {
this.Date = date;
this.Content = content;
this.Category = category;
this.Color = color;
this.Background = background;
};
In my AJAX call i will just create an anonymous object to pass back to the server (note this does not require ko.toJSON):
$.ajax({
url: '#Url.Action("AddNote")',
data: JSON.stringify({ note: {
Date: note.date,
Content: note.content,
Category: note.category,
Color: note.color,
Background: note.background
}
}),
type: "post",
contentType: "application/json; charset=utf-8",
success: function(result) { }
});
(note the different contentType parameter as well)
You will want to make your ActionMethod take in a (Note note) and not just the array of parameters.
Also, because the modelbinders look through the posted values in a couple different ways. I've had luck posting JSON objects with out specifying the ActionMethod parameter name:
instead of:
{ note: {
Date: note.date,
Content: note.content,
Category: note.category,
Color: note.color,
Background: note.background
}
}
just do:
{
Date: note.date,
Content: note.content,
Category: note.category,
Color: note.color,
Background: note.background
}
(but this can get dicey with arrays binding to collections and complex types...etc)
As far as the 'Best' signature for a return on a method that does a db call, we generally prefer to see boolean, but that also depends on your needs. Obviously if it is trivial data, void will be fine, but if its a bit more critical, you may want to relay a boolean (at the least) to let your client know it might need to retry (especially if there's a concurrency exception).
If you really need to let your client know what happened in the database, you can foray into the world of custom error handling and exception catching.
Also, if you need to display very specific information back to your user depending upon a successful/unsuccessful database commit, then you could look at creating custom ActionResults that redirect to certain views based upon what happened in the database transaction.
Lastly, as far as getting data back from the server and using Knockout...
again the mapping plugin will work if your property names are the same case or you create a slightly more explicit mapping
My own trick with my JS objects is below. The initialize function is something i created that should be reusable across all your objects as it just says "if the property names match (after being lowercased), either set them by calling the function (knockout compatible) or just assign the value.:
function Note(values){ //values are what just came back from the server
this.date;
this.content;
this.category;
this.color;
this.background;
initialize(values); //call the prototyped function at the bottom of the constructor
};
Note.prototype.initialize = function(values){
var entity = this; //so we don't get confused
var prop = '';
if (values) {
for (prop in values) {
if (values.hasOwnProperty(prop.toLowerCase()) && entity.hasOwnProperty(prop.toLowerCase())) {
//the setter should have the same name as the property on the values object
if (typeof (entity[prop]) === 'function') {
entity[prop](values[prop]); // we are assuming that the setter only takes one param like a Knockout observable()
} else {// if its not a function, then we will just set the value and overwrite whatever it was previously
entity[prop] = values[prop];
}
}
}
}
};

Resources