web-dev-qa-db-fra.com

comment diviser les données ng-repeat avec trois colonnes à l'aide de bootstrap

J'utilise ng-repeat avec mon code. J'ai un numéro de zone de texte basé sur ng-repeat. Je veux aligner la zone de texte avec trois colonnes.

c'est mon code

<div class="control-group" ng-repeat="oneExt in configAddr.ext">
    {{$index+1}}. 
    <input type="text" name="macAdr{{$index+1}}" 
           id="macAddress" ng-model="oneExt.newValue" value=""/>
</div>
118
kuppu

L'approche la plus fiable et techniquement correcte consiste à transformer les données dans le contrôleur. Voici une fonction de morceau simple et son utilisation.

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);

Votre vue ressemblerait alors à ceci:

<div class="row" ng-repeat="rows in chunkedData">
  <div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>

Si vous avez des entrées dans le ng-repeat, vous voudrez probablement déverrouiller/rejoindre les tableaux lorsque les données sont modifiées ou soumises. Voici comment cela se présenterait dans un $watch, de sorte que les données soient toujours disponibles dans le format d'origine fusionné:

$scope.$watch('chunkedData', function(val) {
  $scope.data = [].concat.apply([], val);
}, true); // deep watch

Beaucoup de gens préfèrent accomplir cela dans la vue avec un filtre. Ceci est possible, mais ne devrait être utilisé qu'à des fins d'affichage! Si vous ajoutez des entrées dans cette vue filtrée, cela posera des problèmes qui peuvent être résolus, mais ne sont ni jolis ni fiables.

Le problème avec ce filtre est qu’il renvoie à chaque fois de nouveaux tableaux imbriqués. Angular surveille la valeur de retour du filtre. Angular connaît la valeur lors de la première utilisation du filtre, puis l'exécute à nouveau pour s'assurer qu'elle est modifiée. Si les deux valeurs sont identiques, le cycle est terminé. Si ce n'est pas le cas, le filtre se déclenchera encore et encore jusqu'à ce qu'ils soient identiques, ou Angular réalisera qu'une boucle de digestion infinie se produit et s'arrête. Etant donné que les nouveaux tableaux/objets imbriqués n’avaient pas été suivis auparavant par Angular, la valeur renvoyée est toujours considérée comme différente de la valeur précédente. Pour corriger ces filtres "instables", vous devez envelopper le filtre dans une fonction memoize. lodash a une fonction memoize et la dernière version de lodash inclut également une fonction chunk. Nous pouvons donc créer ce filtre très simplement en utilisant les modules npm et en compilant le script avec browserify ou webpack.

N'oubliez pas: afficher uniquement! Filtre dans le contrôleur si vous utilisez des entrées!

Installez lodash:

npm install lodash-node

Créez le filtre:

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);
});

Et voici un exemple avec ce filtre:

<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>

Commandez les articles verticalement

1  4
2  5
3  6

En ce qui concerne les colonnes verticales (liste de haut en bas) et non horizontales (de gauche à droite), l'implémentation exacte dépend de la sémantique souhaitée. Les listes qui se divisent de manière inégale peuvent être réparties de différentes manières. Voici un moyen:

<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;
}

Cependant, le moyen le plus direct et le plus simple d'obtenir des colonnes est d'utiliser CSS columns :

.columns {
  columns: 3;
}
<div class="columns">
  <div ng-repeat="item in ['a','b','c','d','e','f','g']">
    {{item}}
  </div>
</div>
251
m59

Cette solution est très simple:

JSON:

[{id:"1",name:"testA"},{id:"2",name:"test B"},{id:"3",name:"test C"},{id:"4",name:"test D"},{id:"5",name:"test E"}]

HTML:

<div ng-controller="MyCtrl">
  <table>
    <tr ng-repeat="item in items" ng-switch on="$index % 3">
      <td ng-switch-when="0">
        {{items[$index].id}} {{items[$index].name}}
      </td>
      <td ng-switch-when="0">
        <span ng-show="items[$index+1]">
          {{items[$index+1].id}} {{items[$index+1].name}}
        </span>
      </td>
      <td ng-switch-when="0">
        <span ng-show="items[$index+2]">    
          {{items[$index+2].id}} {{items[$index+2].name}}
        </span>
      </td>
    </tr>
  </table>
</div>

DEMO in FIDDLE

 enter image description here

