knockout js foreach loop context - javascript

A have template:
<table data-bind="foreach: {data: messages, as: 'message'}">
<tr> <td data-bind="text: $root.go"></td> </tr>
</table>
My js:
function CreateViewModel() {
var self = this;
this.go = ko.computed(function() {
return 'not really important what here for now';
});
}
ko.applyBindings( new MessagesVM );
The context in 'go' while runtime is 'window', I want it to be the current item.
Is it possible?
If I change tmpl line to "<td data-bind="text: go"></td>" I will have an error ['go' is not defined].

The context in 'go' while runtime is 'window', I want it to be the current item. Is it possible?
I don't think you can with a computed, no. If you make it a simple function you can: data-bind="text: $root.go.call(message)" (generically it would be .call($data), but since you're naming it in your foreach):
function MessagesVM() {
var self = this;
this.messages = ko.observableArray([
"Message 1",
"Message 2"
]);
this.go = function() {
return 'this is: "' + this + '"';
};
}
ko.applyBindings(new MessagesVM);
<table data-bind="foreach: {data: messages, as: 'message'}">
<tr>
<td data-bind="text: $root.go.call(message)"></td>
</tr>
</table>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script>
Alternately, if you want some of the functionality of a computed but acting on the individual item, I'd probably use a binding handler:
ko.bindingHandlers.specialText = {
update: function(element, valueAccessor) {
var value = ko.unwrap(valueAccessor());
element.innerHTML = "'value' is " + value;
}
};
function MessagesVM() {
var self = this;
this.messages = ko.observableArray([
"Message 1",
"Message 2"
]);
}
ko.applyBindings(new MessagesVM);
<table data-bind="foreach: {data: messages, as: 'message'}">
<tr>
<td data-bind="specialText: message"></td>
</tr>
</table>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script>

