Parse child nodes in JSON with knockout.js - javascript

let's say that my controller produce such json:
{
"$id": "1",
"$values": [{
"$id": "2",
"kodg": -1643387437,
"name": null,
"data": "2014-02-07T00:00:00",
"pax": 2,
"ch": 0
}, {...}]
}
I need to reach somehow values in child nodes (starting from $id: 2) to be able to bind its to UI, but I do not have any idea how to do it. Please advise.
P.S. foreach is not working here:
<script>
function BookViewModel() {
var baseUri = '/api/grafik/205693'
var self = this;
self.kodg = ko.observable("");
self.name = ko.observable("");
self.data = ko.observable("");
self.pax = ko.observable("");
self.child = ko.observable("");
var book = {
kodg: self.kodg,
name: self.name,
data: self.data,
pax: self.pax,
child: self.child
};
self.book = ko.observable();
self.books = ko.observableArray();
$.getJSON(baseUri, self.books);
}
$(document).ready(function () {
ko.applyBindings(new BookViewModel());
});
</script>
And this how I'm binding.
<table>
<thead>
<tr>
<td>kodg</td>
<td>name</td>
<td>data</td>
<td>pax</td>
<td>child</td>
</tr>
</thead>
<tbody data-bind="foreach: books">
<tr>
<td>
<input type="text" data-bind="value: $data.kodg" />
</td>
<td>
<input type="text" data-bind="value: $data.name" />
</td>
<td>
<input type="text" data-bind="value: $data.data" />
</td>
<td>
<input type="text" data-bind="value: $data.pax" />
</td>
<td>
<input type="text" data-bind="value: $data.child" />
</td>
</tr>
</tbody>
</table>

Several things.
Your usage of self is redundant. self is used to create a closure over the supposed context in a function that is passed, at a later time, as a callback of sorts - to preserve that context:
var self = this;
$('#target').on('click', function() {
self.someMethodOfTheAboveThis();
});
Additionally, getJSON is asynchronous method and it doesn't do what, it seems, you think it does.
Your usage should be:
call getJSON, while passing it a callback that will be invoked when the response is received from the server
populate your books observable array
Something along these lines:
$.getJSON(baseUri, function(data) {
self.books(data.$values);
});
The way you do it now is incorrect for 2 reasons:
you don't pass a callback into getJSON
even if did work and getJSON would, somehow, be able to just dump data onto your books - it would override the observable array without KO or bound DOM knowing about it.
EDIT: to clarify the last point - books is, conincidentally, a function, but the data that it will get populated with is not the one that should be populated onto the books.

Related

Remove items from Knockout observable array

I have the below structure for knockout model. It contains an observable array which in turn contains an object.
function ViewModel() {
var self = this;
self.newItem = ko.observable({
manufacturer: ko.observable(),
itemnumber: ko.observable(),
itemDescription: ko.observable()
});
self.AllItems = ko.observableArray();
self.addItem = function() {
self.newItem().manufacturer("test");
self.newItem().itemDescription("data");
self.AllItems.push(self.newItem);
};
self.removeItem = function(data) {
self.AllItems.remove(data);
};
}
First issue:Through this script I am entering a new itemnumber in the textbox and then clicking on add item to have the new item with the itemnumber from the textbox added to the observable array but when I change the item number and hit add it changes all the itemnumber inside the array. How can i have unique data inside the array.
Second issue: I need to remove the specific items from the array but it's not deleting it. Can someone please tell me how I can delete items from the observable array based on say the itemnumber property.
<input type="text" data-bind="value: newItem().itemnumber"/>
<div>
Items: <button data-bind="click: addItem">Add Item</button>
</div>
<div>
<table>
<tbody data-bind="template: { name: 'itemTemplate', foreach: AllItems }"></tbody>
</table>
</div>
<script type="text/html" id="itemTemplate">
<tr>
<td>
<input data-bind="value: itemnumber" />
Remove Item
</td>
</tr>
</script>
I have created this fiddle for quick view of the issue. Just started learning knockout so any help is appreciated.
http://jsfiddle.net/N3JaW/138/
Try the following for adding new item, which will solve your first issue:-
HTML code
<input type="text" id="textBox" data-bind="value : textBoxVal"/>
<div>
Items: <button data-bind="click: addItem">Add Item</button>
</div>
<div>
<table>
<tbody data-bind="template: { name: 'itemTemplate', foreach: AllItems }"></tbody>
</table>
</div>
<script type="text/html" id="itemTemplate">
<tr>
<td>
<input data-bind="value: itemnumber" />
Remove Item
</td>
</tr>
</script>
JS code:-
function ViewModel() {
var self = this;
self.newItem = ko.observable({
manufacturer: "",
itemnumber: "",
itemDescription: ""
});
self.textBoxVal = ko.observable();
self.AllItems = ko.observableArray();
self.addItem = function() {
self.newItem().manufacturer= "test";
self.newItem().itemDescription= "data";
self.newItem().itemnumber = self.textBoxVal();
self.AllItems.push(self.newItem);
};
self.removeItem = function(data) {
self.AllItems.remove(data);
};
}
$(document).ready(function() {ko.applyBindings(new ViewModel()); });
Your first issue was because, each time you are trying to add a new item, you were changing the value of itemNumber, which is an observable.
Observable value will be changed every where it is binded, when it's value is changed.
Instead you need to create new object and do push into the observableArray.
Refer doc to know more about observableArray.
For your second problem change removeItem as given below:-
self.removeItem = function(data) {
var dtIndex = self.AllItems.indexOf(data); //Get the index of the object you want to remove.
self.AllItems.splice(dtIndex, 1); //Then do splice
};
You can refer the above doc, to know how to use splice.
EDIT based on the suggestion in the comment :-
For working code of edited answer click here.
Hope this will solve your problem.

