Gérer sa cascade de grille et de composant avec Bootstrap

Afin d'apporter un cadre à l'équipe et parce que créer nos propres UI Kit n'est pas à l'ordre du jour, nous avons pris le partie d'utiliser Bootstrap 3 non sans chagrin.

L'idée finale derrière cette utilisation est de pouvoir créer facilement des composants et de pouvoir les déplacer n'importe ou dans un layout ou dans un composant lui-même.

Après avoir défendu l'idée que je n'avais pas besoin d'un quelconque Framework pour réaliser cela, je me suis atteler à Bootstrap.

Les remarques n'ont pas tardées à tomber sur la manière d'intégrer tout cela :

  • « Ça ne sert à rien de mettre un “.col-xs-12” ici, ça revient au même de ne rien mettre »,
  • « Pourquoi il n'y a pas un seul “.container” pour gérer toute la page comme dans la source de cette page »,
  • « Il ne faut pas mettre les “.container” dans les composants mais à l'extérieur sinon en mettant les composants dans des petites colonnes on va dépasser la largeur de la grille. Regarde la source de cette page »,
  • « T'as pas besoin de “.container” pour utiliser des “.row” », etc.

Le constat est clair, l'utilisation de Bootstrap pour réaliser des pages en imbriquant des composants est une chose, mais le faire correctement en est une autre !

Suivez le guide pour comprendre les guidelines que doivent suivre tout composant pour être « Drag and Droppable » n'importe où !

Repérer des équivalences

La première chose va être de lister ce que représente réellement chaque classe :

  • <div> => 100% de la largeur
  • <div class="container-fluid"> => 100% de la largeur + paddings positifs (+15)
  • <div class="container"> => largeur de grille + paddings positifs (+15)
  • <div class="row"> => 100% de la largeur + margins négatives (-15)
  • <div class="col-xs-12"> => 100% de la largeur + paddings positifs (+15)
  • <div class="col-xx-YY"> => ZZ% de la largeur + paddings positifs (+15)

On s'aperçoit alors très vite que :

  1. <div> et <div class="col-xs-12">, ce n'est pas pareil.
  2. .container-fluid et .col-xs-12 sont la même chose.
  3. .container et .col-xs-12 sont la même chose à la seule différence que .container à une largeur fixée.
  4. .row a forcément besoin d'un .container, c'est juste que si vous n'en voyez pas au dessus, c'est peut-être parce que celui-ci s'appelle .col-xs-12 (ou n'importe quelle déclinaison <div class="col-xx-YY">).
  5. .container-fluid/.container/.col-xs-12/.col-xx-YY sont l’opposé de .row (paddings positifs est annulé par margins négatifs).

On peut également aller plus loin en permettant à .container d'avoir une taille maximale fixée plutôt qu'une taille fixée.

Être composant orienté

Plusieurs Container

Si je sais d'avance ce qui va composer ma grille principale et ce qui va composer mes sous-grilles alors effectivement, je n'ai besoin que d'un seul .container pour englober ma page.

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Static</title>
    </head>
    <body>
        <div class="container">
            <div class="jumbotron">
                <h1>Title</h1>
            </div>
            <div class="panel">
                <h1>Carousel</h1>
            </div>
            <div class="row">
                <div class="col-sm-8">
                    <div class="row">
                        <div class="col-sm-4">
                            <div class="panel">
                                <h2>Exemple A</h2>
                                <p>Exemple A</p>
                                <p>Exemple A</p>
                            </div>
                        </div>
                        <div class="col-sm-4">
                            <div class="panel">
                                <h2>Exemple B</h2>
                                <p>Exemple B</p>
                                <p>Exemple B</p>
                            </div>
                        </div>
                        <div class="col-sm-4">
                            <div class="panel">
                                <h2>Exemple C</h2>
                                <p>Exemple C</p>
                                <p>Exemple C</p>
                            </div>
                        </div>
                        <div class="col-sm-4">
                            <div class="panel">
                                <h2>Exemple D</h2>
                                <p>Exemple D</p>
                                <p>Exemple D</p>
                            </div>
                        </div>
                        <div class="col-sm-4">
                            <div class="panel">
                                <h2>Exemple E</h2>
                                <p>Exemple E</p>
                                <p>Exemple E</p>
                            </div>
                        </div>
                        <div class="col-sm-4">
                            <div class="panel">
                                <h2>Exemple F</h2>
                                <p>Exemple F</p>
                                <p>Exemple F</p>
                            </div>
                        </div>
                        <div class="col-sm-4">
                            <div class="panel">
                                <h2>Exemple G</h2>
                                <p>Exemple G</p>
                                <p>Exemple G</p>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="col-sm-4">
                    <nav class="panel">
                        <ul>
                            <li><a href="javascript:;">Link A</a></li>
                            <li><a href="javascript:;">Link B</a></li>
                            <li><a href="javascript:;">Link C</a></li>
                            <li><a href="javascript:;">Link D</a></li>
                        </ul>
                    </nav>
                </div>
            </div>
        </div>
    </body>
