Reputation: 8188
I have a model called customer:
function customer(id, name, age, comments) {
var self = this;
self.Id = id;
self.Name = name;
self.Age = age,
self.Comments = comments;
self.addCustomer = function () {
$.ajax({
url: "/api/customer/",
type: 'post',
data: ko.toJSON(this),
contentType: 'application/json',
success: function (result) {
//SOMETHINGS WRONG HERE
customerVM();
}
});
}
}
After I add a customer the list of customers doesnt automatically update. Calling customerVM() in the model goes into the viewModel function but never goes into the getCustomers function, so I must be calling the viewModel wrong. Thats what I am seeing from debugging.
The function that displays the list is in the viewModel:
function customerVM() {
var self = this;
self.customers = ko.observableArray([]);
self.getCustomers = function () {
self.customers.removeAll();
$.getJSON("/api/customer/", function (data) {
$.each(data, function (key, val) {
self.customers.push(new customer(val.Id, val.Name, val.Age, val.Comments));
});
});
};
}
I need to call getCustomers somehow after adding a customer. How might I do this??
Here is the customers html
<table >
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Age</th>
<th>Comments</th>
</tr>
</thead>
<tbody data-bind="foreach: customers" >
<tr>
<td data-bind="text: Id"></td>
<td data-bind="text: Name"></td>
<td data-bind="text: Age"></td>
<td data-bind="text: Comments"></td>
</tr>
</tbody>
</table>
<br />
<input type="button" id="btnGetCustomers" value="Get Customers" data-bind="click: getCustomers" />
Upvotes: 2
Views: 17808
Reputation: 1802
I think the deeper question you're asking here is about how to manage your data in the browser and keep it in sync with the data on the server. I answered a similar question yesterday which you can read here : Adding new elements to a knockout observable array - best practices?
In your question and code, it looks like you're trying to insert a new customer to the server, and then read the entire list of customers back into your view model after the insert. You may have a need for this which you haven't mentioned, but generally this pattern isn't necessary.
The most common case for requiring a read from the server after an insert to the server is to retrieve a server-generated identifier for the object you just added. With most MV* browser-side frameworks - including Knockout - a common pattern is for the "insert" api call to return the ID of the new element (or however much data you need to return) and simply update the client-side version of that model with the new ID. Knockout will automatically update your UI provided that the ID property is an observable. Here's an example:
var customer = function(name, age, comments){
var self = this;
self.id = ko.observable();//unknown when created in the browser
self.name = ko.observable(name);
self.age = ko.observable(age);
self.comments = ko.observable(comments);
}
var customersViewModel = function(){
var self = this;
self.customers = ko.observableArray([]);
self.addCustomer = function(customer){
self.customers.push(customer);
$.ajax({
url: "/api/customer/add",
type: 'post',
data: ko.toJSON(this),
contentType: 'application/json',
success: function (result) {
//assuming result will contain the server-side customer id
//we provide that value to our customer's id observable
//and knockout will update the UI
customer.id(result.newCustomerId);
//no need to update the entire array, and
//our 'customer' has already been pushed into our
//observable array so we're done.
}
});
}
}
While your customer is in a "pending" state (while the browser is waiting for the server to respond to the insert api call) you know that the customer wont have an ID. You can use that in your bindings to apply a "pending" class to your customer like this:
<tbody data-bind="foreach: customers" >
<tr data-bind="css : {'pending': !id()}">
<td data-bind="text: id"></td>
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
<td data-bind="text: comments"></td>
</tr>
</tbody>
I hope this helps!
Upvotes: 2
Reputation: 1166
If you have performed the applyBindings like Tom has suggested:
<script type="text/javascript">
var vm = new customerVM();
vm.getCustomers();
ko.applyBindings(vm);
</script>
Then your addCustomer method should look something like this:
self.addCustomer = function () {
$.ajax({
url: "/api/customer/",
type: 'post',
data: ko.toJSON(this),
contentType: 'application/json',
success: function (result) {
vm.getCustomers(); //*******THIS IS THE CHANGE YOU NEED
}
});
}
Although, I agree with Tom that it would be more appropriate to move the addCustomer method into the root model.
Upvotes: 1
Reputation: 10452
I think your design actually has a few flaws. First of all, you don't want to be calling customerVM()
at any point after you've called ko.applyBindings(vm);
(Which I don't actually see anywhere in your html)
Somewhere in your html page you should have:
<script type="text/javascript">
var vm = new customerVM();
ko.applyBindings(vm);
</script>
Secondly, think of customerVM()
as containing a collection of customers
and being responsible for managing the populating of your customers
collection, as well as passing the collection (or a single customer
) to your API for persistence. Meaning, I would take the addCustomer function out of the customer object and move it into the customerVM.
// customer is just a simple data object
function customer(id, name, age, comments) {
var self = this;
self.Id = ko.observable(id);
self.Name = ko.observable(name);
self.Age = ko.observable(age);
self.Comments = ko.observable(comments);
}
function customerVM() {
var self = this;
self.customers = ko.observableArray([]);
self.getCustomers = function () {
self.customers.removeAll();
$.getJSON("/api/customer/", function (data) {
$.each(data, function (key, val) {
self.customers.push(new customer(val.Id, val.Name, val.Age, val.Comments));
});
});
};
self.addCustomer = function (customer) {
// pass in a single customer for persistence
}
}
Upvotes: 2