Decrement a value after clicking a button in the same row - knockout

I have a table which I fill with some numbers. There is a button in each row. After clicking this button I would like to decrement a counter in this row. How to to this with knockout?
<div class="panel panel-default">
<div class=panel-heading>Title</div>
<table class=table>
<thead>
<tr>
<th>Counter</th>
<th>Increment</th>
</tr>
</thead>
<tbody data-bind="foreach: records">
<tr>
<td data-bind="text: counter"></td>
<td> <input type="button" value="increment" data-bind=??? ></td>
</tr>
</tbody>
</table>
</div>
<script>
function AppViewModel() {
var self = this;
self.records = ko.observableArray([]);
$.getJSON("/data", function(data) {
self.records(data);
})
//function to decrement
}
ko.applyBindings(new AppViewModel());
</script>
I would do it this way:
Process data you get from server, turn counter property into observable and add function to decrement counter property
Restructure you code a little so viewmodel will be created by the time of ajax request
Move applyBindings call to ajax callback so it would fire when everything has been loaded
So the code would look like:
<tr>
<td data-bind="text: counter"></td>
<td> <input type="button" value="decrement" data-bind="click: decrement"></td>
</tr>
function AppViewModel() {
var self = this;
self.records = ko.observableArray([]);
}
var vm = new AppViewModel();
// load data from server
$.getJSON("/data", function(data) {
data.forEach( function(item) {
// make counter observable
item.counter = ko.observable(item.counter);
// add function to decrement
item.decrement = function() {
this.counter( this.counter()-1 );
}
})
// load array into viewmodel
vm.records(data);
// apply bindings when all obervables have been declared
ko.applyBindings(vm);
})
Check demo: Fiddle
I prefer to initialize and bind my viewmodel right away, but agree with the other poster that you need an observable.
Here is a solution that continues to create and bind your viewmodel right away, as in your original example, but instead of an array of the raw records you receive back it converts them into their own little model objects that have an observable for the counter and an increment function that can be data bound too. This decouples your data load from the life of the viewmodel, so if you wanted to add a button to load fresh data to overwrite it or anything like that, it's just another call to getData().
<!-- ... -->
<tbody data-bind="foreach: records">
<tr>
<td data-bind="text: counter"></td>
<td> <input type="button" value="increment" data-bind="click: increment" ></td>
</tr>
</tbody>
<!-- ... -->
<script>
function AppViewModel() {
var self = this;
self.records = ko.observableArray([]);
self.getData = function(){ /* ... */ };
self.getFakeData = function(){
var data = [{ counter: 1 }, { counter: 2}, { counter: 3 }];
var freshData = data.map(function(record){
return new AppRecord(record);
});
self.records(freshData);
};
}
function AppRecord(rawRecord) {
var self = this;
self.counter = ko.observable(rawRecord.counter);
self.increment = function(){
self.counter(self.counter() + 1);
};
}
var vm = new AppViewModel();
vm.getFakeData(); // replace with getData()
ko.applyBindings(vm);
</script>
Fiddle, with a getFakeData with sample data: https://jsfiddle.net/4hxyarLa/1/
If you are going to have a lot of rows and are concerned abut memory, you could put the increment function in a prototype method for the AppRecord and access the record via a parameter on the function, or you could add the function to the AppViewModel and bind to $parent.increment to call it and access the record via parameter passed to that function to increment it's counter property.