62
sgrillon

Une solution propre et adaptable qui ne nécessite pas de manipulation de données:

Le HTML:

<div class="control-group" class="label"
    ng-repeat="oneExt in configAddr.ext"
    ng-class="{'new-row': startNewRow($index, columnBreak) }">
    {{$index+1}}. 
    <input type="text" name="macAdr{{$index+1}}" 
       id="macAddress{{$index}}" ng-model="oneExt.newValue" />
</div>

Le CSS:

.label {
    float: left;
    text-align: left;
}
.new-row {
    clear: left;
}

Le JavaScript:

$scope.columnBreak = 3; //Max number of colunms
$scope.startNewRow = function (index, count) {
    return ((index) % count) === 0;
};

Il s'agit d'une solution simple pour le rendu dynamique des données en lignes et en colonnes sans qu'il soit nécessaire de manipuler le tableau de données que vous essayez d'afficher. De plus, si vous essayez de redimensionner la fenêtre du navigateur, vous pouvez voir que la grille s’adapte dynamiquement à la taille de l’écran/div.

(J'ai également ajouté un suffixe {{$ index}} à votre identifiant car ng-repeat essayera de créer plusieurs éléments avec le même identifiant si vous ne le faites pas.)

Un exemple de travail similaire

19
Cumulo Nimbus

la réponse de m59 est plutôt bonne. La seule chose que je n'aime pas à ce sujet, c'est qu'il utilise divs pour ce qui pourrait être des données pour une table.

Donc, en conjonction avec le filtre de m59 (réponse quelque part ci-dessus), voici comment l'afficher dans un tableau.

<table>
    <tr class="" ng-repeat="rows in foos | chunk:2">
        <td ng-repeat="item in rows">{{item}}</td>
    </tr>
</table>
15
Jarrod

Voici un moyen plus simple:

<table>
    <tr ng-repeat="item in lists" ng-hide="$index%2!==0">
        <td>
            <label>{{ lists[$index].name}}</label>
        </td>
        <td ng-hide="!lists[$index+1]">
            <label>{{ lists[$index+1].name}}</label>
        </td>
    </tr>
</table>

La réponse de Cumulo Nimbus m'est utile, mais je veux que cette grille soit entourée d'un div qui puisse afficher la barre de défilement lorsque la liste est trop longue. 

Pour ce faire, j'ai ajouté style="height:200px; overflow:auto" à un div autour de la table, ce qui l'a fait apparaître sous la forme d'une colonne unique.

Fonctionne maintenant pour une longueur de tableau de un.

7
geting

J'ai une fonction et l'ai stockée dans un service pour que je puisse l'utiliser dans mon application:

Un service:

app.service('SplitArrayService', function () {
return {
    SplitArray: function (array, columns) {
        if (array.length <= columns) {
            return [array];
        };

        var rowsNum = Math.ceil(array.length / columns);

        var rowsArray = new Array(rowsNum);

        for (var i = 0; i < rowsNum; i++) {
            var columnsArray = new Array(columns);
            for (j = 0; j < columns; j++) {
                var index = i * columns + j;

                if (index < array.length) {
                    columnsArray[j] = array[index];
                } else {
                    break;
                }
            }
            rowsArray[i] = columnsArray;
        }
        return rowsArray;
    }
}

});

Manette:

$scope.rows   = SplitArrayService.SplitArray($scope.images, 3); //im splitting an array of images into 3 columns

Balisage:

 <div class="col-sm-12" ng-repeat="row in imageRows">
     <div class="col-sm-4" ng-repeat="image in row">
         <img class="img-responsive" ng-src="{{image.src}}">
     </div>
 </div>
4
Chancho

Un truc simple avec CSS "clearfix" recommandé par Bootstrap:

<div class="row">
      <div ng-repeat-start="value in values" class="col-md-4">
        {{value}}
      </div>
      <div ng-repeat-end ng-if="$index % 3 == 0" class="clearfix"></div>
</div>

De nombreux avantages: Efficace, rapide, en utilisant les recommandations Boostrap, en évitant les problèmes de $ digest, et en ne modifiant pas le modèle angulaire.

2
Lastnico

Mon approche était un mélange de choses.

Mon objectif était d'avoir une grille qui s'adapte à la taille de l'écran. Je voulais 3 colonnes pour lg, 2 colonnes pour sm et md et 1 colonne pour xs.

Tout d'abord, j'ai créé la fonction de portée suivante, à l'aide du service angulaire $window:

$scope.findColNumberPerRow = function() {
    var nCols;
    var width = $window.innerWidth;

    if (width < 768) {
        // xs
        nCols = 1;
    }
    else if(width >= 768 && width < 1200) {
         // sm and md
         nCols = 2
    } else if (width >= 1200) {
        // lg
        nCols = 3;
    }
    return nCols;
};

Ensuite, j'ai utilisé le cours proposé par @Cumulo Nimbus:

.new-row {
    clear: left;
}

Dans la div contenant le ng-repeat, j'ai ajouté la directive resizable, comme expliqué dans cette page , afin que chaque fenêtre temporelle soit redimensionnée, le service angulaire $window soit mis à jour avec les nouvelles valeurs.

En fin de compte, dans la division répétée, j'ai: 

ng-repeat="user in users" ng-class="{'new-row': ($index % findColNumberPerRow() === 0) }"

S'il vous plaît, laissez-moi savoir les défauts de cette approche.

J'espère que cela peut être utile.

2
igorauad

Voici un moyen facile de le faire. C'est plus manuel et finit avec un balisage désordonné. Je ne le recommande pas, mais cela peut être utile dans certaines situations.

Voici un lien de violon http://jsfiddle.net/m0nk3y/9wcbpydq/

HTML:

<div ng-controller="myController">
    <div class="row">
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3)">
                {{ person.name }}
                </div>
            </div>
        </div>
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3):Math.ceil(data.length/3)">
                {{ person.name }}
                </div>
            </div>
        </div>
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3):Math.ceil(data.length/3)*2">
                {{ person.name }}
                </div>
            </div>
        </div>
    </div>
