Incorrect values from a knockout observable array - javascript

I am working on a web application using Asp.Net Mvc in which I used knockout Js to retrieve and send data to Html View by performing some operation on the data.
For example I have the following data in my array named datainput
Grade Type Volume Price
A Buy 1000 10
A Sell 1200 10
B Sell 100 100
I am computing an array which such that when displaying on a html page it just takes one distinct value of Grade and groups the data under that grade like
A
Buy 1000 10
Sell 1200 10
B
Sell 100 100
Used the above function as follows
self.uniqueSelect = ko.dependentObservable(function () {
var Transactiontypes = ko.utils.arrayMap(self.datainput(), function (item) { return item.Grade })
return ko.utils.arrayGetDistinctValues(Transactiontypes);
});
I dont think there is an issue with above script, not sure though but I went ahead and used the data obtained from the above script and tried to populate it to html as follows
<!--ko foreach: uniqueSelect-->
<tr>
<td class="clientproductHeader" data-bind="text: $data"></td>
<td class="clientproductHeader" colspan="10"></td>
</tr>
<tbody data-bind="foreach: ko.observableArray($root.datainput()).extendsdistinct('Grade').index.Grade()[$data]">
<tr>
<script type="text/html" id="read-template">
<td data-bind="text: Type"></td>
<td data-bind="text: Volume"></td>
<td data-bind="text: (typeof Price() === 'number') ? Price().toFixed(2) : '' "></td>
</script>
</tr>
</tbody>
<!--/ko-->
</table>
The above script for some reasons display duplicate data like if a Grade is repeated two times as in the example data it displays the same data of that grade two times and if it repeated three times then displays it thrice and so on.
I thought the error was with <!--ko foreach: uniqueSelect--> because it just loops the data depending on the number of similar grades.
results on html page are like
A
Buy 1000 10
Sell 1200 10
B
Sell 100 100
A
Buy 1000 10
Sell 1200 10
In the above data Grade A is repeated twice so all the data with grade A is repeated twice where as grade B has just one entry and so, it happened only once
screen of data I received
No clue how to deal with this

I went through and wrote a version that does what I think you are trying to accomplish. It uses the method PÄ“teris described. See it in action.
You want to keep the view code as simple as possible, and also be careful about reaching back into $root. If you are positive you would like to do it with a filter, make one that does a similar grouping as below, and then iterate over each of its entries/children. Also, try to avoid declaring observables in your views.
Finally, you could combine groupByGrade() and getGrades() into a single function that returns an object with a property for each of the results. It would save an iteration cycle.
The view:
<tr>
<td class="clientproductHeader" data-bind="text: 'Grade ' + $data + ' (' + $root.log()[$data].length + ' trades)'"></td>
<td class="clientproductHeader" colspan="10"></td>
</tr>
<tbody data-bind="foreach: $root.log()[$data]">
<tr>
<td data-bind="text: type"></td>
<td data-bind="text: volume"></td>
<td data-bind="text: (typeof price === 'number') ? price.toFixed(2) : '' "></td>
<td class="actioncells">
<a class="btn btn-success" title="Edit" data-bind="click: $root.edit">Edit</a>
</td>
<td class="actioncells">
<a class="btn btn-danger" title="Delete " data-bind="click: $root.remove">Delete</a>
</td>
</tr>
</tbody>
<!--/ko-->
And the JavaScript:
function testViewModel() {
// Underscore and Lo-dash both have a function to group
// an object by key. That's what you want. Here is a
// really simple version.
function groupByGrade(data) {
return data.reduce(function(last, next) {
if (last[next.grade]) {
last[next.grade].push(next);
} else {
last[next.grade] = [next];
}
return last;
}, {});
}
// Get a unique list of the grades in the data to iterate over
function getGrades(data) {
return data.reduce(function(last, next) {
return !!~last.indexOf(next.grade) ? last : last + next.grade;
}, '').split('');
}
var rawData = [
{
grade: 'A',
type: 'Buy',
volume: 1000,
price: 10
}, {
grade: 'A',
type: 'Sell',
volume: 1200,
price: 10
}, {
grade: 'B',
type: 'Sell',
volume: 100,
price: 100
}
];
console.log(getGrades(rawData));
this.log = ko.observable(groupByGrade(rawData));
this.grades = ko.observableArray(getGrades(rawData));
}
ko.applyBindings(new testViewModel());

