Reputation: 4921
I'm a C# MVC developer wanting to evolve my client side javascript from a tangle of event handlers to a more organized system, so I am starting to look at knockoutjs. Initially it looks great and ideas about how to put it into my production environment are quickly forming. I have tried some of the basic principles all of which worked quite nicely without an issue. I am wanting to try something a bit more complicated (which is specific to a project I am working on) but I can't get it work.
I tried searching for other examples of this but didn't really find anything that matched.
What I would like to do is bind a list of objects to the client side. In this example I am using a list of people, of which I am displaying on the page, and would like to update all people on the page on an ajax request (this example is purely fabricated for testing, but the principle is what I am after).
I am using the knockoutjs mapping plugin to do the model mapping for me
my C# Code
public class HomeController : Controller
{
public ActionResult Index()
{
List<Person> people = new List<Person>()
{
new Person()
{
FirstName = "Neil",
LastName = "Diamond"
},
new Person()
{
FirstName = "Bob",
LastName = "Seager"
},
new Person()
{
FirstName = "Tom",
LastName = "Jones"
}
};
return View(people);
}
public JsonResult UpdateNames()
{
Random r = new Random();
var num = r.Next(1, 100);
List<Person> people = new List<Person>()
{
new Person()
{
FirstName = string.Concat("Neil", num.ToString()),
LastName = string.Concat("Diamond", num.ToString())
},
new Person()
{
FirstName = string.Concat("Bob", num.ToString()),
LastName = string.Concat("Seager", num.ToString())
},
new Person()
{
FirstName = string.Concat("Tom", num.ToString()),
LastName = string.Concat("Jones", num.ToString())
}
};
return Json(people, JsonRequestBehavior.AllowGet);
}
}
my view
@model List<TestingKnockout.Models.Person>
@{
ViewBag.Title = "Home Page";
}
@for (int i = 0; i < Model.Count; i++)
{
<p>Firstname: <strong data-bind="text: [@i].FirstName"></strong></p>
<p>Lastname: <strong data-bind="text: [@i].LastName"></strong></p>
<hr />
}
@section scripts
{
<script src="~/Scripts/knockout-2.1.0.js"></script>
<script src="~/Scripts/knockout.mapping.js"></script>
<script type="text/javascript">
var viewModel = {};
$.ajax({
url: "/Home/UpdateNames",
cache: false,
success: function (data) {
viewModel = ko.mapping.fromJS(data);
ko.applyBindings(viewModel);
}
});
setInterval(function () {
$.ajax(
{
url: "/Home/UpdateNames",
cache: false,
success: function (data) {
ko.mapping.fromJS(data, viewModel);
}
});
}, 5000);
</script>
}
Any help would be greatly appreciated, or if I am missing the obvious or if this question has come up before please point me in the right direction.
Regards
EDIT
with both answers from Tomas and Rodney, it appears I was incorrectly assuming some behavior of knockout. Knockout has to start with the whole model as JSON and then update from there. I was hoping to render the HTML with initial data and then get knockout to process updates, but I think my approach was incorrect. With the HTML from Tomas and taking a lead from the code from Rodney (minor changes to the json serialization and people observable) I changed my view code to the following and all seems to work nicely now
@model List<TestingKnockout.Models.Person>
@{
ViewBag.Title = "Home Page";
}
<div data-bind="foreach: people">
<p>Firstname: <strong data-bind="text: FirstName"></strong></p>
<p>Lastname: <strong data-bind="text: LastName"></strong></p>
<hr />
</div>
@section scripts
{
<script src="~/Scripts/knockout-2.1.0.js"></script>
<script src="~/Scripts/knockout.mapping.js"></script>
<script type="text/javascript">
var viewModel = {
people: ko.mapping.fromJS(@Html.Raw(Json.Encode(Model)))
};
ko.applyBindings(viewModel);
setInterval(function () {
$.ajax(
{
url: "/Home/UpdateNames",
cache: false,
success: function (data) {
ko.mapping.fromJS(data, viewModel.people);
}
});
}, 5000);
</script>
}
I would like to give the answer to both Tomas and Rodney. Having to choose, I think Rodney helped the most so sorry Tomas.
Upvotes: 0
Views: 4381
Reputation: 1166
Use the HTML from Tomas's answer.
You will need to serialize your initial c# model into a javascript list. A popular way of doing this is with the JSON.NET library.
Add this c# code to the beginning of the view
@using Newtonsoft.Json
@{
var jsPeople = Html.Raw(JsonConvert.SerializeObject(Model));
}
Replace all of your javascript with the following:
var viewModel = { people: ko.observable([]) };
viewModel.people(ko.mapping.fromJS(@jsPeople));
setInterval(function () {
$.ajax(
{
url: "/Home/UpdateNames",
cache: false,
success: function (data) {
viewModel.people(ko.mapping.fromJS(data));
}
});
}, 5000);
$(document).ready(function(){
ko.applyBindings(viewModel);
});
Oh, and I should mention that I have used jquery here to perform the applyBindings when the document is ready.
Upvotes: 3
Reputation: 8413
You are missing the point on how this should be used. You should setup your view templates (HTML). Then load then data (or it could come together with the page that is being served) in a JSON format.
Applying bindings can be done before loading data or after initial data set was loaded. But that should happen only once. You should let go your habit where you render model using ASP.NET MVC view. Knockout should be responsible for binding data to your HTML template.
You can control what is visible on the screen before data loaded or while it's loading. And your application logic would be in your JavaScript, where you manipulate objects and UI updates are handled by Knockout.
<div data-bind="foreach: people">
<p>Firstname: <strong data-bind="text: FirstName"></strong></p>
<p>Lastname: <strong data-bind="text: LastName"></strong></p>
</div>
Upvotes: 2