</html>

Voir le rendu ici

Problème

  • Si je souhaite que mon carrousel prenne toute la largeur de la page, je suis bloqué dans mon .container. Résolvons ça dans le point suivant.

Solution

Pour permettre à mon carrousel de prendre toute la largeur, je vais donc éviter le .container global mais plutôt utiliser un .container ou un .container-fluid en fonction de mes besoins.

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Multiple container</title>
    </head>
    <body>
        <div class="container">
            <div class="jumbotron">
                <h1>Title</h1>
            </div>
        </div>
        <div class="container-fluid">
            <div class="panel">
                <h1>Carousel</h1>
            </div>
        </div>
        <div class="container">
            <div class="row">
                <div class="col-sm-8">
                    <div class="row">
                        <div class="col-sm-4">
                            <div class="panel">
                                <h2>Exemple A</h2>
                                <p>Exemple A</p>
                                <p>Exemple A</p>
                            </div>
                        </div>
                        <div class="col-sm-4">
                            <div class="panel">
                                <h2>Exemple B</h2>
                                <p>Exemple B</p>
                                <p>Exemple B</p>
                            </div>
                        </div>
                        <div class="col-sm-4">
                            <div class="component-text panel">
                                <h2>Exemple C</h2>
                                <p>Exemple C</p>
                                <p>Exemple C</p>
                            </div>
                        </div>
                        <div class="col-sm-4">
                            <div class="panel">
                                <h2>Exemple D</h2>
                                <p>Exemple D</p>
                                <p>Exemple D</p>
                            </div>
                        </div>
                        <div class="col-sm-4">
                            <div class="panel">
                                <h2>Exemple E</h2>
                                <p>Exemple E</p>
                                <p>Exemple E</p>
                            </div>
                        </div>
                        <div class="col-sm-4">
                            <div class="panel">
                                <h2>Exemple F</h2>
                                <p>Exemple F</p>
                                <p>Exemple F</p>
                            </div>
                        </div>
                        <div class="col-sm-4">
                            <div class="panel">
                                <h2>Exemple G</h2>
                                <p>Exemple G</p>
                                <p>Exemple G</p>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="col-sm-4">
                    <nav class="panel">
                        <ul>
                            <li><a href="javascript:;">Link A</a></li>
                            <li><a href="javascript:;">Link B</a></li>
                            <li><a href="javascript:;">Link C</a></li>
                            <li><a href="javascript:;">Link D</a></li>
                        </ul>
                    </nav>
                </div>
            </div>
        </div>
    </body>
</html>

Voir le rendu ici

Approche « Layouts / Components »

Pour permettre l'interversion de tous les composants dans toutes les zones de la page il va toujours falloir les plugger dans des zones d’atterrissage identique.

Comme .container-fluid/.container/.col-xs-12/.col-xx-YY sont la même chose au niveau des paddings positif, il faut découper les composants entre ces zones pour les rendre interchangeable.

Par conséquent un composant atterrira toujours dans une zones à paddings positif et donc ne contiendra jamais en premier lieu une balise enfant avec un .container-fluid/.container/.col-xs-12/.col-xx-YY.

Composant Jumbotron

<div class="component jumbotron">
    <div class="jumbotron">
        <h1>Title</h1>
    </div>
</div>

Composant Carousel

<div class="component carousel">
    <div class="panel">
        <h1>Carousel</h1>
    </div>
</div>

Composant Menu

