Update Totals in ng-repeat - angularjs

Please take a look at this fiddle
What I'm trying to do is to calculate/update totals for each column inside a nested ng-repeat:
<tr ng-repeat="cust in customers">
<td>{{cust.name}}</td>
<td ng-repeat="book in books">
<p ng-init="index=getIndex(book.id, cust.id)"></p>
<input ng-model="qtys[index].qty">
</td>
</tr>
<tr>
<td>Total</td>
<td ng-repeat="book in books">
<input type=text value="{{sumQty(book.id)}}">
</td>
</tr>
I have 2 problems that I'm trying to solve:
The input boxes are now showing the correct values from my array. I'm trying to use ng-init to get the index from the array first based on the custId and bookId I pass in:
<p ng-init="index=getIndex(book.id, cust.id)"</p>
<input ng-model="qtys[index].qty">
I guess I'm not sure how to correctly bind the ng-model
The totals are not working. But I guess I need to solve the #1 problem before I can get this to work.
Please help. Thanks.

You got it almost right. The problem is in your getIndex method - the return that you do there does not return from getIndex, it just exits the function that you passed to forEach. If you modify it like this, it will work
$scope.getIndex = function(bookId, custId) {
var index = null;
angular.forEach($scope.qtys , function(item, idx) {
if (item.bookId == bookId && item.custId == custId) {
index = idx;
};
});
return index;
};
scope.sumQty seems unfinished (it takes a list as parameter, but you are passing an id to it). One way of fixing it would be to make it accept the id instead
$scope.sumQty = function(id) {
var total=0;
angular.forEach($scope.qtys , function(item){
if(item.bookId == id) {
total+= parseInt(item.qty);
}
});
return total;
}

Related

ng-repeat dynamically create arrays for nested ng-repeats

Is it possible to have ng-repeat dynamically create arrays for nested ng-repeats?
I know this sounds silly, but I'm essentially looking for something like this, and hoping someone will tell me how terrible of an idea this is and present a better solution:
<tbody ng-repeat="row in myRows track by $index">
<tr>{{row.name}}</tr>
<tr ng-repeat="subRow in myRows$index>{{subRow.name}}</tr>
</tbody>
So the idea is that the first <tr> row actually has a button that will show the subRows once clicked. So once clicked (before it actually displays the rows), I'll create the array then, such as:
myRows0 = [{name:"Sub Row A", value:1},
{name:"Sub Row B", value:2}];
or if the second row was clicked, I'd create:
myRows1 = [{name:"Sub Row C", value:3},
{name:"Sub Row D", value:4}];
I'm assuming something like this won't work because ng-repeat needs to have the array created before it can create the DOM. Is that correct? But I'm not sure how else I'd be able to create something like this then using this table structure. Any help?
It is possible and in my opinion it is not a bad idea, for example you may want to load your subRow data only when user clicks on displaySubRow items if your subRow data is big or they are images, to avoid putting an unnecessary burden to your server or keep your users waiting.
Working Plunker
Sample Code
html
<table>
<tbody ng-repeat="item in data" ng-init="item.show = false">
<tr>
<td>
<button ng-show="item.show==false" ng-click="getRowSubItems(item)">Show</button>
<button ng-show="item.show==true" ng-click="item.show = false">Hide</button>
</td>
<td>
{{item.name}}
</td>
</tr>
<tr ng-show="item.show == true">
<td>
</td>
<td>
<div ng-repeat="subItem in item.cars">
{{subItem}}
</div>
</td>
</tr>
</tbody>
</table>
js
$scope.data =
[{"name":"Lex",
"age":43
},
{"name":"Alfred",
"age":30
},
{"name":"Diana",
"age":35
},
{"name":"Bruce",
"age":27
},
{"name":"Oliver",
"age":32
}];
$scope.getRowSubItems = function(item){
//you can also make a http call to get data from your server
item.show = true;
if(item.name == "Lex"){
$http.get('https://jsonplaceholder.typicode.com/posts/1')
.then(function(response) {
item.cars = response.data;
});
}
else{
item.cars = [ "Ford", "BMW", "Fiat" ];
}
}