Related

How to update a particular row of a vueJs array list?

Is there a proper way to refresh one particular row from a vueJS array list without reloading all the data?
In this case, it is a phone list.
<template>
<table class="table">
<tr v-for="phone in phones">
<td #click="edit(phone.id)">
{{phone.number}}
</td>
<td class="pointerCursor" #click="edit(phone.id)">
{{phone.comment | truncate(90)}}
</td>
</tr>
</table>
</template>
<script>
export default {
data () {
return {
phones = [
{id:1, number: '001 656 88 44', comment:''},
{id:2, number: '610-910-3575 x2750', comment:'Quod odit quia'},
{id:3, number: '536.224.2639 x714', comment:'primary phone'},
{id:5, number: '0034 556 65 77', comment:'home phone phone'},
]
}
},
methods: {
edit(id) {
// update using an api that returns the updated data.
var updatedPhone = update(id)
// how to update with reloading all the phone list?
// ...
}
}
}
</script>
PS: this is a code just to demonstrate the problem.
const currentIndex = this.phones.findIndex(p => p.id === id);
this.phones.splice(currentIndex, 1, updatedPhone)
If your target environment doesn't support findIndex on arrays, you can use some other means of finding the current index. One would be pass the phone instead of it's id.
edit(phone) {
const currentIndex = this.phones.indexOf(phone);
// update using an api that returns the updated data.
var updatedPhone = update(phone.id)
this.phones.splice(currentIndex, 1, updatedPhone)
}
In both cases, Vue will detect the change and re-render for you.
You could use the built-in index generation:
<tr v-for="(phone, index) in phones">
<td #click="edit(index)">
{{phone.number}}
</td>
<td class="pointerCursor" #click="edit(index)">
{{phone.comment | truncate(90)}}
</td>
</tr>
And then in your edit function:
methods: {
edit(index) {
phone = this.phones[index];
// update using an api that returns the updated data.
var updatedPhone = update(phone.id)
// how to update with reloading all the phone list?
this.phones.splice(index, 1, updatedPhone)
}
}

How to sum a databound column using knockout or jquery?

I have the folowing table in my view:
<table border="0" id="tbl">
<tr class="te">
<th>DATE</th>
<th>METHOD</th>
<th>DEPOSIT</th>
<th>WITHDRAWAL</th>
<th>MEMO</th>
</tr>
<!-- ko foreach: account -->
<tr>
<td><span data-bind="text: transactionDate"></span></td>
<td><span data-bind="text: tranType"></span> </td>
<td><span data-bind="text: deposit"></span></td>
<td><span data-bind="text: withdrawal"></span> </td>
<td></td>
</tr>
<!-- /ko -->
<tr class="last">
<td> </td>
<td> </td>
<td>***TOTAL DEPOSIT --- SHOULD GO HERE***</td>
<td>***TOTAL WITHDRAWAL --- SHOULD GO HERE***</td>
<td>""</td>
</tr>
</table>
See below my Account Knockout object:
var Account = function (data) {
this.transactionDate = ko.observable(data.transactionDate);
this.payorPayee = ko.observable(data.payorPayee);
this.amount = ko.observable(data.amount);
this.isDebit = ko.observable(data.isDebit);
this.tranType = ko.observable(data.tranType);
this.deposit = ko.pureComputed(function () {
//some code
});
this.withdrawal = ko.pureComputed(function () {
//some code
});
}
As you can see, I loop through the account object and display the information on a table.
the JSON with the Account info looks like this:
[{
"transactionDate": "1/1/2016",
"payorPayee": "AAAAA",
"amount": "111",
"isDebit": false,
"tranType": "qqqq"
}, {
"transactionDate": "1/1/2016",
"payorPayee": "BBBBB",
"amount": "222",
"isDebit": false,
"tranType": "wwww"
}, {
"transactionDate": "1/1/2016",
"payorPayee": "CCCCC",
"amount": "333",
"isDebit": false,
"tranType": "eeee"
}]
What I need to do is loop through the rows, sum the deposit fields and display the total on the Total Deposit field (last 'tr'), then repeat the process for the Withdrawal fields.
I doubt it if it makes more sense to do it using Jquery on page load, or using Knockout.
Any idea would be appreciated.
Just like what you did in your deposit and withdrawal computed, you should also make a computed observable in your viewmodel for the total deposit and total withdrawal. You just need to loop through your account array and extract the sum of the deposits and withdrawals.
for example:
this.totalDeposit = ko.computed(function(){
var sum = 0;
//i don't know if account is observableArray or just plain array
this.account.forEach(function(account){
sum += Number(account.deposit());
});
return sum;
});
And so on and so forth, just create another computed for totalWithdrawal.

