Créer une grille responsive à partir d'une liste avec angularjs

Les framework CSS nous ont apporté énormément d'outils pour créer du
contenu responsive. Le plus appreciable est le système de grille, qui
permet d'afficher des éléments en respectant un pattern bien définit. Je
vous laisse consulter la doc des framework pour en apprendre plus.

Ce système de grille est extrèmement pratique lorsque le nombre
d'éléments que l'on a à afficher est fixe. On peut, par exemple,
découper notre liste d'éléments en une liste de listes de N éléments, N
étant le nombre d'éléments à afficher par ligne.

En python on aurait par exemple ça côté serveur :

>>> def list_to_rows(l, elem_by_row):
        return [[l[l.index(x) + i] for i in xrange(0, elem_by_row) if l.index(x) + i < len(l)]
                for x in l[::elem_by_row]]

>>>> mylist = [1,2,3,4,5,6,7]
>>> list_to_rows(mylist, 2)
[[1, 2], [3, 4], [5, 6], [7]]

Ici on utilise le slicing python en définissant un step. Attention, ça
ne marchera pas avec un générateur !

On aurait plus qu'à parcourir notre liste de liste côté HTML pour
afficher les lignes successives, ici en Jinja par exemple.

<div>
    {% for row in mylist %}
    <div class="row">
        {% for elem in row %}
        <article>
            <div class="col s6">  <!-- On indique s6 parce qu'on veut afficher 2 élément par ligne (list_to_rows(mylist, 2)) -->
            {{ elem }}
            </div>
        </article>
        {% endfor %}
    </div>
    {% endfor %}
</div>

C'est simple et efficace. Mais si on veut pouvoir filtrer dynamiquement
sur cette liste côté client, ça ne fonctionnera plus. Enlevez un élément
à votre liste une fois le Jinja rendu et c'est le drâme.

On peut pour cela faire confiance à AngularJs. Côté serveur, on envoie
simplement notre liste. On ne s'en occupe pas d'avantage.

Côté client en revenche, on va pouvoir controller la façon donc elle
sera affichée :

<div ng-app="myapp" ng-controller="AppCtrl">
    <article ng-repeat="elmt in list">
        <span ng-switch="$index%3">
            <div class="row" ng-switch-when=0>
                {{ elmt.name }}
            </div>
		</span>
    </article>
</div>

Il faut quelques explications je pense. Tout d'abord, on met notre
affichage dans le scope de notre controller AppCtrl

Ensuite, on boucle sur la liste avec la directive ng-repeat. On ne va
s'interesser qu'aux éléments donc l'index est divisble par 3 avec la
directive ng-switch="outerIndex%3" et ng-switch-when=0.

Bon, c'est déjà pas mal. Mais nous on veut tout afficher.

On va donc modifier un peu notre template Angular pour ça.

<div ng-app="myapp" ng-controller="AppCtrl">
    <article ng-repeat="_ in list" ng-init="outerIndex=$index">
        <span ng-switch="outerIndex%3">
            <div class="row" ng-switch-when=0>
                <div ng-repeat="elmt in list.slice(outerIndex, outerIndex+3) track by $index">
                    <div ng-if="elmt">
                        <div class="col s4 bordered">
                            {{ elmt.name }}
                        </div>
                    </div>
                </div>
            </div>
        </span>
    </article>
</div>

On fait une deuxième boucle dans la première. Cette boucle, on la fait
sur un slice de la list originale, entre l'index de l'élément définit
par le switch, et 3 index plus loin. En gros, pour le 4ème élément de la
liste originale (index 3, divisible par 3 donc), on va faire un slice de
la liste de l'index 3 à 6. Ce qui donnera les 4ème, 5ème et 6ème
éléments de la liste.

Ces éléments, on veut qu'ils soient identifiés par leur $index dans la
seconde boucle (AngularJs pleurniche si la liste contient des
doublons...). On est donc obligé de stocker la variable d'indexation de
la première boucle dans une autre variable nommée ici outerIndex et
d'utiliser track by $index dans la seconde.

Ensuite, si l'élément existe, on affiche l'éléments dans un colonne de
largeur 4 (puisqu'on veut afficher 3 éléments par ligne dans cette
exemple).

Voilà, la première étape est faite. C'était le plus important. Relisez
cette partie, faites des tests pour bien comprendre tout ce qu'il se
passe.

Je vous parlais de filtrage, ou tout du moins, de modifications dans la
liste. L'avantage d'angular et de sa directive ng-repeat, c'est qu'il
va modifier le DOM si vous modifier la liste à partir duquel il est
créé. Si vous retirez 3 éléments à la liste, vous faites sauter une
ligne.

Du coup, pourquoi ne pas utiliser de filtre sur cette affichage ? Bah
pourquoi pas :

<div ng-app="myapp" ng-controller="AppCtrl">
    <article ng-repeat="_ in (fList = (list | filter: greaterThan('id', 2)))" ng-init="outerIndex=$index"> <span ng-switch="outerIndex%3">
        <div class="row" ng-switch-when=0>
            <div ng-repeat="elmt in fList.slice(outerIndex, outerIndex+3) track by $index">
                <div ng-if="elmt">
                    <div class="col s4 bordered">
                        {{ elmt.name }}
                    </div>
                </div>
            </div>
        </span>
    </article>
</div>

L'astuce ici repose dans la première directive ng-repeat : on stocke
le résultat du filtre dans une variable fList pour l'utiliser dans la
seconde boucle. Et c'est tout. Vous pouvez chaîner vos filtre, rendre le
nombre de ligne dynamique, ... Il y a déjà de quoi s'amuser un petit
moment !

Afficher les commentaires