How to bind Json data to Html in knockout - javascript

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/

Related

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>

knockout js foreach loop context

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>

Angular don't sorting data

I try to sort data with angular.
Here my code:
<table id="data-table" width="100%" class="table table-striped table-hover">
<thead>
<tr>
<th class="sortable" field="name">Name</th>
<th class="sortable" field="phone">Phone number</th>
</tr>
</thead>
<tbody>
<tr class="tr-data" ng-repeat="el in list | orderBy:sortCol:sortType" >
<td>{{el.name}}</td>
<td>{{el.phone}}</td>
</tr>
</tbody>
</table>
<div>{{sortType}}</div>
<div>{{sortCol}}</div>
javascript:
var someData = [
{'name': 'Vasja', 'phone': '00375 29 654 1185'},
{'name': 'Sasha', 'phone': '00375 29 654 1176'}];
app.controller('myCtrl', function($scope)
{
$scope.sortType = false;
$scope.sortCol = 'phone';
$scope.list = someData;
$scope.applySort = function()
{
$('th.sortable').each(function(idx, el)
{
var uarr = $('<span class="sort-span" ng-click="xSort(this);">↑</span>');
var darr = $('<span class="sort-span">↓</span>');
uarr.appendTo(el).on('click', function() { $scope.sortCol = $(el).attr('field'); $scope.sortType = false;});
darr.appendTo(el).on('click', function() { $scope.sortCol = $(el).attr('field'); $scope.sortType = true;});
});
};
$scope.applySort();
});
By clicking on arrow - nothing changed. Even data sortCol and SortType don't changed.
But, when i change data in list - sorting is applying;
Angular couldn't fire it's events by jquery events. Either you could add $scope.$apply() to the end of your jquery events, it would work but this isn't a good solution.
The better way is to render your arrows in angular and bind events with angular.
Don't use jQuery, it should be done with Angular directives like ngClick:
<thead>
<tr>
<th class="sortable" field="name">
Name
<span class="sort-span" ng-click="sort('name', false)">↑</span>
<span class="sort-span" ng-click="sort('name', true)">↓</span>
</th>
<th class="sortable" field="phone">
Phone number
<span class="sort-span" ng-click="sort('phone', false)">↑</span>
<span class="sort-span" ng-click="sort('phone', true)">↓</span>
</th>
</tr>
</thead>
and controller:
app.controller('myCtrl', function ($scope) {
$scope.sortType = false;
$scope.sortCol = 'phone';
$scope.list = someData;
$scope.sort = function (field, type) {
$scope.sortCol = field;
$scope.sortType = type;
};
});

When clicking a row, KnockoutJS returns an empty JSON object

My problem is that when I click a row that is data-bound in KnockoutJS, the data that is sent to my selectItem function is empty and only displays {"data":{}} in the Firefox Web Console.
What I do not understand is how to get the values of my table cells so that I can refer to the sender portion of my JSON object? At the moment, it is empty.
KnockoutJS:
function ServiceViewModel() {
var self = this;
self.rows = ko.observableArray();
$.ajax({
method: "GET",
url: "URL",
success: function(data) {
var observableData = ko.mapping.fromJSON(data);
var array = observableData();
self.rows(array);
}
});
self.computedRows = ko.computed(function() {
if(!self.query()) {
return self.rows();
} else {
return ko.utils.arrayFilter(self.rows(), function(row) {
return row.sender() == self.query();
});
}
});
self.selectedItem = ko.observable();
self.selectItem = function(data) {
self.selectedItem(data);
console.log(JSON.stringify(self.selectedItem()));
};
};
$(document).ready(function() {
var svc = new ServiceViewModel();
ko.applyBindings(svc);
});
JSON:
[{"statusmsg":"OK","data":{"status":"running"},"sender":"hostname","statuscode":0}]
HTML:
<div class="table-responsive">
<table class="table table-condensed table-striped table-bordered table-hover">
<thead>
<tr>
<th>Host</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody data-bind="foreach: computedRows().sort(function(l, r) { return l.sender() > r.sender() ? 1 : -1})">
<tr data-bind="click: $root.selectItem">
<td data-bind="text: sender"></td>
<td><span data-bind="text: data.status"></span></td>
<td>
<div class="btn-group">
<button data-bind="click: $root.selectItem">Start</button>
<button>Stop</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
You need to use ko.toJSON to convert your knockout model to json instead of JSON.stringify as knockout uses functions for the observables.
console.log(ko.toJSON(self.selectedItem()));

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

Categories

Resources