I use knockoutjs to update my Html view when an Javascript (Signalr) function is fired. But the view does not update when producthub.client.showOnlineUser is called. When i apply the binding everytime the table has same content multiple times. How can i just update the view in knockoutjs?
Html:
<table id="usersTable">
<thead>
<tr>
<th>Name</th>
<th>Mail</th>
</tr>
</thead>
<tbody data-bind="foreach: seats">
<tr>
<td data-bind="text: NameFirst"></td>
<td data-bind="text: Mail"></td>
</tr>
</tbody>
</table>
Javascript:
$(document).ready(function () {
var applied = false;
var model;
producthub.client.showOnlineUser = function (userOnlineOnUrl, msg1) {
function ReservationsViewModel() {
var self = this;
self.seats = ko.observableArray(userOnlineOnUrl);
}
model = new ReservationsViewModel();
if (!applied) {
ko.applyBindings(model);
applied = true;
}
};
});
userOnlineOnUrl is an JSON-Array which changes it's data on each function call. The view (table) is not updated with it's data.
try not to call model every time just update it with function
$(document).ready(function () {
var applied = false;
var model;
function ReservationsViewModel() {
var self = this;
self.seats = ko.observableArray();
}
model = new ReservationsViewModel();
ko.applyBindings(model);
applied = true;
producthub.client.showOnlineUser = function (userOnlineOnUrl, msg1) {
model = new ReservationsViewModel();
//remove the previous data in array
model.seats.removeAll();
//Add new data to array
model.seats(userOnlineOnUrl);
$('#onlineUsers').append(msg1);
};
});
Related
I'm new to knockout.js and I have some trouble with two-way binding of a checkbox.
I have a table that's bound to a list of "companies".
<table>
<thead>
<tr>
<th>...</th>
<th>...</th>
<th>...</th>
</tr>
</thead>
<tbody data-bind="foreach: companies">
<tr>
<td>...</td>
<td><input type="checkbox" data-bind="checked:isCompanyForCommunication, click:$root.checkedNewCompanyForCommunication" /></td>
<td>...</td>
</tr>
</tbody>
</table>
But there should only be 1 company with isCompanyForCommunication = true in the table. So when another checkbox is checked, I have to set all other companies to isCompanyForCommunication = false. Therefore I created a method in the viewModel to uncheck all other companies.
// Definition of Company
var Company = function (crmAccountObject, contactId, companyForCommunicationId) {
this.isCompanyForCommunication = ko.observable(false);
}
// Definition of the ViewModel
var ViewModel = function () {
var self = this;
// ...
// Lists
this.companies = null; // This observableArray is set somewhere else
// Events
this.checkedNewCompanyForCommunication = function (company, event) {
// Set all companies to False
for (var i = 0; i < self.companies().length; i++) {
self.companies()[i].isCompanyForCommunication = ko.observable(false);
}
// Set the "checked" company to TRUE
company.isCompanyForCommunication = ko.observable(true);
return true;
}
}
However, the changes are not reflected in the HTML page. All other checkboxes remain checked.
I created a simplyfied example in jsfiddle to show what exactly I want to achieve with the checkbox https://jsfiddle.net/htZfX/59/
Someone has any idea what I'm doing wrong? Thanks in advance!
You are not setting the value of the isCompanyForCommunication but overriding it with a new observable.
Observables are functions and to update them you need to call them with the new value as the argument:
this.checkedNewCompanyForCommunication = function (company, event) {
// Set all companies to False
for (var i = 0; i < self.companies().length; i++) {
self.companies()[i].isCompanyForCommunication(false);
}
// Set the "checked" company to TRUE
company.isCompanyForCommunication(true);
return true;
}
I've also updated your sample code: JSFiddle.
Here is a sample for the problem, in this case someone selects toppings for a pizza (my real world problem is analogue to that):
http://jsfiddle.net/csabatoth/aUH2C/4/
HTML:
<h2>Pizza toppings (<span data-bind="text: toppings().length"></span>)</h2>
<table class="table table-bordered">
<thead><tr>
<th>Topping</th><th>Number of units</th>
</tr></thead>
<tbody data-bind="foreach: toppings">
<tr>
<td><select data-bind="options: $root.availableToppings(), value: name, optionsText: 'name'"></select></td>
<td><input data-bind="value: num" /></td>
<td>Remove</td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-primary" data-bind="click: addTopping, enable: toppings().length < 5">Add another topping</button>
JS:
// Class to represent a possible topping
function Topping(name) {
var self = this;
self.name = name;
// This will have other properties
}
// Class to represent a row in the grid
function ToppingRow(topping, num) {
var self = this;
self.topping = ko.observable(topping);
self.num = ko.observable(num);
self.toppingName = ko.computed(function() {
return self.topping().name;
});
}
function ToppingsViewModel() {
var self = this;
// Non-editable catalog data - would come from the server
self.availableToppings = ko.observableArray([
new Topping("Mushroom"),
new Topping("Pepperoni"),
new Topping("Cheese"),
new Topping("Olives"),
new Topping("Chicken")
]);
// Editable data
self.toppings = ko.observableArray([
new ToppingRow(self.availableToppings()[0], 1)
]);
// Operations
self.addTopping = function() {
self.toppings.push(new ToppingRow(self.availableToppings()[0], 1));
}
self.removeTopping = function(topp) { self.toppings.remove(topp) }
}
ko.applyBindings(new ToppingsViewModel());
What I would like is: when the user selects a topping, that option should disappear from the popup list. Once the user removes the topping, it should reappear in the popup. In other words: I don't want the user to add the same topping more than once. How to do that?
(Or now I think if I should approach this in a totally different way and would have a list with the toppings on the left, and the user could drag&drop to the right destination list from there...). In the real world example the number of "toppings" would be maybe some dozen I think.
You could simplify this slightly by having a topping selector with 1 drop down. Then when you click add it inserts the currently selected item to a selected toppings section and then removes the option from the available list.
If you are feeling clever you could also bind the drop-down to a computed collection of items that does not include already selected items. (underscore.js will also help with this immensely).
(Fiddle)
JS
// Class to represent a possible topping
function Topping(name) {
var self = this;
self.name = name;
// This will have other properties
}
// Class to represent a row in the grid
function ToppingRow(topping, num) {
var self = this;
self.topping = topping;
self.num = ko.observable(num);
self.toppingName = ko.computed(function() {
return self.topping.name;
});
}
function ToppingsViewModel() {
var self = this;
// Non-editable catalog data - would come from the server
self.allToppings = ko.observableArray([
new Topping("Mushroom"),
new Topping("Pepperoni"),
new Topping("Cheese"),
new Topping("Olives"),
new Topping("Chicken")
]);
self.selectedToppings = ko.observableArray([]);
self.availableToppings = ko.computed(function(){
return _.reject(self.allToppings(), function(topping) {
return _.contains(_.pluck(self.selectedToppings(), 'topping'), topping);
})
});
self.currentlySelectedTopping = ko.observable();
self.currentlySelectedToppingNumber = ko.observable(1);
// Operations
self.addTopping = function() {
self.selectedToppings.push(new ToppingRow(self.currentlySelectedTopping(), self.currentlySelectedToppingNumber()));
}
//self.removeTopping = function(topp) { self.toppings.remove(topp) }
}
ko.applyBindings(new ToppingsViewModel());
HTML
<h2>Pizza toppings</h2>
<table class="table table-bordered">
<thead><tr>
<th>Topping</th><th>Number of units</th>
</tr></thead>
<tbody>
<tr>
<td><select data-bind="options: availableToppings, value: currentlySelectedTopping, optionsText: 'name'"></select></td>
<td><input data-bind="value: currentlySelectedToppingNumber" /></td>
<td>Remove</td>
</tr>
<!-- ko foreach: selectedToppings -->
<tr><td data-bind="text: toppingName"></td><td data-bind="text: num"></td></tr>
<!-- /ko -->
</tbody>
</table>
<button type="button" class="btn btn-primary" data-bind="click: addTopping, enable: availableToppings().length">Add another topping</button>
I dont why you are making simple things difficult. Here is some modified version.
function Topping(name) {
var self = this;
self.name = name;
self.active = ko.observable(false)
self.toggle = function () {
self.active(!self.active())
}
// This will have other properties
}
// Class to represent a row in the grid
function ToppingRow(name, num) {
var self = this;
self.name = name;
self.num = num;
}
function ToppingsViewModel() {
var self = this;
// Non-editable catalog data - would come from the server
self.availableToppings = ko.observableArray([
new Topping("Mushroom"),
new Topping("Pepperoni"),
new Topping("Cheese"),
new Topping("Olives"),
new Topping("Chicken")
]);
self.list = ko.observableArray()
self.num = ko.observable(1)
self.selected = ko.observable()
// Operations
self.addTopping = function() {
self.list.push(new ToppingRow(self.selected(),self.num()));
self.setAvailableToppings(self.selected())
}
self.removeTopping = function(item) {
self.list.remove(item)
self.setAvailableToppings(item.name)
}
self.setAvailableToppings = function (name) {
var items = []
ko.utils.arrayForEach(self.availableToppings(),function (item) {
if(item.name == name){
item.toggle()
}
items.push(item)
})
self.availableToppings([])
self.availableToppings(items)
var selected = ko.utils.arrayFirst(self.availableToppings(),function (item) {
return item.active() == false
})
if(selected){
self.selected(selected.name)
}
}
self.setOptionDisable = function(option, item) {
ko.applyBindingsToNode(option, {disable: item.active()}, item);
}
}
$(document).ready(function(){
ko.applyBindings(new ToppingsViewModel());
})
And view
<h2>Pizza toppings (<span data-bind="text: list().length"></span>)</h2>
<table class="table table-bordered">
<thead><tr>
<th>Topping</th><th>Number of units</th>
</tr></thead>
<tbody data-bind="foreach: list">
<tr>
<td data-bind="text:name"></td>
<td data-bind="text: num"></td>
<td>Remove</td>
</tr>
</tbody>
</table>
<br clear="all"/>
<select data-bind="
options: $root.availableToppings(),
value: $root.selected,
optionsText: 'name',
optionsValue : 'name',
optionsAfterRender: $root.setOptionDisable,
enable: list().length < 5
">
</select>
<input data-bind="value: $root.num,enable: list().length < 5" />
<br clear="all"/>
<button type="button" class="btn btn-primary"
data-bind="
click: addTopping,
enable: list().length < 5
">Add another topping</button>
Fiddle Demo
I have an issue in below example
HTML CODE:
<table id="items">
<thead>
<tr>
<td>ToDo List</td>
</tr>
</thead>
<tbody data-bind="foreach: toDoItems">
<tr>
<td><label data-bind="text: toDoItem"></label></td>
<td>Remove</td>
</tr>
</tbody>
</table>
Add item: <input type="text" id="newitem" />
<button data-bind="click: addnewItem">Add</button>
This is my JS code:
$(function () {
var MetalViewModel = function() {
var self = this;
self.toDoItems = ko.observableArray();
self.update = function() {
self.toDoItems.removeAll();
self.toDoItems.push(
new metals({"Task":"This is urgent task."}),
new metals({"Task":"You need to do it also."})
);
}
self.addnewItem = function () {
alert( $("#newitem").val() );
self.toDoItems.push(
new metals({"Task":$("#newitem").val()})
);
};
self.removeToDoItem = function(item) {
self.toDoItems.remove(item);
};
};
var MetalViewModel = new MetalViewModel();
var y = window.setInterval(MetalViewModel.update,1000);
ko.applyBindings(MetalViewModel, document.getElementById("items"));
});
var metals = function (data) {
return {
toDoItem: ko.observable(data.Task)
};
};
Everything is working fine. Listing, removing ....
The only issue I am facing is that when I add new item, function is totally not working... means even if I alert click binding does not reach there.
You bound only the table with your vewmodel (id=items):
ko.applyBindings(MetalViewModel, document.getElementById("items"));
And your button is outside the table so it is not related to your viewmodel.
As a solution, you can bind to the common parent or to the whole page:
ko.applyBindings(MetalViewModel);
Here're two issues:
invalid binding, instead of ko.applyBindings(MetalViewModel, document.getElementById("items")); should be ko.applyBindings(MetalViewModel);
invalid data-bind function name instead of additemToAdd should be addToDoItem
here's working sample
I have this Viewmodel to load the users and their list of Socialgraphs from WCF services. The users appear correct but no socialgraph entry appears. I have checked the service and json returned and all seems ok.
Should I change my Models to sth different or is it the way I'm loading stuff in the ViewModel? thanks
$(document).ready(function () {
var viewModel = {
users: ko.observableArray([]),
loadUsers: function () {
OData.read("Service_UserProfile/", function (data) {
viewModel.users.removeAll();
$.each(data.results, function (index, item) {
var socialgraphs = viewModel.loadSocialGraph();
var user = new UserProfileModel(item, socialgraphs);
viewModel.users.push(user);
});
});
},
loadSocialGraph: function () {
var result = new Array();
// user id will be loaded dynamically in later steps
OData.read("/Service_UserProfile(1)/Socialgraph/", function (data) {
$.each(data.results, function (index, item) {
result.push(new SocialGraph(item));
});
});
return result;
}
};
ko.applyBindings(viewModel);
viewModel.loadUsers();
});
The Model
function UserProfileModel(item,socialgraphs) {
this.Id = ko.observable(item.Id),
this.Nickname = ko.observable(item.Nickname),
this.socialgraphs = ko.observableArray(socialgraphs)
};
function SocialGraph(item) {
this.Id = ko.observable(item.Id),
this.StartTime = ko.observable(item.StartTime),
this.Latitude = ko.observable(item.Latitude),
this.Longitude = ko.observable(item.Longitude)
};
The View
<table>
<thead>
<tr>
<th>User ID</th>
<th>Nickname
</th>
<th>Social Graph
</th>
</tr>
</thead>
<tbody data-bind="foreach: users">
<tr>
<td data-bind="text: Id"></td>
<td data-bind="text: Nickname"></td>
<td>
<ul data-bind="foreach: socialgraphs">
<li data-bind="text: Id"></li>
<li data-bind="dateString: StartTime"></li>
<li data-bind="text: Latitude"></li>
<li data-bind="text: Longitude"></li>
</ul>
</td>
</tr>
</tbody>
</table>
You should change:
loadSocialGraph: function () {
var result = ko.observableArray();
// user id will be loaded dynamically in later steps
OData.read("/Service_UserProfile(1)/Socialgraph/", function (data) {
$.each(data.results, function (index, item) {
result.push(new SocialGraph(item));
});
});
return result;
}
and
function UserProfileModel(item,socialgraphs) {
this.Id = ko.observable(item.Id),
this.Nickname = ko.observable(item.Nickname),
this.socialgraphs = socialgraphs
};
Why:
At the line this.socialgraphs = ko.observableArray(socialgraphs)
socialgraphs in the right part is []. Only after some time interval it
will be filled with values. And because of it is not observable array,
knockout won't notice that some items were pushed in it. I.e. it will stay empty.
I'm trying to bind a 1-many mapping using KnockoutJS, where 1 zip code can have many 'agents'. I have the following classes:
function CaseAssignmentZipCode(zipcode, agent) {
var self = this;
self.zipcode = ko.observable(zipcode);
self.agent = ko.observable(agent);
}
function Agent(id, name) {
var self = this;
self.id = id;
self.name = name;
}
function ZipcodeAgentsViewModel() {
var self = this;
self.caseAssignmentZipCodes = ko.observableArray([]);
self.agents = ko.observableArray([]);
jdata = $.parseJSON($('#Agents').val());
var mappedAgents = $.map(jdata, function (a) { return new Agent(a.Id, a.Name) });
self.agents(mappedAgents);
var dictAgents = {};
$.each(mappedAgents, function (index, element) {
dictAgents[element.id] = element;
});
var jdata = $.parseJSON($('#CaseAssignmentZipCodes').val());
var mappedZipcodeAgents = $.map(jdata, function (za) { return new CaseAssignmentZipCode(za.ZipCode, dictAgents[za.UserId], false) });
self.caseAssignmentZipCodes(mappedZipcodeAgents);
}
var vm = new ZipcodeAgentsViewModel()
ko.applyBindings(vm);
My bindings look like this:
<table>
<thead><tr><th>Zipcode Agents</th></tr></thead>
<tbody data-bind="foreach: caseAssignmentZipCodes">
<tr>
<td><input data-bind="value: zipcode"></td>
<td><select data-bind="options: $root.agents, value: agent, optionsText: 'name'"></select></td>
<td>Remove</td>
</tr>
</tbody>
</table>
Everything binds fine the first time, with the table and select fields appearing properly. However, nothing happens when I change the selected value on any of the select elements. I have bound other elements to them and these don't update, and I've tried using .subscribe() to listen for the update event, but this doesn't fire either.
I expect there's something wrong with the way I'm setting up/binding these relationships, but I can't figure it out to save my life.
Thanks!
I think you need to add
self.agents = ko.observableArray([]);
at the top of ZipcodeUsersViewModel