How to add a new record and not update previous record in angularjs

How do I add a new record and have angular "forget" the new item added after I click the corresponding button.
The ability to add a record works and when I add the new record it display in the table I have, correctly, However my problem is that when I go to add a second record ie I remove the previous input and type something else. This results in the newly added record below to also change.
In short after I add an item to my array I want angular to forget it. How do I accomplish this.
In my controller I have this
(function () {
var app = angular.module("mainApp");
var ordersController = function ($scope,$filter, ordersService,customerService) {
$scope.orders = [];
$scope.addOrder = function (newOrder) {
$scope.orders.push(newOrder);
}
app.controller("ordersController", ["$scope","$filter", "ordersService","customerService", ordersController]);
}());
in my html I have this
<div><table>
<tr>
<td><input ng-model="item.quantity" type="text" /></td>
<td><button type="button" ng-click="addOrder(item)">Add Line Item</button></td>
</tr>
</table></div>
<div>
<table>
<tr ng-repeat="order in orders track by $index">
<td>
{{ order.quantity }}
</td>
</tr>
</table>
</div>
The problem might be that you are adding the newOrder object to the array while Angular keeps it's model bound to that object. Try using Angular's copy functionality like this (assuming that you are using Angular 1):
$scope.addOrder = function (newOrder) {
var copiedOrder = angular.copy(newOrder);
$scope.orders.push(copiedOrder);
}
The documentation for the copy method can be found here.

Hide html element with same data in ng-repeat in angularjs

I have a array of task in which there are two duplicate departments, but i dont want to show the second duplicate record in ng-repeat, but only for the first record i want to show even if it is duplicate.
Here is my code, can anyone tell me where i'm going wrong.
<tr ng-repeat="t in item.requisitionTask track by t.id">
<td>
<div ng-hide="!$first ? item.requisitionTask[$index-1].department.departmentCode==$index.department.departmentCode : false">
{{t.department.departmentName}}</div>
</td>
Solved it by targeting the data particularly.
<td><div ng-hide="!$first ? item.requisitionTask[$index-1].department.departmentCode==item.requisitionTask[$index].department.departmentCode : false">
I suspect that you want to hide data if the previous item contains the same departmentCode as your current item. As mentioned in my comment, you should move this logic into a function on your controller's scope.
<tr ng-repeat="t in item.requisitionTask track by t.id">
<td>
<div ng-hide="isNotFirstOrSameCodeAsPrevious($index)">
{{t.department.departmentName}}
</div>
</td>
</tr>
In your controller:
function isNotFirstOrSameCodeAsPrevious($index) {
if ($index === 0) return false;
return item.requisitionTask[$index - 1].department.departmentCode ===
item.requisitionTask[$index].department.departmentCode;
}

Angular ng-repeat stay paged after filter

I'm using AngularJS and I have a simple table using ng-repeat and filter. When i insert anything at the search input it filters OK but it keeps paginated as it was before I filter the table. Any ideas why this is happening ?
Here's my code:
//some var initialization
$scope.pagina = {};
$scope.pagina.currentPage = 1,
$scope.pagina.numPerPage = 10,
$scope.pagina.maxSize = 5;
//return from server with data
coresFactory.getCores().then(function(response) {
$scope.tabelaCores = response.data;
$scope.filteredTodos = $scope.tabelaCores.cores.slice(0, 10);
});
//do pagination
$scope.$watch('pagina.currentPage', function() {
var begin = (($scope.pagina.currentPage - 1) * $scope.pagina.numPerPage),
end = begin + $scope.pagina.numPerPage;
$scope.filteredTodos = $scope.tabelaCores.cores.slice(begin, end);
},true);
<input ng-model="pesquisaTabela" type="search" style="width:300px;" class="form-control input-inline" placeholder="" aria-controls="sample_1"></label>
<table class="table" style="margin-bottom:5px;border:1px solid #DDDDDD" id="sample_1">
<thead>
<tr style="background-color:#F9F9F9">
<th style="width:100px; text-align:center;"> Id </th>
<th> Nome </th>
<th> Plural </th>
<th style="width:100px; text-align:center;"> Ativo </th>
</tr>
</thead>
<tbody>
<tr ng-repeat="cor in filteredTodos | filter:pesquisaTabela" ng-click="setSelected(cor.id)" ng-class="{linhaSelected: cor.id === idCorSelecionada}">
<td style="text-align:center;"> {{cor.id}} </td>
<td style="text-transform:capitalize"> {{cor.nome}} </td>
<td style="text-transform:capitalize"> {{cor.plural}} </td>
<td style="text-align:center;">
<span ng-if="cor.status=='sim'" class="label label-sm label-success"> Sim </span>
<span ng-if="cor.status=='nao'" class="label label-sm label-danger"> Não </span>
</td>
</tr>
</tbody>
</table>
<pagination first-text="<<" last-text=">>" next-text=">" previous-text="<" ng-model="pagina.currentPage" total-items="tabelaCores.cores.length" max-size="pagina.maxSize" boundary-links="true"></pagination>
You have quite a disconnect between various parts
Pagination is working off of one array, display from another and filter from yet another because when filter kicks in it returns a new filtered array.
The way you have things structured, your filter won't work properly either.
When you slice the main data array the filter is only going to work on that part that is sliced....not the whole main array
In order for pagination to be synchronized with filtering your simplest start point would likely be do your own filtering and share the same filtered array between the pagination and the table.
There are some other built in filters that you could use also like limitTo that takes 2 arguments limit and begin. That would help you get rid of currrent slice
There are lots of available grid/table directives available.
Using one of those would be my best suggestion
There is one other way you could do this all in the view. There is a syntax for ng-repeat that creates a new filtered array on the scope and therefore gives you access to it's length
<tr ng-repeat="row in filteredData = ( data |filter:pesquisaTabela | limitTo:10:start)">
Using this you could pass filteredData array to the total-items of the pagination directive.
It also now lets you do things like:
<div> Filtered length is: {{filteredData.length}}</div>

AngularJS dynamically add elements to ng-repeat template

I have an array called 'variables' that I turn into a table.
<tr ng-repeat="variable in variables">
<td>
<textarea ng-model="variable.extras" ...>{{variable.extras}}</textarea>
</td>
I then have a function numberOfRowsForChoiceVariable that looks at variable.extras and determines how many lines it has, doing things like removing blank lines, etc... So now on the next <td> element I want to have as many <select> elements as returned by the call to numberOfRowsForChoiceVariable(variable).
So if they put 4 lines into variable.extras, then I want to display 4 select elements inside the next <td>
I'm thinking I need to use some type of $watch() to enable that, but I'm not sure how to use that within the ng-repeat scope.
Use Case
Please let me know if I have misunderstood your question. Here's what I understand from your question.
User enters text into the textarea
System counts the number of lines
Number of lines will create that number of select element. [edit: updated solution to meet this requirement]
Solution
You can use a filter to create a list from your line counting.
Controller
Based on your requirements, this controller uses regular expressions to count the number of lines. I haven't tested this on a Unix system, but it works fine on Windows. The lines function will return the number of lines from a given string input.
function ctrl($scope) {
$scope.variables = [{extras: ""}, {extras: ""}, {extras: ""}];
$scope.lines = function (input) {
if (angular.isString(input)) {
return input.split(/\r?\n/).length;
}
return 0;
};
}
Filter
A filter is required to create a temporary array of a given length. This is a common component from functional programming.
var app = angular.module('app', []);
app.filter('range', function () {
return function (input, total) {
total = parseInt(total);
input = Array.apply(null, Array(total))
.map(function(value, index) {
return index;
});
return input;
};
});
View
Using this view, the <select> can be dynamically created next to the textarea. I've shortened the variable names so it could fit within this screen, but don't do this, it's bad form.
<div ng-app='app' ng-controller="ctrl">
<table>
<tr ng-repeat="v in variables">
<td>
<textarea ng-model="v.extras">
{{v.extras}}
</textarea>
</td>
<td>
<select ng-repeat="n in [] | range:lines(v.extras)">
</select>
</td>
</tr>
</table>
</div>
Demo
You can access a demo from this jsFiddle.

Resources