11

I have a situation where I have a list of data to be displayed in individual panels, Using Bootstrap's grid system, I'd like to take advantage of a wide screen and display several panels horizontally, but on narrow screens have them stack. I'm currently laying things out on the server side with ejs like this, with columns being passed in as a query parameter, typically set to 2 or 3, so each colClass is either col-sm-6 or col-sm-4.

<% var colWidth = 12/columns; var colClass = "col-sm-" + colWidth; %>
<% for(var i=0; i<contestData.classData.length; i++) {%>
    <% if ((classCount % columns) == 0) { %>
        <div class="row">
    <% } %>
    <div class="<%= colClass %>">
        <div class="panel panel-primary">
            <div class="panel-heading">
                <h3 class="panel-title"> <%= contestData.classData[i].name %> </h3>
            </div>
            <div>...</div>
        </div>
    </div>
    <% classCount++ %>
    <% if ((classCount % columns) == 0) { %>
        </div>
    <% } %>
<% } %>

This works, but doing this level of layout on the server side offends me, I'd really rather do this with Angular but I can't figure out how to wrap the appropriate number of panels in a div with class=row while doing ng-repeat or even ng-repeat-start="classData in contestData.classData"

Thanks!

pvogel
  • 131
  • 1
  • 1
  • 6

5 Answers5

55

Here a simple solution with just HTML, 3 ROWS

<div class="row" >
    <div class="col-md-4" ng-repeat-start="item in data">
        I'M A ROW
    </div>
    <div class="clearfix" ng-if="($index+1)%3==0"></div>
    <div ng-repeat-end=""></div>
</div>
Mohammed Safeer
  • 20,751
  • 8
  • 75
  • 78
Oswaldo Alvarez
  • 4,772
  • 1
  • 22
  • 20
  • 1
    I'm gonna go test this now, and if it works like I'm expecting I'm gonna give you an upvote. Okay? – M.K. Safi Apr 10 '15 at 21:21
  • @Jervelund Since all bootstrap column are floating with the `float:left` css property, the clearfix is a bootstrap class that is going to `clear:both` side of the row in order to start a new row. So, at each loop, a column is going to be created and after 3 column, a separator is inserted to start a new row. – RPDeshaies Jun 18 '15 at 18:22
  • Just add the cols to the rows and BS3 figures out the stacking. – Ali Gajani Sep 09 '15 at 21:42
  • Not the best approach but it works. Filter works as well. So +1 – ziaprog Nov 11 '15 at 09:22
  • Its good solution but its issue with ng-messages. control move top and bottom when showing error message. – Vijay Maheriya Mar 20 '17 at 05:44
4

If you start by chunking your data into smaller parts, based on the number of columns, it will be easy to use nested ng-repeats to create your layout:

$scope.getRows = function(array, columns) {
  var rows = [];

  //https://stackoverflow.com/questions/8495687/split-array-into-chunks
  var i,j,temparray, chunk = columns;
  for (i=0,j=array.length; i<j; i+=chunk) {
      temparray = array.slice(i, i+chunk);

      rows.push(temparray);
  }

  return rows;
};

$scope.rows = $scope.getRows($scope.contestData, $scope.columns);

Then your markup is simply:

<div ng-repeat="row in rows">
  <div class="row">
    <div ng-class="{'col-xs-4': columns == 3, 'col-xs-3': columns == 4}" ng-repeat="contest in row">
      <div class="panel panel-primary">
          <div class="panel-heading">
            <h3 class="panel-title">{{contest}}</div>
          </div>
      </div>
    </div>
  </div>
</div>

Notice that ng-class is doing the work of deciding which type of class to add based on the number of columns. This example is handing 3 and 4, but you could extend it to handle others.

Here is a working demo: http://plnkr.co/edit/B3VAXlq9dkzO3hQkbkN3?p=preview

Update:
Plunker's full screen mode seems to interfere with the column width style, so I changed the link to display in preview mode.

Community
  • 1
  • 1
j.wittwer
  • 9,497
  • 3
  • 30
  • 32
1

Answering my own question here, similar to the answer from j.wittwer, I created a filter to chunk my data appropriately by row, etc.:

angular.module('myApp.filters').
    filter('rowfilter', function () {
        return function (data, columnCount) {
            var rows = [];
        var colCount = columnCount || 2;
        var columns = [];
        for (var i = 0; i< data.length; i++) {
        columns.push(data[i]);
        if (columns.length == colCount) {
        rows.push(columns);
        columns = [];
        }
        }
        if (columns.length > 0) {
        rows.push(columns);
        }
        return rows;
        };
    });

And then I use the filter (jade shown here): .row(ng-repeat="row in contestData.classData | rowfilter") .col-sm-6(ng-repeat="column in row")

Works very nicely, still wrapping my head around Angular!

pvogel
  • 131
  • 1
  • 1
  • 6
  • It's elegant, it's nice, it's logical, it's angularish. Though I have this weird behavior in angular that this filter gets called recursively and I get this: Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting! – Diego Pamio Jul 12 '15 at 02:27
1

I have this decision, seems to be working for 3 col

<div ng-repeat="r in data">
    <div class="row" ng-if="$index%3==0">
        <div class="col-md-4" ng-if="$index<data.length">
        {{data[$index]}}
        rrr
        </div>
        <div class="col-md-4" ng-if="$index+1<data.length">
        {{data[$index+1]}}
        rrr
        </div>
        <div class="col-md-4" ng-if="$index+2<data.length">
        {{data[$index+2]}}
        rrr
        </div>
    </div>
</div>

and data is

$scope.data = ['1','2','3','4','5','6','7'];
0

You can add something like this, first in your controller, do a function dad gets an integer "breakpoint" that is the number of columns you want to wrapped by a row, and the data you want inside each column like so:

  function getRows(breakpoint,data) {
        var len = data.length; var i = 0;
        var rows = []; var temp = [];
        for (; i < len; i++) {
            if (i % breakpoint == 0 && i != 0) {
                rows.push(temp);               
                temp = [];
            } 
            temp.push(data[i]);
        }
        var len2 = rows.length * breakpoint;
        if (len > len2) {
            //var leftOvers = len - len2;
            i = len2; temp = [];
            for (; i < len; i++) {
                temp.push(data[i]);
            }
            rows.push(temp);
        }

        return rows;
    }

then whenever you recive the data yo simply do:

 $scope.rows = getRows(3,data); // in case you want 3 cols.

then in your html:

    <div class="row"  ng-repeat="row in rows">
     <div class="col-lg-4" ng-repeat="data in row">
       {{data.whatever}}
     </div>
</div>
     </div>

and that`s it, it should work for u.