How KnockoutJS Mapping plugin works - asp.net-mvc

here i got a sample code which said KnockoutJS Mapping plugin automatically bind UI.
here is code
<script src="~/Scripts/knockout.mapping-latest.js"></script>
<script type="text/javascript">
$(function() {
var viewModel = ko.mapping.fromJS(#Html.Raw(Model.ToJson()));
ko.applyBindings(viewModel);
});
</script>
suppose my html binding as follows
<p>First name: <strong data-bind="text: firstName"></strong></p>
<p>Last name: <strong data-bind="text: lastName"></strong></p>
and my server side model return json like
{fname:Josh;lname:Steve};
so here my question is how KnockoutJS Mapping plugin could understand that fname value need to map to databinding firstName & lname need to map to databinding lastName ?
am i clear what i am trying to say. so guide me in this case how one could bind json to html UI through KnockoutJS Mapping plugin.
in this situation KnockoutJS Mapping plugin would be right choice or not ?
do i need to bind manually by viewmode like
First name:
Last name:
json as follows var person = {fname:Josh;lname:Steve};
var viewModel = function(person) {
this.firstname= ko.observable(person.fname);
this.lastname= ko.observable(person.lname);
};
ko.applyBindings(new viewModel(person));

As requested in the comment I put this as a possible answer.
If you have model with many properties and want only some of them to map to different names, this is the way to do it:
var viewModel = ko.mapping.fromJS(#Html.Raw(Model.ToJson()));
var extendViewModel = function () {
this.firstName = ko.computed({
read: function(){
return viewModel.fname();
},
write: function(value){
viewModel.fname(value);
},
owner: viewModel
});
};
viewModel = ko.mapping.fromJS(new extendViewModel(), viewModel);
ko.applyBindings(viewModel);
Then you can use firstName in your markup for bindings and viewModel.fname will be updated, too.

Related

KO viewmodel's property is available but viewmodel itself is empty

I have a viewmodel bound on a html page for submitting a form.
When I submit the form, the viewmodel itself is coming up as empty but the observable items within the viewmodel are populated.
Hence I am unable to post the viewmodel to the server as it is coming up null.
Please help!
My html:
<p>First name:<input data-bind='value: firstName' /></p>
<p>Last name:<input data-bind='value: lastName' /></p>
<p><button data-bind='click: saveUserData'>Submit</button></p>
And my script:
function PersonViewModel() {
var self = this;
this.firstName = ko.observable();
this.lastName = ko.observable();
this.saveUserData = function () {
alert(ko.toJSON(self)); // alerts '{}' i.e. nothing
alert(ko.toJSON(self.firstName())); // alerts value of the firsTname
//do the post here
};
};
ko.applyBindings(new PersonViewModel());
I found the root for this issue and am posting the solution here so that it would be helpful to anyone if they stumble upon similar issue.
Solution: Perform the binding (applyBindings function call) after the document is ready. i.e.,
$(document).ready(function () {
ko.applyBindings(new PersonViewModel());
});
Thanks!

ASP.NET MVC 5, Knockout.js and mapping: the right way?

Questions about ASP.NET MVC, Knockout.js and the mapping plugin abound. I have the following (very simple "getting started") example, which works. However, I would like feedback on whether this is the recommended approach. Primarily, I am interested in future models being significantly more complex (children, dropdowns, etc.). I feel maintaining two sets of models is not justifiable, thus my desire to use the mapping plugin.
PS: I wouldn't mind having the form submit, but I couldn't get that to work due to all different issues, apparently related to URL-encoding of string values with quotes by ko.utils.postJSON. Any updates on that would be appreciated also.
HTML
<h2>Simple View</h2>
<p>A simple model with 2 properties</p>
<p>Model property "Name": <span data-bind="text: Name"></span></p>
<p>Model property "Count" (just some integer): <span data-bind="text: Count"></span></p>
<form data-bind="submit: save">
<h3>Editing</h3>
<p>Name: <input type="text" data-bind="value: Name" /></p>
<p>Count: <input type="text" data-bind="value: Count" /></p>
<h3>Posting Back</h3>
<button type="submit">Update</button>
</form>
JavaScript
<script type="text/javascript">
var KoViewModel;
$(function () {
KoViewModel = ko.mapping.fromJSON('#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model))');
ko.applyBindings(KoViewModel);
});
var save = function () {
$.ajax({
url: location.href,
type: 'POST',
data: ko.mapping.toJSON(KoViewModel),
contentType: 'application/json; charset=utf-8',
success: function (data) {
ko.mapping.fromJSON(data, KoViewModel);
}
});
}
</script>
Server-Side
public ActionResult Index()
{
Models.SimpleModel m = new Models.SimpleModel();
m.Name = "Some name";
m.Count = 1;
return View(m);
}
[HttpPost]
public JsonResult Index(Models.SimpleModel fc)
{
fc.Name += " (updated)";
fc.Count++;
return (new JsonResult() { Data = Newtonsoft.Json.JsonConvert.SerializeObject(fc) });
}
Thanks for any insights!
I would structure the script differently.
function SimpleModel(data) {
var self = this;
// data
self.Name = ko.observable();
self.Count = ko.observable();
// api
self.load = function (data) {
if (data) ko.mapping.fromJSON(data, {}, self);
};
self.save = function () {
return $.ajax({
type: 'POST',
url: location.href,
data: ko.mapping.toJSON(self),
contentType: 'application/json; charset=utf-8',
}).done(self.load);
};
// init
self.load(data);
}
// later... -------------------------------------------------------------------------
$(function () {
var data = '#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model))';
ko.applyBindings( new SimpleModel(data) );
});
Advantages:
All data properties of the viewmodel are explicitly defined. This helps reduce bugs because it is immediately obvious what a viewmodel looks like. It also helps reduce run-time errors because properties will exist even if they happen to be missing on the JSON input. This way, the view can always bind without any problems.
You can move the viewmodel to a separate script file.
The viewmodel can initialize itself from the data you pass into the constructor. This will be useful if you refactor the application - for example when your viewmodel suddenly becomes a part of a larger viewmodel.
No awkward references to global variables or separate helper functions, the viewmodel is self-contained.
Returning the Ajax promise from the save method allows you to attach extra behavior without rewiring the viewmodel:
var vm = new SimpleModel(data);
// programmatically save the model
vm.save().done(function () {
// whatever
localStorage.setItem('lastAutoSave', Date.now());
});

Knockout Mapping attempt not mapping with JSON responses

I am new to knockout and cannot seem to get a simple mapping to work at all. I am in an MVC4 C#.
My View looks like such:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
ViewBag.Title = "Index";
}
<h2>Simple Mapping Example</h2>
The time on the server is: <span data-bind="text: ServerTime"></span>
and <span data-bind="text: NumberOfUsersConnected"></span> user(s) are connected.
#section Scripts
{
<script type="text/javascript">
function ViewModel() {
var self = this;
self.ServerTime = ko.observable();
self.NumberOfUsersConnected = ko.observable();
}
var vm = new ViewModel();
ko.applyBindings(vm);
$.getJSON('/Mapping/GetServerInfo', function(data) {
ko.mapping.fromJSON(data, {}, vm);
});
</script>
}
Example JSON that is returned from the controller call 'Mapping/GetServerInfo' is:
{"ServerTime":"2013-03-13T14:24:10.5675104-07:00","NumberOfUsersConnected":5}
For the data-binds I have tried text and value, neither one causes any data to be bound and displayed.
Am I missing something really obvious?
As per the question in the comments yes this is what is in my Layout.
<script type="text/javascript" src="~/Scripts/knockout-2.2.1.js"></script>
<script type="text/javascript" src="~/Scripts/knockout.mapping-latest.js"></script>
Thanks
From the $.getJSON documentation
The success callback is passed the returned data, which is typically a
JavaScript object or array as defined by the JSON structure and parsed
using the $.parseJSON() method.
So your the data variable is holding an already parsed object so you need to use the ko.mapping.fromJS method:
$.getJSON('/Mapping/GetServerInfo', function(data) {
ko.mapping.fromJS(data, {}, vm);
});

MVC Model Binding before KnockoutJS Model Binding

So if your Controller Action returns a Model with pre-populated values, how do you make KnockoutJS aware of them?
E.g.:
#Html.TextBoxFor(m => m.Title, new { data_bind="value: title"} )
however, on $(document).ready() where I bind knockout.js ViewModel, this value isn't yet populated:
$(document).ready({
var viewModel = {
title: ko.observable($("#Title").val()) // too early for this?!
}
ko.applyBindings(viewModel);​
});
How do you make KnockoutJS work with MVC's model binding?
One workaround I found was to set the JavaScript variable in my Razor View, like so:
<script>
var strTitle = '#Model.Title';
</script>
and than use it in the Knockout model binding. That works, but I hate it. What if your form has like hundreds of fields? You don't want as many JavaScript variables in your page.
Am I missing the obvious here?
This seems similar to this question. Normally you would set your view model by converting #Model to JSON in a script:
<script type="text/javascript">
var model = #(new HtmlString(new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(Model)));
</script>
You could also create your own binding handler that will initially load the view model based on control values. This new myvalue handler basically calls the existing value handler, except it updates the view model from the initial control value on init.
ko.bindingHandlers['myvalue'] = {
'init': function (element, valueAccessor, allBindingsAccessor) {
// call existing value init code
ko.bindingHandlers['value'].init(element, valueAccessor, allBindingsAccessor);
// valueUpdateHandler() code
var modelValue = valueAccessor();
var elementValue = ko.selectExtensions.readValue(element);
modelValue(elementValue); // simplified next line, writeValueToProperty isn't exported
//ko.jsonExpressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'value', elementValue, /* checkIfDifferent: */ true);
},
'update': function (element, valueAccessor) {
// call existing value update code
ko.bindingHandlers['value'].update(element, valueAccessor);
}
};
Then when you call ko.applyBindings, your observable will be set based on the control's value initially:
<input type="text" data-bind="myvalue: Title" value="This Title will be used" />
<input type="text" data-bind="value: Title" value="This will not be used" />
<!-- MVC -->
#Html.TextBoxFor(m => m.Title, new { data_bind="myvalue: Title"} )
SAMPLE FIDDLE
What about simply serializing your entire page model to json using JSON.NET or similar. Then your page will be populated via normal razor view bindings for non-js users. Then your page scripts can be something like:
<script>
ko.applyBindings(#Html.ToJSON(Model));
</script>
Or if you have a typed viewModel
<script>
var viewModel = new MyViewModel(#Html.ToJSON(Model));
ko.applyBindings(viewModel);
</script>
It makes sense to structure your client side and actual view models the same so no manipulation of the json shape is required.
EDIT
Example of the toJSON helper.
public static MvcHtmlString ToJson(this HtmlHelper html, object obj)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
return MvcHtmlString.Create(serializer.Serialize(obj));
}
Hope this helps.
Because I don't have a 50 point reputation to add a comment to the Jason Goemaat answer, I decided to add my comment here as an answer.
All the credits go to Jason Goemaat.
I wasn't able to make the code work for me. So I had to make a small change.
ko.bindingHandlers['myvalue'] = {
'init': function (element, valueAccessor, allBindingsAccessor) {
//get initial state of the element
var modelValue = valueAccessor();
var elementValue = ko.selectExtensions.readValue(element);
// call existing value init code
ko.bindingHandlers['value'].init(element, valueAccessor, allBindingsAccessor);
//Save initial state
modelValue(elementValue);
},
'update': function (element, valueAccessor) {
// call existing value update code
ko.bindingHandlers['value'].update(element, valueAccessor);
}
};
if I had this line at the top,
// call existing value init code
ko.bindingHandlers['value'].init(element, valueAccessor, allBindingsAccessor);
It was deleting the original state of the element. So whatever value I had in the input field, it was being deleted.
Inside the model I have this:
//publishing comment
self.publishingComment = ko.observable();
And my MVC looks like this
#Html.TextBoxFor(model => model.Comment, new { data_bind = "myvalue: publishingComment" })

SPA Knockout JS Filter

I developed MVC 4 Single Page Application using ADO.Net as a data source. Trying to filter the view by ID, tried session variables without any luck. Here is the view code:
<script type="text/javascript" src="#Url.Content("~/Scripts/BloodPressuresViewModel.js")"></script>
<script type="text/javascript">
$(function () {
upshot.metadata(#(Html.Metadata<KOTest2.Controllers.DALController>()));
var viewModel = new MyApp.BloodPressuresViewModel({
serviceUrl: "#Url.Content("~/api/DAL")"
});
ko.applyBindings(viewModel);
});
</script>
and hee is the calss code in the Javascript file:
.....
var entityType = "BloodPressure:#KOTest2.Models";
MyApp.BloodPressure = function (data) {
var self = this;
// Underlying data
self.ID = ko.observable(data.ID);
self.PHN = ko.observable(data.PHN);
self.Day = ko.observable(data.Day);
self.Systolic = ko.observable(data.Systolic);
self.Diastolic = ko.observable(data.Diastolic);
self.HeartRate = ko.observable(data.HeartRate);
upshot.addEntityProperties(self, entityType);
}
.....
I think the best solution is to pass the ID using ViewBag to the view from the controller. Any idea how I can do that!!
Since I am not experienced programmer, will it be possible to filter (foreach)
<tbody data-bind="foreach: bloodPressures">
Thanks in advance.
I am not sure I understand how you access the database (on the server, right?) to do the filtering, but you could do something like this:
<table data-bind="foreach: rows">
<tr>
<td>id: <span data-bind="text: ID"></span></td>
<td>PHN: <span data-bind="text: PHN"></span></td>
....
</tr>
</table>
and in your javascript
function viewModel() {
var self = this;
this.loggedIn = ko.observable(false);
this.rows = ko.observableArray([]);
// return an array of objects to display to the user
function getDataFromServer() {
return ...;
}
ko.computed(function() {
if (this.loggedIn())
this.rows(getDataFromServer());
},this);
...
}
However you do your authentication, after it succeeds, execute this.loggedIn(true) which will cause the computed function to trigger the pull from the server and the setting of the this.rows(); this in turn will update the display.

Resources