How to bind a ko.obersavableArray that is nested in an object

I have my knockout page hub, and I need a ko.obeservableArray nested in a ko.observable object, this is where I define them:
function IncomeDeclarationHub() {
//data comes from a ajax call.
self.myIncomeDeclarationViewModel = ko.observable(new IncomeDeclarationViewModel(data));
}
function IncomeDeclarationViewModel(data) {
var self = this;
self.retentionAmount = ko.observable();
self.taxableMonth = ko.observable();
self.incDecDetGroViewModels = ko.observableArray();
if (data != null) {
var arrayLenght = data.IncDecDetGroViewModels.length;
for (var i = 0; i < arrayLenght; i++) {
var myObject = new IncomeDecDetGroViewModel(data.IncDecDetGroViewModels[i]);
self.incDecDetGroViewModels.push(myObject);
}
}
}
And this is my HTML code:
<span class="label">
Retention Amount
</span>
<input data-bind="value: myIncomeDeclarationViewModel.retentionAmount" />
<table>
<tbody data-bind="foreach: myIncomeDeclarationViewModel.incDecDetGroViewModels">
...
</tbody>
</table>
Ok so the thing is that incDecDetGroViewModels never gets populated, I used to have that ko.obersableArray outside the object, and it worked fine, now that I inserted it in my object myIncomeDeclarationViewModel is not populating the html table. Do I need to call it in a different way at the data-bind
myIncomeDeclarationViewModel is an observable, so you have to unwrap it to access it's properties. Add parenthesis to unwrap it (access the observable's underlying value) like this:
<span class="label">
Retention Amount
</span>
<input data-bind="value: myIncomeDeclarationViewModel().retentionAmount" />
<table>
<tbody data-bind="foreach: myIncomeDeclarationViewModel().incDecDetGroViewModels">
...
</tbody>
</table>
Here's a working jsFiddle based on your example
JsFiddle
well previously you can access just becoz it is in scope but right now you done some nesting so you just need to some looping in your view part to get that .
Something like this may be :
<table data-bind="foreach:myIncomeDeclarationViewModel">
<tbody data-bind="foreach:$data.incDecDetGroViewModels">
...
</tbody>
</table>
You can also ContainerLess foreach if you looking for something different like :
<!-- ko foreach:myIncomeDeclarationViewModel -->
//your table code
<!--/ko-->
I hope this solves the riddle .

Values not updating when using functions to assign templates

I have this HTML and am trying to use it to generate a table with the option to add more rows:
<thead>
<tr>
<th>Item</th>
<th>Cost</th>
<th>Amount</th>
<th>
<button class="btn btn-default" data-bind="click: addItem">Add Item</button>
</th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="template: {name: $parent.dynTemplate, data: item }">
<td data-bind="template: {name: $parent.dynTemplate, data: cost() }"></td>
<td data-bind="template: {name: $parent.dynTemplate, data: amount() }">
<td></td>
</tr>
</tbody>
I'm using two different templates:
<script id="inpTmp" type="text/html">
<input data-bind="value: $data" />
</script>
<script id="dispTmp" type="text/html">
<p data-bind="text: $data"></p>
</script>
and am choosing which one to call based on the results of the dynTemplate function. The knockout that I have powering this is very simple:
function ItemAdd(name, icost, iamount) {
var self = this;
self.item = name;
self.cost = ko.observable(icost);
self.amount = ko.observable(iamount);
}
function TestModel() {
var self= this;
self.items= ko.observableArray([
new ItemAdd("a", 5, 10),
new ItemAdd("b", 6, 4)
]);
self.addItem= function() {
self.items.push(new ItemAdd("", 0, 0));
};
self.dynTemplate= function(init, s) {
if(init=== 0 || init=== '') {
return 'inpTmp';
}
return 'dispTmp';
};
}
ko.applyBindings(new TestModel());
The problem that I am running into is that when I enter values into newly created rows, the values in items do not change. They initialize properly, but when I run a function to log the values in items they stay as their defaults. If I use knockout if statements, then everything updates properly. However, using 6 sets of if statements didn't seem very effective so I wanted to see if I could pull it out into a function and then send back the proper template. I'm trying to have inputs there when the value is "" or 0, and then change them to <p> when something is entered.
I've tried changing how the data is passed into the template, and I've tried to assign context using with, but to no avail. Calling dynTemplate does not work unless prefixed by $root or $parent. If that is changing the context, is there a way to reset it?
Is this a problem of context, and if so, is there a way to assign context with the dynTemplate function? Or are the newly created elements from the template not properly binding? I've searched quite a bit, and have found templates within foreach loops, but have not seen functions being used to apply them. If there is a better way to do this, please let me know.
Thank you for the help
Your current sample doesn't work because ko dependency tacker doesn't see that your model field is changed. It happens because 'init' is unwrapped value (not an observable).
This fiddle shows how to make it work with single 'item' field.
http://jsfiddle.net/tabalinas/VXXqr/
In this changed version of dynTemplate we get the value of observable, and thus dependency tracker can see that value changed. Of course, we need to change the template.
self.dynTemplate= function(item, s) {
var val = item.item();
if(val=== 0 || val=== '') {
return 'inpTmp';
}
return 'dispTmp';
};
<script id="inpTmp" type="text/html">
<input data-bind="value: $data.item" />
</script>
<script id="dispTmp" type="text/html">
<p data-bind="text: $data.item"></p>
</script>
For your case, where you need universal template for all fields you can do the following: pass as data the name of the field. The template will pick up data from $parent. dynTemplate func is changed accordingly.
<tbody data-bind="foreach: items">
<tr>
<td data-bind="template: {name: $parent.dynTemplate, data: 'item' }">
</td>
<td data-bind="template: {name: $parent.dynTemplate, data: 'cost' }">
</td>
<td data-bind="template: {name: $parent.dynTemplate, data: 'amount' }">
</td>
</tr>
</tbody>
<script id="inpTmp" type="text/html">
<input data-bind="value: $parent[$data]" />
</script>
<script id="dispTmp" type="text/html">
<p data-bind="text: $parent[$data]"></p>
</script>
self.dynTemplate= function(field, context) {
var value = context.$parent[field]();
if(value=== 0 || value=== '') {
return 'inpTmp';
}
return 'dispTmp';
};
See fiddle http://jsfiddle.net/tabalinas/VXXqr/5/