</div>

JS:

var app = angular.module('myApp', []);

app.controller('myController', function($scope) {

    $scope.Math = Math;
    $scope.data = [
        {"name":"Person A"},
        ...
    ];
});

Cette configuration nécessite l'utilisation de Math sur le balisage: /, vous devrez donc injecter Math en ajoutant cette ligne: $scope.Math = Math;

1
Gene Parcellano

Cet exemple produit un répéteur imbriqué dans lequel les données externes incluent un tableau interne que je voulais répertorier dans deux colonnes. Le concept serait valable pour trois colonnes ou plus.

Dans la colonne intérieure, je répète la "ligne" jusqu'à ce que la limite soit atteinte. La limite est déterminée en divisant la longueur du tableau d'éléments par le nombre de colonnes souhaité, dans ce cas par deux. La méthode de division repose sur le contrôleur et la longueur actuelle du tableau est transmise en tant que paramètre. La fonction JavaScript slice (0, array.length/columnCount) a ensuite appliqué la limite au répéteur.

Le deuxième répéteur de colonne est ensuite appelé et répète une tranche (array.length/columnCount, array.length) qui produit la seconde moitié du tableau de la deuxième colonne.

<div class="row" ng-repeat="GroupAccess in UsersController.SelectedUser.Access" ng-class="{even: $even, odd: $odd}">
    <div class="col-md-12 col-xs-12" style=" padding-left:15px;">
        <label style="margin-bottom:2px;"><input type="checkbox" ng-model="GroupAccess.isset" />{{GroupAccess.groupname}}</label>
    </div>
    <div class="row" style=" padding-left:15px;">
        <div class="col-md-1 col-xs-0"></div>
        <div class="col-md-3 col-xs-2">
            <div style="line-height:11px; margin-left:2px; margin-bottom:2px;" ng-repeat="Feature in GroupAccess.features.slice(0, state.DivideBy2(GroupAccess.features.length))">
                <span class="GrpFeature">{{Feature.featurename}}</span>
            </div>
        </div>
        <div class="col-md-3 col-xs-2">
            <div style="line-height:11px; margin-left:2px; margin-bottom:2px;" ng-repeat="Feature in GroupAccess.features.slice( state.DivideBy2(GroupAccess.features.length), GroupAccess.features.length )">
                <span class="GrpFeature">{{Feature.featurename}}</span>
            </div>
        </div>
        <div class="col-md-5 col-xs-8">
        </div>
    </div>
</div>


// called to format two columns
state.DivideBy2 = function(nValue) {
    return Math.ceil(nValue /2);
};