Bind dynamic columns to a table using angularJS

I am getting some data from an external service and I am trying to show it on a table. The problem is that the data I get from service will be with dynamic columns, some times there will be 5 column another time 8. I don't know how I could handle it in ng-repeat. and using things like ng-grid won't be a good solution I think as there will be only 10 rows to display. for this If I use any external solution that will be a overhead. Is there any angular method to achieve this? if not what is the best option for this small data.
Note: Column names will also be dynamic
My code
<div ng-app='myApp' ng-controller="MainCtrl">
<div ng-repeat="prdElement in packageElement track by $index" class="package-grid">
<table class="hovertable">
<thead>
<tr>
<th>Line #</th>
<th>Quantity in Plt</th>
<th>Allready Packed</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="data in prdElement.Data" ng-init="data.newquantity = 0">
<td>{{data.itemId}}</td>
<td>
{{data.quantity}}
</td>
<td>{{data.packed}}</td>
</tr>
</tbody>
</table>
</div>
angular.module('myApp', []).controller('MainCtrl', function ($scope) {
var counter = 0;
$scope.packageElement = [{
name: counter,
show: true,
Data: [{
name: 'item 1',
itemId: '284307',
quantity: '100',
packed: 0
}, {
name: 'item 2',
itemId: '284308',
quantity: '200',
packed: 0
}]
}];
});
Will there be the same number of columns for all data items? If so, I think you can do this.
1. Define a function on your scope that gives you the object keys:
$scope.keys = function(obj) {
var key;
var keys = [];
for (key in obj) {
if (key === "$$hashKey") break; //angular adds new keys to the object
if (obj.hasOwnProperty(key)) keys.push(key);
}
return keys;
}
2. use a repeater on the table header (if the objects can have different properties, you need to find the object with the highest number of properties/columns)
<th ng-repeat="key in keys( prdElement.Data[0] )">{{key}}</th>
3. use a repeater on the table cell
<td ng-repeat="key in keys( prdElement.Data[0] )">{{ data[key] }}</td>

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/

Knockout.js, how to change a value of mapped two dimensional array?

I have a controller that returns two dimensional array of objects as json. I make a call from my javascript:
$.getJSON("/Game/GetBoard", function (json) {
data = json;
board = ko.mapping.fromJS({ board: data });
ko.applyBindings(board, $('.board')[0]);
});
I also have the following html:
<table>
<tbody data-bind="foreach: board">
<tr data-bind="foreach: $data">
<td data-bind="attr: { class: Color }"></td>
</tr>
</tbody>
</table>
It generates a nice 2 dimensional html table with nicely colored cells (based on a class that comes from a Color property). How can I now change this color to something else?
I tried: board[1][1]({Color: 'red'});, but I get an error saying that board[1] does not exist...
And another question, how can I add more than one class to the binding? I tried:
...
<td data-bind="attr: { class: Color + ' some-other-class' }"></td>
...
But then I get:
class="function b() { if (0 < arguments.length) { if (!b.equalityComparer || !b.equalityComparer(d, arguments[0])) { b.H(), d = arguments[0], b.G(); } return this; } r.T.Ha(b); return d; } some-other-class"
Is it a bug or am I doing something wrong?
The mapping plugin will turn your array into an observableArray and your properties into observables.
For your first case, you would need to unwrap the observable array by doing: board.board()[1][1]
For the other question, Color is an observable. If you are using it in an expression and want to get its value, then you need to do Color(). So, it would look like:
<td data-bind="attr: { class: Color() + ' some-other-class' }"></td>

Categories

Resources