Knockoutjs very slower with 100 records

I was writing a demo when I saw some result of my knockout page and I was shocked.
This are the results:
What I do is quite simple, when someone ask to load the data I do the following:
self.items = ko.observableArray([]);
self.colors = ko.observableArray([]);
self.productModels = ko.observableArray([]);
self.loadData = function() {
var buffer;
$.getJSON('/Product/InventoryData')
.success(function(allData) {
buffer = [];
buffer = $.map(allData, function(item) { return new SDF.Data.DTO.ProductDto(item); });
self.items(buffer);
})
.error(function() {
alert("error on load data");
});
$.getJSON('/Product/GetColors')
.success(function(allData) {
buffer = [];
ko.utils.arrayForEach(allData, function (item) {
buffer.push(item);
});
self.colors(buffer);
})
.error(function () {
alert("error on load colors");
});
$.getJSON('/Product/GetProductModels')
.success(function (allData) {
buffer = [];
ko.utils.arrayForEach(allData, function (item) {
buffer.push(item.Name);
});
self.productModels(buffer);
})
.error(function () {
alert("error on load product models");
});
};
all the server method results are cached and they are very quickly.
Colors and ProductModels are observable too because I want give to the user the ability to change the "color" or the "model" of a product for each printed item.
The amount of data that I load is just 100 items.
Following my html:
<tbody data-bind="foreach: items">
<tr>
<td>
<input type="text" data-bind="value: name" />
</td>
<td>
<select data-bind="options: $root.colors, optionsCaption: 'Choose...'">
</select>
</td>
<td>
<input type="number" data-bind="value: price" />
</td>
<td>
<select data-bind="options: $root.productModels, optionsCaption: 'Choose...'">
</select>
</td>
<td>
<label data-bind="text: qty">
</label>
</td>
<td>
Sell
</td>
<td>
</td>
</tr>
</tbody>
any suggestion other than "page it" are appreciated.
UPDATE 1
I found which is the problem but now I don't know how can I resolve it.
The problem is how I write the selects foreach Item. Probably there are repaint foreach item.
How can avoid that?
UPDATE 2
The best solution that I found is use the Knockoutjs If-binding in my markup: http://knockoutjs.com/documentation/if-binding.html
Have you got this online anywhere, or could you knock together a quick jsfiddle for this, as it really shouldn't be taking anywhere near that long!
Saying that, why are you looping through the results of your AJAX calls and putting values into an array?
Have you tried only making one of your AJAX calls and seeing what effect that has on the time? Is that most of the html that is on your page, or is there a lot more? I'd be interested to see if it is the data binding that is causing the style calculations and layout time to be so large, as it really shouldn't be.

Categories

Resources