J'espère que cela aide à regarder la solution d'une autre manière. (PS c'est mon premier post ici! :-))

1
Harvey Mushman

Toutes ces réponses semblent excessivement élaborées. 

De loin, la méthode la plus simple consiste à configurer les divs d’entrée dans un bootstrap de colonne col-md-4, puis bootstrap le formatera automatiquement en 3 colonnes en raison de la nature à 12 colonnes de bootstrap:

<div class="col-md-12">
    <div class="control-group" ng-repeat="oneExt in configAddr.ext">
        <div class="col-md-4">
            <input type="text" name="macAdr{{$index}}"
                   id="macAddress" ng-model="oneExt.newValue" value="" />
        </div>
    </div>
</div>
1
cullimorer

Je répare sans .row

<div class="col col-33 left" ng-repeat="photo in photos">
   Content here...
</div>

et css

.left {
  float: left;
}
1
selahattinunlu
<div class="row">
  <div class="col-md-4" ng-repeat="remainder in [0,1,2]">
    <ul>
      <li ng-repeat="item in items" ng-if="$index % 3 == remainder">{{item}}</li>
    </ul>
  </div>
</div>
1
mrded

Une autre façon est de définir width:33.33%; float:left sur un div en wrapper comme ceci:

<div ng-repeat="right in rights" style="width: 33.33%;float: left;">
    <span style="width:60px;display:inline-block;text-align:right">{{$index}}</span>
    <input type="number" style="width:50px;display:inline-block" ">
</div>
0
geting

Se baser sur la très bonne réponse de m59. J'ai trouvé que les entrées de modèle seraient floues si elles étaient modifiées. Vous ne pouvez donc modifier qu'un seul caractère à la fois. Ceci est un nouveau pour les listes d'objets pour ceux qui en ont besoin:

EDITMise à jour pour gérer plusieurs filtres sur une page

app.filter('partition', function() {
  var cache = {}; // holds old arrays for difference repeat scopes
  var filter = function(newArr, size, scope) {
    var i,
      oldLength = 0,
      newLength = 0,
      arr = [],
      id = scope.$id,
      currentArr = cache[id];
    if (!newArr) return;

    if (currentArr) {
      for (i = 0; i < currentArr.length; i++) {
        oldLength += currentArr[i].length;
      }
    }
    if (newArr.length == oldLength) {
      return currentArr; // so we keep the old object and prevent rebuild (it blurs inputs)
    } else {
      for (i = 0; i < newArr.length; i += size) {
        arr.Push(newArr.slice(i, i + size));
      }
      cache[id] = arr;
      return arr;
    }
  };
  return filter;
}); 

Et ce serait l'usage:

<div ng-repeat="row in items | partition:3:this">
  <span class="span4">
    {{ $index }}
  </span>
</div>
0
JSous

Je me suis retrouvé dans un cas similaire, voulant générer des groupes d'affichage de 3 colonnes chacun. Cependant, bien que j'utilisais bootstrap, j'essayais de séparer ces groupes en différentes div. Je voulais aussi faire quelque chose de génériquement utile.

Je l'ai approché avec 2 ng-repeat comme ci-dessous:

<div ng-repeat="items in quotes" ng-if="!($index % 3)">
  <div ng-repeat="quote in quotes" ng-if="$index <= $parent.$index + 2 && $index >= $parent.$index">
                ... some content ...
  </div>
</div>

Cela rend très facile de passer à un nombre différent de colonnes et de les séparer en plusieurs div parents.

0
Matt Kindy

Lodash a une méthode de morceau intégrée maintenant que j'utilise personnellement: https://lodash.com/docs#chunk

Sur cette base, le code du contrôleur pourrait ressembler à ceci:

$scope.groupedUsers = _.chunk( $scope.users, 3 )

Voir le code:

<div class="row" ng-repeat="rows in groupedUsers">
  <div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>
0
sean2078

Je suis novice dans bootstrap et angularjs, mais cela pourrait aussi faire Array pour 4 éléments en tant que groupe, le résultat sera presque 3 colonnes . Cette astuce utilise le principe de la ligne de rupture bootstrap.

<div class="row">
 <div class="col-sm-4" data-ng-repeat="item in items">
  <div class="some-special-class">
   {{item.XX}}
  </div>
 </div>
</div>
0
Nathan