knockout.js foreach $data AND $index()? - javascript

HTML
You can see in the html I am calling editUser with two parameters. $data being the first parameter and $index() being the second. It should send the current user object in the first parameter and the index of the foreach loop as the second.
<tbody data-bind="foreach: users">
<tr>
<td>
<div data-bind="click: $parent.editUser.bind($data, $index())" class="clickable icon black-icon big-icon icon-edit"></div>
</td>
<td data-bind="text: email"></td>
<td data-bind="text: username"></td>
<td data-bind="text: level"></td>
</tr>
</tbody>
Code
appViewModel.editUser = function( user, index ){
console.log(user);
console.log(index);
};
Output
0
Object{}
It completely reverses the parameters somehow. I am at a loss to explain this behavior.

The first argument of bind is the function context. So, if you want to pass 2 arguments you're gonna need this:
$parent.editUser.bind($data, $data, $index())
This way, the this in your editUser function would be the current item in your foreach iteration.
See MDN

Related

Get the sum of a calculated expressions depends on template variable in Angular?

I have a simple array of numbers :
data:Array<number> = [1,2,3,4,5];
And I also have an editable input which is a multiplication factor.
on the right side , I show the user the result :
The html is pretty simple :
<table>
<tbody>
<tr *ngFor="let a of data">
<td>{{a}}</td>
<td><input type='text' #i [ngModel]="a" /></td>
<td>{{i.value*a}}</td>
</tr>
<tr>
<td></td>
<td></td>
<td>-------<br/>sum ?</td>
</tr>
<tbody>
</table>
Please notice that the multiplication result is calculated via a template variable (#i ----> i.value*a)
Question:
But how can I calc the sum of that calculated column ?
I wonder if I can do it only via in the template.
Sure I can find solutions via TS.
But I wonder if I can do this only via template calculations
nb
I've used [ngModel] and not banana since I need to show (in the input) the left side value at initialization , and if I'd do banana , when I'll change the input value , it would also change the left side value - which I don't want.
StackBlitz
Fiddled my brain over this for half a day. Finally found a solution to the above. All value manipulations directly from html are often discouraged as these don't qualify as good code practices. Nevertheless, I implemented the solution as follows.
Add the following in your component.ts.
data : Array<number> = [1,2,3,4,5];
sum : Array<number> = [0];
Add the following code in your component.html
<table>
<tbody>
<ng-container *ngIf="{sample : sum} as variable">
<tr *ngFor="let a of data;let index of index">
<td>{{a}}</td>
<td><input type='text' #i [ngModel]="a"/></td>
<td>{{i.value*a}}</td>
<td style="display:none;">{{variable.sample[index] = variable.sample[index-1]+i.value*a}}</td>
</tr>
<tr>
<td></td>
<td></td>
<td>Sum={{variable.sample[5]}}</td>
</tr>
</ng-container>
</tbody>
</table>
Basically, I have used a Array<number> named sum which will hold the sum during each iteration of ngFor. I am calculatiing the same using expression inside the last td as {{variable.sample[index] = variable.sample[index-1]+i.value*a}}. For each iteration of the loop, the sample variable gets updated with the new sum. On any change of the input value the sample variable value also changes and this reflects as the Total.
Hope this helps. A nice and challenging question it is, I must say!.
---EDIT1---
The case was failing in case of non-consecutive numbers. Basically, let a of data;let index of index has two loop conditions. In the longhand notation, the condition evaluates to <tr ngFor let-index="$implicit" ngFor let-a="$implicit" [ngForOf]="data"> , which converts both index and a to implicit variables for the loop over data, thus meaning index === a. I have rectified the same as below. The following code works fine for all cases:
<table>
<tbody>
<ng-container *ngIf="{sample : sum} as variable">
<tr *ngFor="let a of data;let in=index">
<td>{{a}}</td>
<td><input type='text' #i [ngModel]="a"/></td>
<td>{{i.value*a}}</td>
<td style="display:none;">{{variable.sample[in+1] =variable.sample[in]+ i.value*a}}</td>
</tr>
<tr>
<td></td>
<td></td>
<td>SUM={{variable.sample[data.length]}}</td>
</tr>
</ng-container>
</tbody>
</table>
You need use [(ngModel)]. The problem when we use a ngModel with an array is that we can not iterate over the same array
/***** This NOT valid ****/
<tr *ngFor="let a of data;let i=index">
<td><input type='text' [(ngModel)]="a" /></td>
</tr>
See Angular two way data binding in string array doesn't works correctly
So we need use an auxiliar variable
//in ts
data:Array<number> = [1,2,3,4,5];
count:number[]=new Array(this.data.length); //<--this is the auxiliar variable
//for the sum, I use a getter
get sum()
{
let sum=0;
this.data.forEach((p,index)=>{
sum+=p*index
})
return sum;
}
In .html
<table>
<tbody>
<tr *ngFor="let item of count;let i=index">
<td>{{i}}</td>
<td><input type='text' [(ngModel)]="data[i]" /></td>
<td>{{i*data[i]}}</td>
</tr>
<tr>
<td></td>
<td></td>
<td>-------<br/>{{sum}}</td>
</tr>
<tbody>
</table>

Trying to pass object assigned checkedValue using jquery map, returning null?

I have been puzzling over this for the past three days and have gotten to a point where I really don't know what else to do. I'll explain the situation:
I have a for-each set up to display an observable array that was populated from an ajax GET request to a Sharepoint library. It is set up to display inside of a table and inside of this table I am adding checkboxes to each row with a checkedValue assignment of $data to pull in the objects selected. The thing that is puzzling me, is I can pass down a specific value of a property on the object and it will be the correct value, but if I try to pass down the whole object and then step into the object using dot(.Id) notation, the value I know exists console logs as undefined. My first instinct is that it is a quirk of $data in knockout that I am just unfamiliar with, but this is the first project I am having to use it on. Usually prefer Angular myself, but work wants otherwise, oh well.
Code is snipped below:
HTML:
<tbody data-bind="foreach: budgetDocs">
<tr>
<!--<td style="width: 5%" class="glyphicon glyphicon-triangle-right"></td>-->
<!--checkedValue: Id, click: $root.selectedDoc,-->
<!--Code above is to be pasted back into checkbox input upon finishing practice with checkboxes in knockout-->
<td style="width: 5%;"><input type="checkbox" class="budgets" data-bind="checked:$root.koselectedDocs, checkedValue:$data" /></td>
<td style="width: 5%;"><img src="/_catalogs/masterpage/scripts/css/Excel-icon.png" width="25px"/></td>
<td style="width: 35%;"><a class="docA" data-bind="text: Title, click: $root.openDoc"></a></td>
<td style="width: 25%;" data-bind="text: Property.PropertyID"></td>
<td style="width: 15%;" data-bind="text: Status"></td>
<!--Failed Attempt at new doc button placement-->
<!--<td style="width: 15%;"><button type="button">New Doc</button></td>-->
</tr>
</tbody>
Javascript:
self.postStatusUpdate = function(){
var selectedDocs = $("input:checkbox:checked").map(function ()
{
return $(this).val();
}).get();
for (var i = 0; i < selectedDocs.length; i++) {
var item = selectedDocs[i];
if(item.Id){
console.dir(item.Id);
}
else{console.log('No dice')}
}
return true;
}
Thank you for whatever insight you may be able to provide me into why this happens.

How to compare for two different objects inside ng-repeat

What is the best way to compare two different objects for equality inside of ng-repeat, why doesn't angular.equals work for me here:
<tr ng-repeat="row in ctrl.filteredItems" ng-class="{'active':angular.equals(row,ctrl.ngModel), 'focus': angular.equals(row,ctrl.focusedRow)}">
<td ng-repeat="value in ctrl.sort(row) track by $index" class="text-center">
{{value}}
</td>
</tr>
I want to add the active class if the current row and the pre-selected row coming from the controller match.
Write a function in JS which will do angular.equals(obj1, obj2) and use this function to check is it active or not?
HTML
<tr ng-repeat="row in ctrl.filteredItems" ng-class="{'active': checkEqualiy(row, ctrl.ngModel), 'focus': checkEquality(row,ctrl.focusedRow)}">
<td ng-repeat="value in ctrl.sort(row) track by $index" class="text-center">
{{value}}
</td>
</tr>
JS
$scope.checkEquality= function(param1, param2){
return angular.equals(param1, param2)
}

Displaying contents of a knockout observable array

Hi guys I'm new to java Script knockout framework, I would like to display contents of an array using knockout, in reality I want to retrieve these contents from a database using ajax but I decided to start with something simpler, which is like this:
$(document).ready(function() {
function requestViewModel() {
this.branchName = ko.observable();
this.allItems = ko.observableArray({items:[{orderItemId:1,
description:"Chocolate",
unitCost:8.50,
quantity:10,
total:84.0},
{orderItemId:2,
description:"Milk",
unitCost:5.0,
quantity:10,
total:50.0},
{orderItemId:3,
description:"Sugar",
unitCost:10.0,
quantity:20,
total:200.0}]});
};
ko.applyBindings(new requestViewModel());
});
......and here is my HTML
Branch Name: <input type="text" name = "branchName">
<br><br><br><br>
<table>
<thead>
<tr><th>Item id</th><th>Description</th><th>Unit Cost</th><th>Quantity</th><th>Total</th></tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: orderItemId"></td>
<td data-bind="text: description"></td>
<td data-bind="text: unitCost"></td>
<td data-bind="text: quantity"></td>
<td data-bind="text: total"></td>
</tr>
</tbody>
</table>
Please help where you can coz I get an error which is:
"Error: The argument passed when initializing an observable array must be an array, or null, or undefined."
The error says it all. You need to pass in an array instead of an object like so:
this.allItems = ko.observableArray([{orderItemId:1,
description:"Chocolate",
unitCost:8.50,
quantity:10,
total:84.0},
{orderItemId:2,
description:"Milk",
unitCost:5.0,
quantity:10,
total:50.0},
{orderItemId:3,
description:"Sugar",
unitCost:10.0,
quantity:20,
total:200.0}]);
Note the removal of {items: and }.

knotckout js foreach appending rows automatically in IE 7 and 8

I have a "mini-cart" in a eCommerce site that I am bulding. For the "mini-cart" i use foreach to add items to a table.
The "mini-cart" is a dropdown and it is rendered from a razor file. When i close the "mini-cart" and opens it again it automatically appends all the rows one more time. So everytime I close the "mini-cart" and opens it again. All the rows gets appended one more time.
When the cart is opened this code is run.
showCart = function () {
WebService.PostJson("/services/CartService.svc/GetCart", {},
function (cartDto) {
updateCart(cartDto);
postman.deliver('cartShown', 1);
},
function () {
});
};
The table looks like this.
<table width="100%" >
<thead data-bind="foreach: Items, stripe: Items, evenClass: 'even', oddClass: 'odd'">
<tr>
<td data-bind="text: ArticleNo">
</td>
<td data-bind="text: Name" style="width:390px;">
</td>
<td>
<input type="text" data-bind="value: Quantity" class="quantity"/>
</td>
<td class="cart-price-column">
<span class="cartRowSubTotal" style="display: none;" data-bind="text: SubTotal"></span>
</td>
<td>
Ta bort
</td>
</tr>
</thead>
</table>
updateCart does a Items.splice(0); so it should reload it self each time. But it does not seem to work i internet explorer 7 and 8.
Is there anyway to "clear" the table everytime? Or can the viewmodel figure this out on its own?
Thanks.
UPDATE:
It seemed that the splice method did not empty the array for some reason. When changed to cart.Items([]) it started to work.
Set cart.items(null);
Nothing to do with IE!
In IE, the length parameter is required. Leaving it empty causes the function to do nothing. Call it like this: Items.splice(0, Items().length).

Categories

Resources