Turn it into a function and pass the context in. As a computed, it has a cached value and so cannot be context-dependent.
function CreateViewModel() {
var self = this;
this.messages = [
'one',
'two'
];
this.go = function(data) {
var context = data;
return 'Context:' + context;
};
}
ko.applyBindings(new CreateViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<table data-bind="foreach: {data: messages, as: 'message'}">
<tr>
<td data-bind="text: $root.go(message)"></td>
</tr>
</table>

Related

Trying to set an ID for an element using knockout.js and getting undefined

I am new to knock out and fairly new to JavaScript so apologies if I have done something silly here...
I am using knockout.js (3.4.2) to maintain a list of dependents, I have the add and delete working OK but I want to set an ID on an element to allow me to make it read only.
I have based my code to do this on the knockout example, their working example can be seen here: Demo
When I run my code the ID is set to undefined:
<input data-bind="uniqueId: dependantName, value: dependantName" id="undefined" type="text">
My code on JS fiddle
https://jsfiddle.net/zacmarshall/khb13ha4/
HTML:
<!DOCTYPE html>
<head>
<script src="javascripts/vendor/knockout-3.4.2.js" type="text/javascript"></script>
<script src="javascripts/vendor/jquery-1.11.1.min.js"></script>
<script src="javascripts/vendor/jquery.validate.js" type="text/javascript"></script>
<head>
<body>
<input data-bind="value: newDependantName" />
<input data-bind="value: newDobDD" />
<button data-bind="click: addDependant">Add</button>
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
<table>
<thead>
<tr>
<th>Name</th>
<th>Date of Birth</th>
<th></th>
</tr>
</thead>
<tbody id="qualifications-summary" data-bind="foreach: dependants">
<!--div data-bind="uniqueId: dependantName"-->
<div>
<tr>
<td><label data-bind="uniqueFor: dependantName">Before</label>
<input type="text" data-bind="uniqueId: dependantName, value: dependantName"/>
</td>
<td><input data-bind="value: dobDD" /></td>
<td><input data-bind="value: itemIndex"/></td>
<td>Remove</td>
</tr>
</div>
</tbody>
</table>
</body>
javascript:
function Dependant(data) {
this.itemIndex = ko.observable(0);
this.dependantName = ko.observable(data.dependantName);
this.dobDD = ko.observable(data.dobDD);
this.readOnly = ko.observable(data.readOnly);
this.showEditButton = ko.observable(data.showEditButton);
}
function DependantListViewModel() {
// Data
var self = this;
self.dependants = ko.observableArray([]);
self.newDependantName = ko.observable();
self.newDobDD = ko.observable();
self.readOnly = ko.observable();
self.showEditButton = ko.observable();
self.itemIndex = ko.observable();
var i = 0;
// Operations
self.addDependant = function() {
var data1 = { itemIndex : (i),
dependantName : this.newDependantName(),
dobDD : this.newDobDD(),
readOnly : "readonly",
showEditButton : (!0)
};
self.dependants.push(data1);
self.newDependantName("");
self.newDobDD("");
i++
};
self.removeDependant = function(dependant) { self.dependants.remove(dependant) };
self.editDependant = function(i) {
console.log("editedDependant");
self.readOnly(undefined);
self.showEditButton(!1)
};
self.saveDependantItem = function(self) {
self.readOnly("readonly"), self.showEditButton(!0)
};
}
ko.bindingHandlers.uniqueId = {
init: function(element, valueAccessor) {
var value = valueAccessor();
value.id = value.id || ko.bindingHandlers.uniqueId.prefix + (++ko.bindingHandlers.uniqueId.counter);
console.log("in handler");
element.id = value.id;
},
counter: 0,
prefix: "unique"
};
ko.bindingHandlers.uniqueFor = {
init: function(element, valueAccessor) {
var value = valueAccessor();
value.id = value.id || ko.bindingHandlers.uniqueId.prefix + (++ko.bindingHandlers.uniqueId.counter);
element.setAttribute("for", value.id);
}
};
ko.applyBindings(new DependantListViewModel());
The bug refers to this in your init:
var value = valueAccessor(); // this is undefined
value.id = value.id // therefore this falls over
This is the issue:
self.dependants.push(data1);
It should be:
self.dependants.push(new Dependant(data1));
And then update your Dependant model to pass the index:
this.itemIndex = ko.observable(data.itemIndex);
Here is a working Fiddle

Is it possible to pass a Knockout.JS variable into a onclick function WITHOUT binding?

Is it possible to pass a Knockout.JS variable into an onclick function WITHOUT binding the function to the view model?
Below, i'm trying to pass the username parameter for the particular row below:
<tbody data-bind="foreach: viewModel">
<tr>
<td data-bind="text: UserId"></td>
<td><button onclick="alertRowName($element.UserName)"></button></td>
</tr>
</tbody>
<script>
function alertRowName(string){
alert(string);
}
//in my example the model is from a c# viewmodel..
viewModel = ko.mapping.fromJS(model);
var jsModel = ko.toJS(viewModel);
ko.applyBindings(viewModel);
</script>
You can do it something like this.
EDIT
hmmmm.. just noticed the Without binding. Can you clarify why without binding?
var app = Window.app || {};
app.model = [{
UserId: 1,
UserName: "User Name Here",
}];
app.ViewModel = function ViewModel(model){
var self = this;
self.data = ko.mapping.fromJS(model);
};
app.ViewModel.prototype.alertRowName = function alertRowName(user) {
alert(user.UserName());
};
ko.applyBindings(new app.ViewModel(app.model));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<table>
<tbody data-bind="foreach: data">
<tr>
<td data-bind="text: UserId"></td>
<td><button data-bind="click: $root.alertRowName, text: UserName"></button> </td>
</tr>
</tbody>
</table>
BEWARE...HERE BE DRAGONS
You can do the following, but I would strongly recommend not doing this.
This is attaching a function to the Window object making it a Global function. each event handler generally has an event object available that you can use.
var app = Window.app || {};
app.model = [{
UserId: 1,
UserName: "User Name Here",
}];
app.ViewModel = function ViewModel(model){
var self = this;
self.data = ko.mapping.fromJS(model);
};
app.ViewModel.prototype.alertRowName = function alertRowName(user) {
alert(user.UserName());
};
ko.applyBindings(new app.ViewModel(app.model));
Window.myButtonClick = function(){
var item = ko.dataFor(event.currentTarget);
alert(item.UserName());
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<table>
<tbody data-bind="foreach: data">
<tr>
<td data-bind="text: UserId"></td>
<td><button onclick="Window.myButtonClick()" data-bind="text: UserName"></button> </td>
</tr>
</tbody>
</table>

How to bind Json data to Html in knockout

I am receiving below data from my json object,
var dataFromServer='[
{
"Id":1,
"Name":"Province A",
"AreaDTO":[],
"SubStationDTO":[]
},
{
"Id":2,
"Name":"Province B",
"AreaDTO":[
{
"Id":1,
"Name":"ProvinceB Area A",
"ProvinceId":2,
"SubStationDTO":null
},
{
"Id":2,
"Name":"Province B Area B",
"ProvinceId":2,
"SubStationDTO":null
}],
"SubStationDTO":[
{
"Id":1,
"Name":" Province B Area A SubStation A",
"AreaId":1,
"MetersDTO":null
}
]
}
]';
Please see : http://jsfiddle.net/Jayaruvan/s8403t71/1/
<table class="table table-bordered table-condensed">
<thead>
<tr>
<th>Province Name</th>
<th class="numeric">Area Count</th>
<th class="numeric">SubStation Count</th>
<th colspan="2"></th>
</tr>
</thead>
<tbody data-bind="foreach: ProvincesArray">
<tr>
<td><p data-bind="text: Name"></p></td>
<td ><p data-bind="text: Areas().length" ></p></td>
<td> <p data-bind="text: SubStations().length" ></p></td>
<td class=""><a class="btn-link" data-bind="click: $parent.EditProvince">Edit</a></td>
<td class=""><a class="btn-link" data-bind="click: $parent.RemoveProvince">Delete</a></td>
</tr>
</tbody>
</table>
I want it to bind above table and it's not working. Can anyone help?
I am referring this : http://jsfiddle.net/rVPBz/2/ as an example,
(Thank you ace for the proper formatting)
I have make a new Jsfiddle for your. I have fix some basic errors.
The table is implemented.
Your view model implementation was bad, I reorganised it.
var ProvinceViewModel = function () {
var self = this;
this.Province = function(data){
data = data || {};
this.Id = data.Id;
this.Name=data.Name;
this.Areas = ko.observableArray([]);
this.SubStations = ko.observableArray([]);
};
this.ProvincesArray = ko.observableArray([new this.Province()]);
this.Area = function(data){
data = data || {};
this.Id = ko.observable(data.Id);
this.Name = ko.observable(data.Name);
this.SubStations = ko.observableArray([]);
};
this.SubStation = function(data){
data = data || {};
this.Id = ko.observable(data.Id);
this.Name = ko.observable(data.Name);
this.Meters = ko.observableArray([]);
};
this.ProvincesArray = ko.utils.arrayMap(dataFromServer, function(item) {
var Province = new self.Province(item);
var Area= new self.Area(item);
var SubStations= new self.SubStation();
return Province;
});
};
ko.applyBindings(new ProvinceViewModel());
You can see the new structure which work, you can add any paramet
http://jsfiddle.net/YvanBarbaria/s8403t71/15/

Knockout options binding: how to remove items dynamically from popup if they are selected

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

Knockout.js use foreach to display object properties

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.

Categories

Resources