knockout.js, asp.net mvc4 - update simpel viewmodel with getJSON data - jquery-mobile

This is a newbie question and not to mention I am very new to knockout.js. All I am trying to do is, get details of a single Grower (Name, Company, Address) from the server and display it on the webpage. I am using $(document).bind('pageinit', function () since I'm using jQuery mobile.
Now my code is:
<h3><span data-bind="text: Name"></span></h3>
<span data-bind="text: Company"></span><br />
<span data-bind="text: Address"></span>
<script type="text/javascript">
$(document).bind('pageinit', function () {
function MyGrowerModel() {
//this.Name = "My Name";
//this.Company = "My Company";
//this.Address = "My Address";
//Load initial state from server, convert it to Task instances, then opulate self.tasks
$.getJSON("Grower/GetGrower", function (allData) {
this.Name = allData.Name;
this.Company = allData.Company;
this.Address = allData.Address;
alert(allData.Name); //works!
});
}
ko.applyBindings(new MyGrowerModel());
});
</script>
I am getting "Unable to parse bindings. Message: ReferenceError: Name is not defined; Binding value: Name
It makes sense because Name, Company and Address are scoped inside the getJSON function. So, my question is, where to declare those variables and how to update those ViewModel data?
I do not want to use the mapping plugin.
Any help is appreciated.

You're going to want to be using observable properties. That way, once your $.getJSON request finishes, your view will update with the new data.
<h3><span data-bind="text: Name"></span></h3>
<span data-bind="text: Company"></span><br />
<span data-bind="text: Address"></span>
<script type="text/javascript">
function MyGrowerModel() {
var self = this;
self.Name = ko.observable('');
self.Company = ko.observable('');
self.Address = ko.observable('');
self.Name.subscribe(function(newValue) {
alert("Name has changed: " + newValue);
});
//Load initial state from server and populate viewmodel
$.getJSON("Grower/GetGrower", function (allData) {
self.Name(allData.Name);
self.Company(allData.Company);
self.Address(allData.Address);
});
}
$(document).bind('pageinit', function () {
ko.applyBindings(new MyGrowerModel());
});
</script>

You need to preserve a reference to the object:
function MyGrowerModel() {
var self = this;
//Load initial state from server, convert it to Task instances, then populate self.tasks
$.getJSON("Grower/GetGrower", function (allData) {
self.Name = allData.Name;
self.Company = allData.Company;
self.Address = allData.Address;
});
}
That won't be the only problem though. $.getJSON is asynchronous by default, you'll either want to set async: false or change the callback function to apply the bindings. A better solution would be to use observables:
function MyGrowerModel() {
var self = this;
self.Name = ko.observable(''); // Value shown before getJSON returns
self.Company = ko.observable('');
self.Address = ko.observable('');
//Load initial state from server, convert it to Task instances, then populate self.tasks
$.getJSON("Grower/GetGrower", function (allData) {
self.Name(allData.Name);
self.Company(allData.Company);
self.Address(allData.Address);
});
}
This will initially display empty strings for each of Name, Company, and Address and will automatically update to the data returned from the getJSON call.

Related

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());
});

select id and label for bootstrap autocomplete

I get typeahead working in my project but only for names. I am not able to process id of that particular label coming in autocomplete.
Bootstrap typeahead-
<input type="text" name="names" value="" id="typeahead" data-provide="typeahead" />
Script-
<script type="text/javascript">
$(function () {
$('#typeahead').typeahead({
source: function (term, process) {
var url = '#Url.Content("~/Invoice/GetNames")';
return $.getJSON(url, { term: term }, function (data) {
return process(data);
});
}
});
})
</script>
Json method-
[HttpGet]
public JsonResult GetNames(string term)
{
var names = (from u in db.Contacts
where u.name.Contains(term)
select u).ToArray();
var results = names.Select(u => u.name);
return new JsonResult()
{
Data = results,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
I am selecting whole table row on match. But How do I get Id with label name.
Or-
I can select whole row at server's side, But the problem persist for filtering result for label and name.
I used this info to also get the ID: http://fusiongrokker.com/post/heavily-customizing-a-bootstrap-typeahead (More complex... part)

Knockoutjs binding JSON

I am have a problem binding a JSON result with knockout js. Below is my code
var AddDeparmentViewModel = function() {
var self = this;
self.AddDepartmentModel = {};
$.getJSON('/EventTracker/Department/GetEmptyModel/', function(data) {
ko.mapping.fromJS(data, {}, self.AddDepartmentModel);
});
};
$(document).ready(function() {
var departmentViewModel = new AddDeparmentViewModel();
ko.applyBindings(departmentViewModel);
})
Here is my HTML:
<div class="titleWrapper">
Add Department
</div>
<label>Department Name:</label>
<input data-bind="value: AddDepartmentModel.DepartmentName" id="departmentNameTextbox" type="text" />
<p data-bind="text: AddDepartmentModel.DepartmentName"></p>
The values I am returning are not appearing. Could someone point out where my mistake is. In this case the server is returning a single object not an array of objects.
Thanks!
Edit:
The Server returns this JSON:
{"DepartmentID":0,"DepartmentName":"Test","EVNTTRKR_Admins":[],"EVNTTRKR_Event": [],"EVNTTRKR_ItemCategories":[]}
Edit:
Here is the function GetEmptyModel:
public JsonResult GetEmptyModel()
{
var eventT = new EVNTTRKR_Departments();
eventT.DepartmentName = "Test";
return Json(eventT, JsonRequestBehavior.AllowGet);
}
The problem you are having seems to be fairly common. You are creating observables after binding the viewmodel. When this happens there is nothing for knockout to hook into and nothing gets rendered. A fix for this would be to make sure AddDepartmentModel is an observable and to set it to the return value rather than overwritting it with the fromJS.
var AddDeparmentViewModel = function() {
var self = this;
self.AddDepartmentModel = ko.observable({}); //important to have a default value so bindings dont break
$.getJSON('/echo/json/', function(data){
var mapped = ko.mapping.fromJS(data);
self.AddDepartmentModel(data); // here we are pushing values into the observable
});
};
This also requires fixes to the bindings as they now need to invoke AddDepartmentModel as a function:
<input data-bind="value: AddDepartmentModel().DepartmentName" id="departmentNameTextbox" type="text" />
Example fiddle with json request and population in callback.
http://jsfiddle.net/infiniteloops/YWC9N/
I created a jsfiddle here and replaced your getJson call with your server data (because I cannot call your getJson url). And it all seems to work which leads me to believe that an error is occuring in the getJson call.
Could you open your browser developer tools and look for any errors in the console. If there is an error and you're not sure what it means, add the error information to your original problem description.
var AddDeparmentViewModel = function() {
var self = this;
self.AddDepartmentModel = {};
var data =
{"DepartmentID":0,"DepartmentName":"Test","EVNTTRKR_Admins":[],"EVNTTRKR_Event": [],"EVNTTRKR_ItemCategories":[]
};
ko.mapping.fromJS(data, {}, self.AddDepartmentModel);
};
$(document).ready(function() {
var departmentViewModel = new AddDeparmentViewModel();
ko.applyBindings(departmentViewModel);
})

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);
});

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