how to split the ng-repeat data with three columns using bootstrap
The most reliable and technically correct approach is to transform the data in the controller. Here's a simple chunk function and usage.
function chunk(arr, size) {
var newArr = [];
for (var i=0; i<arr.length; i+=size) {
newArr.push(arr.slice(i, i+size));
}
return newArr;
}
$scope.chunkedData = chunk(myData, 3);
Then your view would look like this:
<div class="row" ng-repeat="rows in chunkedData">
<div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>
If you have any inputs within the ng-repeat
, you will probably want to unchunk/rejoin the arrays as the data is modified or on submission. Here's how this would look in a $watch
, so that the data is always available in the original, merged format:
$scope.$watch('chunkedData', function(val) {
$scope.data = [].concat.apply([], val);
}, true); // deep watch
Many people prefer to accomplish this in the view with a filter. This is possible, but should only be used for display purposes! If you add inputs within this filtered view, it will cause problems that can be solved, but are not pretty or reliable.
The problem with this filter is that it returns new nested arrays each time. Angular is watching the return value from the filter. The first time the filter runs, Angular knows the value, then runs it again to ensure it is done changing. If both values are the same, the cycle is ended. If not, the filter will fire again and again until they are the same, or Angular realizes an infinite digest loop is occurring and shuts down. Because new nested arrays/objects were not previously tracked by Angular, it always sees the return value as different from the previous. To fix these "unstable" filters, you must wrap the filter in a memoize
function. lodash
has a memoize
function and the latest version of lodash also includes a chunk
function, so we can create this filter very simply using npm
modules and compiling the script with browserify
or webpack
.
Remember: display only! Filter in the controller if you're using inputs!
Install lodash:
npm install lodash-node
Create the filter:
var chunk = require('lodash-node/modern/array/chunk');
var memoize = require('lodash-node/modern/function/memoize');
angular.module('myModule', [])
.filter('chunk', function() {
return memoize(chunk);
});
And here's a sample with this filter:
<div ng-repeat="row in ['a','b','c','d','e','f'] | chunk:3">
<div class="column" ng-repeat="item in row">
{{($parent.$index*row.length)+$index+1}}. {{item}}
</div>
</div>
Order items vertically
1 4
2 5
3 6
Regarding vertical columns (list top to bottom) rather than horizontal (left to right), the exact implementation depends on the desired semantics. Lists that divide up unevenly can be distributed different ways. Here's one way:
<div ng-repeat="row in columns">
<div class="column" ng-repeat="item in row">
{{item}}
</div>
</div>
var data = ['a','b','c','d','e','f','g'];
$scope.columns = columnize(data, 3);
function columnize(input, cols) {
var arr = [];
for(i = 0; i < input.length; i++) {
var colIdx = i % cols;
arr[colIdx] = arr[colIdx] || [];
arr[colIdx].push(input[i]);
}
return arr;
}
However, the most direct and just plainly simple way to get columns is to use CSS columns:
.columns {
columns: 3;
}
<div class="columns">
<div ng-repeat="item in ['a','b','c','d','e','f','g']">
{{item}}
</div>
</div>
how to split the ng-repeat data with three columns using bootstrap
The most reliable and technically correct approach is to transform the data in the controller. Here's a simple chunk function and usage.
function chunk(arr, size) {
var newArr = [];
for (var i=0; i<arr.length; i+=size) {
newArr.push(arr.slice(i, i+size));
}
return newArr;
}
$scope.chunkedData = chunk(myData, 3);
Then your view would look like this:
<div class="row" ng-repeat="rows in chunkedData">
<div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>
If you have any inputs within the ng-repeat
, you will probably want to unchunk/rejoin the arrays as the data is modified or on submission. Here's how this would look in a $watch
, so that the data is always available in the original, merged format:
$scope.$watch('chunkedData', function(val) {
$scope.data = [].concat.apply([], val);
}, true); // deep watch
Many people prefer to accomplish this in the view with a filter. This is possible, but should only be used for display purposes! If you add inputs within this filtered view, it will cause problems that can be solved, but are not pretty or reliable.
The problem with this filter is that it returns new nested arrays each time. Angular is watching the return value from the filter. The first time the filter runs, Angular knows the value, then runs it again to ensure it is done changing. If both values are the same, the cycle is ended. If not, the filter will fire again and again until they are the same, or Angular realizes an infinite digest loop is occurring and shuts down. Because new nested arrays/objects were not previously tracked by Angular, it always sees the return value as different from the previous. To fix these "unstable" filters, you must wrap the filter in a memoize
function. lodash
has a memoize
function and the latest version of lodash also includes a chunk
function, so we can create this filter very simply using npm
modules and compiling the script with browserify
or webpack
.
Remember: display only! Filter in the controller if you're using inputs!
Install lodash:
npm install lodash-node
Create the filter:
var chunk = require('lodash-node/modern/array/chunk');
var memoize = require('lodash-node/modern/function/memoize');
angular.module('myModule', [])
.filter('chunk', function() {
return memoize(chunk);
});
And here's a sample with this filter:
<div ng-repeat="row in ['a','b','c','d','e','f'] | chunk:3">
<div class="column" ng-repeat="item in row">
{{($parent.$index*row.length)+$index+1}}. {{item}}
</div>
</div>
Order items vertically
1 4
2 5
3 6
Regarding vertical columns (list top to bottom) rather than horizontal (left to right), the exact implementation depends on the desired semantics. Lists that divide up unevenly can be distributed different ways. Here's one way:
<div ng-repeat="row in columns">
<div class="column" ng-repeat="item in row">
{{item}}
</div>
</div>
var data = ['a','b','c','d','e','f','g'];
$scope.columns = columnize(data, 3);
function columnize(input, cols) {
var arr = [];
for(i = 0; i < input.length; i++) {
var colIdx = i % cols;
arr[colIdx] = arr[colIdx] || [];
arr[colIdx].push(input[i]);
}
return arr;
}
However, the most direct and just plainly simple way to get columns is to use CSS columns:
.columns {
columns: 3;
}
<div class="columns">
<div ng-repeat="item in ['a','b','c','d','e','f','g']">
{{item}}
</div>
</div>
Structure issues using ng-repeat with Bootstrap and AngularJS
Place a custom directive on your inner element together with a position counter that starts with 1 and a marker describing if it's the last element:
<div ng-repeat="item in items" class="col-xs-3">
<div class="item" the-directive position="{{ $index + 1 }}" last="{{ $last }}">
</div>
</div>
Create the directive with an isolated scope, bind scope properties to the values of the position and last attributes and attach a click event handler to the element:
app.directive('theDirective', function() {
return {
restrict: 'A',
scope: { position: '@', last: '@' },
link: function(scope, element, attrs) {
element.bind('click', function() {
...
});
}
};
});
In the click handler first create the collapse element or select it if it already exists:
var collapseQuery = document.querySelector('#collapse');
var collapse = collapseQuery === null ?
angular.element('<div id="collapse" class="col-xs-12"><div class="twelve"></div></div>') :
angular.element(collapseQuery);
Based on the position of the clicked element calculate the rounded number up to the nearest multiple of four:
var calculatedPosition = Math.ceil(scope.position / 4) * 4;
Get the element at the calculated position or the last one if the position is out of range:
var calculatedQuery = document.querySelector('[position="' + calculatedPosition + '"]');
if (calculatedQuery === null) calculatedQuery = document.querySelector('[last="true"]');;
var calculatedElement = angular.element(calculatedQuery);
Insert the collapse element after the element at the calculated position:
calculatedElement.parent().after(collapse);
Could use some optimizations, but hopefully puts you on the right track.
Demo with some extra visuals: http://plnkr.co/edit/fsC51vS7Ily3X3CVmxSZ?p=preview
Related Topics
Verify If a Point Is Land or Water in Google Maps
Disabling and Enabling a HTML Input Button
Accessing Redux State in an Action Creator
Filter Array of Objects with Another Array of Objects
Jquery Submit Form and Then Show Results in an Existing Div
How to Find the Width of a Div Using Vanilla JavaScript
How to Use JavaScript to Dynamically Change a Video's Source
How to Detect a Long Touch Pressure with JavaScript for Android and Iphone
Detecting That the Browser Has No Mouse and Is Touch-Only
Document.Getelementsbyclassname().Innerhtml Always Returns "Undefined"
How to Convert a Currency String to a Double with JavaScript
How to Get a Word Under Cursor Using JavaScript
Web Workers Without a Separate JavaScript File
Browser-Native JSON Support (Window.JSON)
Get Video Duration When Input a Video File
How to Load Images Dynamically (Or Lazily) When Users Scrolls Them into View
What HTML Tags Support the Onload/Onerror JavaScript Event Attributes