<nav class="component menu">
    <div class="panel">
        <ul>
            <li><a href="javascript:;">Link A</a></li>
            <li><a href="javascript:;">Link B</a></li>
            <li><a href="javascript:;">Link C</a></li>
            <li><a href="javascript:;">Link D</a></li>
        </ul>
    </div>
</nav>

Composant Texte

<div class="component text">
    <div class="panel">
        <h2>Exemple A</h2>
        <p>Exemple A</p>
        <p>Exemple A</p>
    </div>
</div>

Cependant un composant peut commencer par une .row pour annuler le padding de sa zone d'arrivée, en vu d'injecter plusieurs .col-xx-YY.

Composant Texte et Image à Droite

<div class="component text right image">
    <div class="panel">
        <div class="row">
            <div class="col-xs-8">
                <h2>Exemple B</h2>
                <p>Exemple B</p>
            </div>
            <div class="col-xs-4">
                <img class="img-responsive" src="" alt="">
            </div>
        </div>
    </div>
</div>

Après le découpage précédent qui a donné naissance à des composants, où est passé la carcasse et les zones d'atterrissage ? C'est ce qui constitue le layout. Nos composants précédents peuvent être indépendamment placer dans ce layout :

Layout A

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Layout A</title>
    </head>
    <body>
        <div class="container">
            <!-- include component -->
        </div>
        <div class="container-fluid">
            <!-- include component -->
        </div>
        <div class="container">
            <div class="row">
                <div class="col-sm-8">
                    <div class="row">
                        <div class="col-sm-4">
                            <!-- include component -->
                        </div>
                        <div class="col-sm-4">
                            <!-- include component -->
                        </div>
                        <div class="col-sm-4">
                            <!-- include component -->
                        </div>
                        <div class="col-sm-4">
                            <!-- include component -->
                        </div>
                        <div class="col-sm-4">
                            <!-- include component -->
                        </div>
                        <div class="col-sm-4">
                            <!-- include component -->
                        </div>
                        <div class="col-sm-4">
                            <!-- include component -->
                        </div>
                    </div>
                </div>
                <div class="col-sm-4">
                    <!-- include component -->    
                </div>
            </div>
        </div>
    </body>
</html>

ou pourquoi pas celui là :

Layout B

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Layout B</title>
    </head>
    <body>
        <div class="container">
            <!-- include component -->
        </div>
        <div class="container">
            <div class="row">
                <div class="col-sm-6">
                    <!-- include component -->
                </div>
                <div class="col-sm-6">
                    <!-- include component -->    
                </div>
            </div>
        </div>
        <div class="container">
            <!-- include component -->
        </div>
    </body>
</html>

Voir le rendu B ici

ou n'importe quel autre layout !

Approche « Full Components »

Cette approche part du postulat que vous n'avez aucun layout et que tout n'est que composant de composant ! Dans cette approche le découpage va être sensiblement différent et c'est bien les composants eux-mêmes qui vont embarquer les .container en tout premier lieux.

À noter également que les zones d’atterrissage de composant ne devrons cette fois jamais avoir de padding et donc toujours être précédé d'un .row.

Il y a également 2 composants spéciaux qui sont le header et le footer qui contiennent les injections CSS et JavaScript ainsi que les meta données.

Grille Bootstrap non fixe mais à limite max

Avant tout, pour que cela fonctionne il va falloir transformer les tailles fixes des .containers de Bootstrap en tailles maximales.

Ajoutez soit cela à la suite de l'appel du fichier Bootstrap :

@media (min-width: 768px) {
  .container {
    width: auto;
    max-width: 750px;
  }
}
@media (min-width: 992px) {
  .container {
    width: auto;
    max-width: 970px;
  }
}
@media (min-width: 1200px) {
  .container {
    width: auto;
    max-width: 1170px;
  }
}

ou remplacez dans le fichier d'origine Bootstrap les 3 width précédentes en max-width.

Cela fera que .container deviendra :

  • <div class="container"> => largeur de grille + paddings positifs (+15)
  • <div class="container"> => 100% de la largeur + paddings positifs (+15) + limite max de grille

et pourra être inclus lui-même dans un .container sans poser de problème.

Composants

Revoyons le découpage de nos précédents composants :

Composant Jumbotron

<div class="component jumbotron">
    <div class="container">
        <div class="jumbotron">
            <h1>Title</h1>
        </div>   
    </div>
