AngularJS ng-repeat to populate grid from array - arrays

Thanks in advance for reading. I am trying to utilize angular's ng-repeat to render objects from an array into an Nx3 Table. For the sake of example let's consider a 3x3 table.
Here is a simplified example of the array:
objects = [{"text": "One, One"},{"text": "One, Two"},{"text": "One, Three"},
{"text": "Two, One"},{"text": "Two, Two"},{"text": "Two, Three"},
{"text": "Three, One"},{"text": "Three, Two"},{"text": "Three, Three"}];
The "text" field describes where in the 3x3 grid matrix each element should appear. I would like to use ng-repeat on objects to generate html that looks like this:
<table>
<tr>
<td>One, One</td>
<td>One, Two</td>
<td>One, Three</td>
</tr>
<tr>
<td>Two, One</td>
<td>Two, Two</td>
<td>Two, Three</td>
</tr>
<tr>
<td>Three, One</td>
<td>Three, Two</td>
<td>Three, Three</td>
</tr>
</table>
Is there any way to achieve this without needing to break up the array into separate arrays for each row?

Best possibly way would be to alter your view model in the controller and bind that to ng-repeat (But you already said you do not want to do that). If you ever plan to take that route you can also take a look at user #m59 answer where he creates a reusable filter to do it. However this is just a simple answer making use of built in filter's configurable evaluation expression where we can return truthy/falsy value to determine if they need to be repeated or not. This eventually has the only advantage of no need to create 2 ng-repeat blocks (But that is not so bad though). So in your controller add a function on the scope,
$scope.getFiltered= function(obj, idx){
//Set a property on the item being repeated with its actual index
//return true only for every 1st item in 3 items
return !((obj._index = idx) % 3);
}
and in your view apply the filter:
<tr ng-repeat="obj in objects | filter:getFiltered">
<!-- Current item, i.e first of every third item -->
<td>{{obj.text}}</td>
<!-- based on the _index property display the next 2 items (which have been already filtered out) -->
<td>{{objects[obj._index+1].text}}</td>
<td>{{objects[obj._index+2].text}}</td>
</tr>
Plnkr

