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!