</div>

Composant Carousel

<div class="component carousel">
    <div class="container-fluid">
        <div class="panel">
            <h1>Carousel</h1>
        </div>
    </div>
</div>

Composant Menu

<nav class="component menu">
    <div class="container">
        <div class="panel">
            <ul>
                <li><a href="javascript:;">Link A</a></li>
                <li><a href="javascript:;">Link B</a></li>
                <li><a href="javascript:;">Link C</a></li>
                <li><a href="javascript:;">Link D</a></li>
            </ul>
        </div>
    </div>
</nav>

Composant Texte

<div class="component text">
    <div class="container">
        <div class="panel">
            <h2>Exemple A</h2>
            <p>Exemple A</p>
            <p>Exemple A</p>
        </div>
    </div>
</div>

Composant Texte et Image à Droite

<div class="component text right image">
    <div class="container">
        <div class="panel">
            <div class="row">
                <div class="col-xs-8">
                    <h2>Exemple B</h2>
                    <p>Exemple B</p>
                </div>
                <div class="col-xs-4">
                    <img class="img-responsive" src="" alt="">
                </div>
            </div>
        </div>
    </div>
</div>

Nous voyons qu'a présent se sont les composants eux-mêmes qui embarquent leur comportement maximum. Ajoutons maintenant un composant embarquant des composants.

Composant Liste à 3 Colonnes

<div class="component list three colums">
    <div class="container">
        <div class="row">
            <!-- start for -->
            <div class="col-sm-4">
                <div class="row">
                    <!-- include component[i] -->
                </div>
            </div>
            <!-- end for -->
        </div>
    </div>
</div>

Composant 2 Tiers 1 Tiers

<div class="component grid2-1">
    <div class="container">
        <div class="row">
            <div class="col-sm-8">
                <div class="row">
                    <!-- include component[0] -->
                </div>
            </div>
            <div class="col-sm-4">
                <div class="row">
                    <!-- include component[1] -->
                </div>
            </div>
        </div>
    </div>
</div>

Composant Liste à 2 Colonnes

<div class="component list two colums">
    <div class="container">
        <div class="row">
            <!-- start for -->
            <div class="col-sm-6">
                <div class="row">
                    <!-- include component[i] -->
                </div>
            </div>
            <!-- end for -->
        </div>
    </div>
</div>

Finissons avec nos 2 composants spéciaux.

Composant Header

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Multiple container</title>
    </head>
    <body>

Composant Footer

    </body>
</html>

Pages

Il ne nous reste plus, avec un unique moteur comme par exemple NodeAtlas, à appeler à chaque affichage de page avec une configuration .json par exemple.

Moteur Lamba

<!-- start for -->
<!-- include component[i] -->
<!-- end for -->

Équivalent Layout A

[
    { "header": [] },
    { "jumbotron": [] },
    { "carousel": [] },
    { "grid2-1": [
        { "list-three-columns": [
            { "text": [] },
            { "text": [] },
            { "text": [] },
            { "text": [] },
            { "text": [] },
            { "text": [] },
            { "text": [] }
        ] },
        { "menu": [] }
    ] },
    { "footer" }
]

Équivalent Layout B

[
    { "header": [] },
    { "jumbotron": [] },
    { "list-two-columns": [
        { "menu": [] },
        { "text-image-right": [] }
    ] },
    { "carousel": [] },
    { "footer" }
]

Voir le rendu B ici

Pour aller plus loin

Enfin qui dit découpe par composant dans le HTML dit découpe des CSS et JS dans le même sens. Vous pouvez par exemple enregistrer tout ce qui à trait au composant carousel dans component.carousel.js et component.carousel.css et l'injecter dans un bundle spécifique à la page avec les autres composants qui ne sont appelés qu'ici ou dans un bundle commun à toutes les pages. Des Framework comme NodeAtlas s'occupe pour vous comme des grands de vos bundles.

Vous pouvez également organiser votre découpe du JavaScript par composant en vous inspirant de ces exemples.

Et pour finir, même si je n'en ai pas parlé ici, il est évident que le contenu des composants doit être mappé à une source transversale de données indépendamment du composant en lui-même en fonction de la page ou du layout comme peut le faire NodeAtlas.