I wanted to do the exact same thing.
Convert an array into a matrix/ grid
I have an array which i wanted to convert into a grid/matrix of column size 4. the following implementation worked for me. You can use the two counters : row and col as you like in side the nested ng-repeat
In my case number of columns is 3. But you can replace that 3 with a variable everywhere. h.seats is my array of the objects and i want to print either X or - based on value of element in that array
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th ng-repeat="n in [].constructor(3 + 1) track by $index">{{$index}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="(row, y) in getNumber(h.seats.length, 3) track by $index">
<td>{{row+1}}</td>
<td class="text-primary"
ng-repeat="(col, t) in h.seats track by $index"
ng-if="col >= (row)*3 && col < (row+1)*3">
<span ng-show="t.status"> X </span>
<span ng-show="!t.status"> - </span>
</td>
</tr>
</tbody>
</table>
</div>
<th ng-repeat="n in [].constructor(3 + 1) track by $index">{{$index}}</th> prints the header row with column number at the top. getNumber(h.seats.length, 3) returns me the number of rows of that table as follows
.controller('CustomViewController', function ($scope, Principal, $state) {
$scope.getNumber = function(length, columns) {
return new Array(parseInt(length / columns + 1, 10));
}
The line ng-if="col >= (row)*3 && col < (row+1)*3" is important logic to calculate which elements should be put in that row.
The output looks like below
0 1 2 3
1 e1 e2 e3
2 e4 e5 e6
3 e7 e8
Refer to following link for details of how row and col counters are used:
https://stackoverflow.com/a/35566132/5076414

Related

how to print a two diffetent array element in single tr using json in angularjs

I want to print a two arrays in a single tr. My json is like
[{"group_name":"Ecofresh","id":"19","userId":"19","device_details":[{"userid":"19","unit_id":"35","unit_name":"Test123","mac_id":"EB9F5E5727D7","m2xa":"95005c74d3cb33ca320e4b92cdee617d","m2xd":"5a00144b70bda43667498cf44677ad3e","mdlno":"Mtemp","group_id":"19","watchstat":"0","battery":"3.02","network":"-58","updatetime":"2018-06-22 11:50:20 ","unitShow":[{"unit":"celcius"},{"unit":"%"}],"channels":[{"id":"106865","chkey":"ch1","chvalue":"30","updatetime":"2018-06-22 11:50:20 ","unit_id":"35"},{"id":"106866","chkey":"ch2","chvalue":"69","updatetime":"2018-06-22 11:50:20 ","unit_id":"35"}]},{"userid":"19","unit_id":"46","unit_name":"T4","mac_id":"E51068E8A28C","m2xa":"f6852dfc4b49908c02a3d7893eba6331","m2xd":"4c798928f651626ccb2a30ffbd4820c3","mdlno":"Mtemp","group_id":"19","watchstat":"0","battery":"3.04","network":"-62","updatetime":"2018-06-22 11:50:21 ","unitShow":[{"unit":"celcius"},{"unit":"celcius"}],"channels":[{"id":"106867","chkey":"ch1","chvalue":"30","updatetime":"2018-06-22 11:50:21 ","unit_id":"46"},{"id":"106868","chkey":"ch2","chvalue":"69","updatetime":"2018-06-22 11:50:21 ","unit_id":"46"}]},{"userid":"19","unit_id":"47","unit_name":"T2","mac_id":"C0A02FF440A1","m2xa":"ed2e2b818fbe24584c6a7b50b3db8d9e","m2xd":"4e2f9beb516da0a65361770fd2276c0d","mdlno":"Mtemp","group_id":"19","watchstat":"0","battery":"3","network":"-62","updatetime":"2018-06-22 11:50:24 ","unitShow":[{"unit":"%"},{"unit":"%"}],"channels":[{"id":"106869","chkey":"ch1","chvalue":"30","updatetime":"2018-06-22 11:50:24 ","unit_id":"47"},{"id":"106870","chkey":"ch2","chvalue":"68","updatetime":"2018-06-22 11:50:24 ","unit_id":"47"}]}]}]
and code is.
<tr ng-repeat="item3 in item1.channels" ng-show="item3.chkey !=null "&&"item3.value!= null"><td> {{item3.chkey}}={{item3.chvalue}}</td> </tr>
want to show element unitShow next to the chvalue. means for first row i want to print
ch1=30 celcius
ch2=69 %
like this
You can use the $index to access element from the first array.
<tr ng-repeat="item3 in item1.channels" ng-show="item3.chkey !=null && item3.value!= null"><td> {{item3.chkey}}={{item3.chvalue}} {{item1.unitShow[$index].unit}}</td></tr>

How to enter vertical values in Tables in HTML via AngularJS?

For the starters, I would like to have something like this,
Array 1 Array 2 Array 3 Array 4
Arr1val1 Arr2val1 Arr3val1 Arr4val1
Arr1val2 Arr2val2 Arr3val2 Arr4val2
Arr1val3 Arr3val3 Arr4val3
Arr1val4 Arr4val4
Arr4val5
I have already solved the issue in the crudest way possible,a solution that won't scale to other data types and if number of variable increase to lets say 10 columns it will fail in the most glorious way which makes me cringe and ask for suggestions of improvement.
My naive approach is here in a fiddle.
As the code is simple I would like to explain what I did.
Step 1 : Calculate the highest number of elements in all arrays and the array from which it is coming.
Step 2 : Make the number of elements in all array same as the number of elements in the highest number of element possible by filling in blanks.
Step 3 : Iterate over one in the HTML and print all.
Would like to know what can be done to make this solution better and more scalable?
Instead of finding max length of arrays, push all the values to the specific array.
Draw a table inside the td. So here the data are drawn vertically.
Using this scenario, you can add any number of data to any array, since
this is generating vertically.
Please see the working code
https://jsfiddle.net/yaxmjpkp/7/
var app = angular.module('myApp', []);
app.controller('myController', function($scope) {
//Test case 1
$scope.arr1 = ["Arr1val1", "Arr1val2", "Arr1val3", "Arr1val4"];
$scope.arr2 = ["Arr2val1", "Arr2val2"];
$scope.arr3 = ["Arr3val1", "Arr3val2", "Arr3val3"];
$scope.arr4 = ["Arr4val1", "Arr4val2", "Arr4val3", "Arr4val4", "Arr4val5","Arrayval6"];
$scope.tableValues=[];
$scope.tableValues.push($scope.arr1);
$scope.tableValues.push($scope.arr2);
$scope.tableValues.push($scope.arr3);
$scope.tableValues.push($scope.arr4);
debugger
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myController">
<table>
<thead>
<td>
Header 1
</td>
<td>
Header 2
</td>
<td>
Header 3
</td>
<td>
Header 4
</td>
</thead>
<tbody>
<tr>
<td ng-repeat="val in tableValues" valign = "top">
<table>
<tr ng-repeat="val2 in val">
<td>{{val2}}</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</div>
</div>
As far as I know you don't need to do the first two steps. As javascript is loosely typed language and so is angular (although its not a language but its built over JS). So when you access some value like
<tr ng-repeat="val in arr1 track by $index">
<td>{{arr1[$index]}}</td>
<td>{{arr2[$index]}}</td>
<td>{{arr3[$index]}}</td>
<td>{{arr4[$index]}}</td>
</tr>
Angular won't find any element on that index (Because of the unequal size of that array). So you won't have any issue regarding the access of the value on that particular index. And an empty <td> will be added in that place.
Btw, you can store array elements in associative array and you can check max length in a more generic way.
$scope.arrs = {
arr1: ["Arr1val1", "Arr1val2", "Arr1val3", "Arr1val4"],
arr2: ["Arr2val1", "Arr2val2"],
arr3: ["Arr3val1", "Arr3val2", "Arr3val3"],
arr4: ["Arr4val1", "Arr4val2", "Arr4val3", "Arr4val4", "Arr4val5"]
};
var maxLength = 0;
for(var arrKey in $scope.arrs){
maxLength = Math.max($scope.arrs[arrKey].length, maxLength);
});
Here is complete solution: https://plnkr.co/edit/p6EcXeiRKHY2wQA3xCi4?p=preview

identifying that table header values are what they should be

I'm trying to prove that in a table we have the following table labels. Date, amount, comment.
<table class="grid-table-body">
<thead>
<tr>
<th>Date</th>
<th>Amount (£)</th>
<th>Description</th>
</tr>
</thead>
<tbody>
.....
.....
.....
</tbody>
</table>
I've got as far as proving the table is present!
var myTable = element(by.css('.grid-table-body'));
expect(myTable.isPresent()).toBeTruthy();
How can I loop through each <th> and get the text. If I was just to put them into an array I could prove they are what they should be. i.e.
expect(data.get(0).getText()).toBe("Date");
Would be enough (I think)
First locate the elements, then you can call getText():
var headers = $$(".grid-table-body thead th");
expect(headers.getText()).toEqual(["Date", "Amount (£)", "Description"]);
To check that all of the headers are visible, you can either use:
expect(headers.isDisplayed()).toEqual([true, true, true]);
Or, check if there is no false in the array:
expect(headers.isDisplayed()).not.toContain(false);
You can also map()/reduce() it to a single boolean value.

Keep a running total in Angular's ng-repeat

I'm having a hard time getting the result that I'm looking for. Here is an image of my table row.
The final column (the one circled in blue) is support to count that mini-tables (Quantity - Allocated - Reserved) then subtracts the number in orange (in this case -4).
I can get the first row to work (for example, it shows 1, which is correct). But the second row should be accumulative, so that it adds the blue number from the previous row. I'm not sure how to accomplish this in Angular.
This is my view template:
<tr ng-repeat="inbound in inventory.inbound">
<td><% inbound.fordate %></td>
<td><% inbound.quantity %></td>
<td><% inbound.allocated %></td>
<td><input type="number" ng-model="inbound.reserved" ng-change="submitChangedInbound()" class="inbound_reserved"></td>
<td><span class="label round" ><% availableAfterShipment() %></span</td>
</tr>
and this is my function:
$scope.availableAfterShipment = function(amount) {
return $scope.dealerAvailable + this.inbound.quantity - this.inbound.allocated - this.inbound.reserved;
};

Ng-Repeat array to rows and columns

Thanks for taking the time to read this, I was wondering how I might be able to use ng-repeat to create a grid like box of options. I would like to take an array repeat nth number of items and then move to the next row or column until all items are listed. e.g.
assuming I had an array like [opt1,opt2,opt3,opt4,opt5,opt6,opt7] I would like to display it like this:
opt1 opt2 opt3
opt4 opt5 opt6
opt7
This is more a styling/markup problem than an AngularJS one. If you really want to, you can do:
<span ng:repeat="(index, value) in array">
{{value}}<br ng:show="(index+1)%3==0" />
</span>
http://jsfiddle.net/JG3A5/
Sorry for my HAML and Bootstrap3:
.row
.col-lg-4
%div{'ng:repeat' => "item in array.slice(0, array.length / 3)"}
{{item}}
.col-lg-4
%div{'ng:repeat' => "item in array.slice(array.length / 3, array.length * 2/3)"}
{{item}}
.col-lg-4
%div{'ng:repeat' => "item in array.slice(array.length * 2/3, array.length)"}
{{item}}
There is another version, with possibility to use filters:
<div class="row">
<div class="col-md-4" ng-repeat="remainder in [0,1,2]">
<span ng-repeat="item in array" ng-if="$index % 3 == remainder">{{item}}</span>
</div>
</div>
If all of your items are in one single array, your best bet is to make a grid in CSS. This article should be helpful: http://css-tricks.com/dont-overthink-it-grids/
You can use $index from ng-repeat to apply the correct class for your column (in this case a 4 column grid):
<div class="col-{{ $index % 4 }}"></div>
If you have a 2 dimensional array (split into rows and columns) that opens up more possibilities like actually using an HTML table.
I find it easier to simply use ng-repeat combined with ng-if and offsetting any indexes using $index. Mind the jade below:
div(ng-repeat="product in products")
div.row(ng-if="$index % 2 === 0")
div.col(ng-init="p1 = products[$index]")
span p1.Title
div.col(ng-if="products.length > $index + 1", ng-init="p2 = products[$index + 1]")
span p2.Title
div.col(ng-if="products.length <= $index + 1")
Between Performance, Dynamics and Readability
It seems putting the logic in your JavaScript is the best method. I would just bite-the-bullet and look into:
function listToMatrix(list, n) {
var grid = [], i = 0, x = list.length, col, row = -1;
for (var i = 0; i < x; i++) {
col = i % n;
if (col === 0) {
grid[++row] = [];
}
grid[row][col] = list[i];
}
return grid;
}
var matrix = listToMatrix(lists, 3);
console.log('#RedPill', matrix);
# Params: (list, n)
Where list is any array and n is an arbitrary number of columns desired per row
# Return: A matroid
# Note: This function is designed to orient a matroid based upon an arbitrary number of columns with variance in its number of rows. In other words, x = desired-columns, y = n.
You can then create an angular filter to handle this:
Filter:
angular.module('lists', []).filter('matrical', function() {
return function(list, columns) {
return listToMatrix(list, columns);
};
});
Controller:
function listOfListsController($scope) {
$scope.lists = $http.get('/lists');
}
View:
<div class="row" ng-repeat="row in (lists | matrical:3)">
<div class="col col-33" ng-repeat="list in row">{{list.name}}</div>
</div>
With this, you can see you get n number of rows -- each containing "3" columns. When you change the number of desired columns, you'll notice the number of rows changes accordingly (assuming the list-length is always the same ;)).
Here's a fiddle.
Note, that you get the ol' Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!. This is because Angular is recalling the matrical function upon every iteration. Allegedly, you can use the as results alias to prevent Angular from reevaluating the collection, but I had no luck. For this, it may be better to filter the grid inside of your controller and use that value for your repeater: $filter('matrical')(items) -- but please post back if you come across an elegant way of filtering it in the ng-repeat.
I would stress, again, you're probably heading down a dark alley by trying to write the logic in your view -- but I encourage you to try it in your view if you haven't already.
Edit
The use of this algorithm should be combined with a Matrical Data-Structure to provide methods of push, pop, splice, and additional methods -- in tandem with appropriate logic to complement Bi-Directional Data-Binding if desired. In other words, data-binding will not work out of the box (of course) as when a new item is added to your list, a reevaluation of the entire list must take place to keep the matrix's structural integrity.
Suggestion: Use the $filter('matrical')($scope.list) syntax in combination with $scope.$watch and recompile/calculate item-positions for the matrix.
Cheers!

Resources