Séparations et mise en application des rôles du HTML / CSS / JavaScript

Cet article fait office de conventions d'architecture d'un site web orienté composant pour la partie frontale, peu importe la technologie d'implémentation finale, de manière à ce que le code soit valide, performant et maintenable par des Front-end Developers, des Back-end Developers ainsi que des Content Fillers (Pousseurs de Contenu). Ces techniques étant en constantes évolution, et les problématiques évoluant au fur et à mesure de mes itérations créatives, cet article est voué à se compléter et changer.

Présentation globale des rôles

HTML

Le HTML est le centre de tout. C'est lui qui doit présenter le contenu, qui est la clé de voûte de tout site web. Il doit être au service du contenu et non à celui du design (visuel). Le site web doit être valide et respecter les normes W3C en fonction de son DocType et être le plus cohérent à travers tout le site. Ce qui doit justifier son changement est un apport/une modification de contenu. Il est le lien entre le travail des Front-end Developers, des Back-end Developers ainsi que des Content Fillers et doit bouger le moins possible quand il est question de changement de design.

Il y a 3 types de fragment HTML :

  • Les Gabarits de page (ou Layouts, ou Templates ou encore Grids) : ils représentent la structure globale d'une page qui doit accueillir des Composants.
  • Les Composants (ou Modules, ou Views ou encore Items Template) : ils représentent une partie auto-suffisante à elle-même, traitant du même type de Contenu et l'organisant hiérarchiquement.
  • Les Contenus (ou Contents, ou Datas) : ils représentent les données a exposées au visiteur et pour lequel le site web existe. Elles sont sémantiquement parsemées de balise HTML et sont les zones qui proviennent généralement de fichier externe (HTML, Markdown, JSON, etc.) ou de base de donnée (SQL, NoSQL, etc.).

Exemple de Layout, Composants et Contenus

titre
menu (liste)
titre
paragraphes
bouton
...
...
...
paragraphe
partage (liste)

CSS

Le CSS est ce qui s'occupe d'habiller graphiquement le site web. Il est totalement invisible pour les Back-end Developers et Content Fillers. Ils ne doivent jamais mettre les mains dans des fichiers CSS, Less, Stylus, etc. Cependant, les classes sur lesquels ils s’appuient demande à être documentées.

Il y a 2 types d'approche en CSS :

  • CSS-Driven, avec l'utilisation des sélecteurs CSS ciblant le HTML. Elles s'appliquent en premier lieu sur les Composants mais également sur les Gabarits. Elles ne doivent jamais être utilisées dans du Contenu. Le CSS-Driven est décrit, par exemple, avec BEM. Une documentation des états ou variations possibles des Gabarits et Composants doit être fourni aux Back-end Developers.

  • HTML-Driven, avec l'utilisation de Patrons de classe (Class Pattern). Ils s'appliquent en premier lieu sur les Contenus (et possiblement sur les Layouts). Ils ne doivent jamais être utilisé dans des Composants. Le HTML-Driven est décrit par exemple avec OOCSS (Bootstrap, Semantic-UI, etc.) et une documentation de l'utilisation des Patrons de classe doit être fourni aux Content Fillers.

Exemple CSS-Driven

.user-profile (facultatif: .as-popup, .is-opened)
.user-profile--avatar
.user-profile--information

Défini dans la CSS
Le composant user-profile a un fond vert. Le sous-composant avatar est à gauche. Le sous-composant information est à droite. Avec la variation as-popup le composant est une popup caché. Avec l'état is-opened le composant est visible.

Exemple HTML-Driven

img .img-responsive
p .lead
a .btn.btn-default

Appliqué sur le HTML,
Le patron img-responsive rend l'image adaptable à son containeur. Le patron lead met en avant le texte. Le patron btn transforme le lien en bouton et le patron btn-default lui donne un skin par défaut.

JavaScript

Les JavaScript s'occupent, en partie, de rendre dynamique votre site web notamment en permettant les intéractions entre l'utilisateur et celui-ci. Les JavaScript doivent, tout comme les feuilles de style être séparés du code HTML faisant office de template. Ils se placent soit avant, soit après le contenu de <body> en fonction de leur nature.

  • Les Avants DOM (ou Bootstraper), sont les fichiers qui se placent en haut des pages HTML. Ils n'interagissent pas avec le DOM mais évitent le phénomène de FOUC. Ils sont peu nombreux et doivent être le plus léger possible car ils bloquent l'affichage de la page. On aura, par exemple, Modernizr ou encore RequireJS pour les librairies ou Angular ou Aurelia pour les frameworks destinés aux applications web qui injectent le DOM côté client. Placer un fichier ici avec les propriétés defer ou async revient à les placer dans la partie Après DOM.

  • Les Après DOM (ou DOM Ready) :

    • Les communs, ils sont chargés sur chaque page et sont :
      • Le fichier principaux du site (ou Common, ou Main, ou App). Ils représentent l'unique point de démarrage du code qui va tourner dans toute votre page courante et également le code utilisé sur les Class Patterns.
      • Les librairies externes (ou External), comme jQuery, qui sont utilisées à travers toutes les pages (possiblement amené depuis des CDN).
    • Les spécifiques, ils sont chargés uniquement sur des pages dédiées :
      • Le fichier principal de la page (ou Specific), habituellement rattachés à un Gabarit. Il est lancé par le Common en fonction du Gabarit trouvé.
      • Les Composants (ou Class ou Interface), habituellement rattachés à un Composant HTML. Ils sont lancés par le Specific en fonction des Composants trouvés (ou par le Common si l'on se passe des Specifics, notamment pour les petits sites).

Rôle et Utilisation du HTML

Les Gabarits

  • Les Gabarits de page (Layouts) incluent exclusivement des Composants. Ils ne doivent jamais inclure directement du Contenu. Un Gabarit est composé de plusieurs Zone d’atterrissage (PlaceHolder).

  • Un même Gabarit peut être utilisé avec quelques différences entre deux rendus de page donc, dans ce cas, une Variation de classe (Class Variation) avec with-* est utile. Nous verrons ça plus loin.

Voici des exemples de Gabarit :

Avec un Placeholder Unique

Qui pourra servir de Gabarit standard pour toutes pages simples.

<head>
    <!-- Common CSS file declaration -->
</head>
<body class="<name-of-layout>">
    <!-- Boot JS file (modernizr, require, etc.) -->
    <div class="layout">
        <!-- List of Components -->
    </div>
    <!-- Common JS file declaration -->
</body>

Note : Dans ce cas, class="<name-of-layout>" n'est pas requis puisqu'il n'y a qu'un seul PlaceHolder pour tout.

Avec de Multiples PlaceHolders

Qui servira pour les dispositions les plus complexes.

<head>
    <!-- Common CSS file declaration -->
</head>
<body class="<name-of-layout>">
    <!-- Boot JS file (modernizr, require, etc.) -->
    <div class="layout">
        <div class="area for-header">
            <!-- List of Components -->
        </div>
        <div class="area for-overview">
            <div class="part for-content">
                <!-- List of Components -->
            </div>
            <div class="part for-aside">
                <!-- List of Components -->
            </div>
        </div>
        <div class="area for-footer">
            <!-- List of Components -->
        </div>
    </div>
    <!-- Common JS file declaration -->
</body>

Note : Dans ce cas, class="<name-of-layout>" est obligatoire pour appliquer du CSS différemment sur chaque Zone (area/part) en fonction du Gabarit.

Placeholders Multiples avec Class Patterns

Comme par exemple la Grille de Bootstrap.

<head>
    <!-- Common CSS file declaration -->
</head>
<body class="<name-of-layout>">
    <!-- Boot JS file (modernizr, require, etc.) declaration -->
    <div class="container">
        <div class="row">
            <!-- List of Components -->
        </div>
        <div class="row">
            <div class="col-xs-12 col-sm-8">
                <!-- List of Components -->
            </div>
            <div class="col-xs-12 col-sm-4">
                <!-- List of Components -->
            </div>
        </div>
        <div class="row">
            <!-- List of Components -->
        </div>
    </div>
    <!-- Common JS file declaration -->
</body>

Note : Dans ce cas, class="<name-of-layout>" n'est pas obligatoire pour les CSS mais le sera pour le JavaScript qui doit pouvoir différencier (au besoin) un Gabarit d'un autre.

Les Composants

  • Les Composants —composés de plusieurs Sous-Partie (Subcomponent)— sont inclus dans les PlaceHolders et peuvent eux-même inclure des Composants (c'est le cas des composants de type « Composant de composant »).

  • Les Composants incluent des Contenus mais n'incluent jamais directement des Patrons de conception en dehors de ses zones de contenu.

  • Les Composants peuvent être incluent plus d'une fois dans de même Gabarit et également plus d'une fois dans le même PlaceHolder.

Les Variations de classe

  • Un Composant peut-être utilisé dans différent contexte d'affichage et avec différent comportement, donc, pour chaque alternative une Variation de classe as-* est utilisée.
  • Un Composant (ou une Sous-Partie) peuvent avoir différent états, donc, pour chaque état, une Variation de classe is-* ou has-* est utilisée.
  • Parce que un Composant peut-être trouvé plus d'une fois dans un même PlaceHolder, une Variation de classe for-* peut-être utilisée pour différencier les mêmes composants les uns des autres.

Nous reparlerons des trois précédents points plus loin.

Exemple de Composant

<!-- Specific CSS file declaration -->
<section class="<name-of-component> ui">
    <div class="<name-of-component>--inner">
        <h1><!-- Contents --></h1>
        <div class="<name-of-component>--image">
            <!-- Contents -->
        </div>
        <div class="<name-of-component>--text">
            <!-- Contents -->
        </div>
        <aside class="<name-of-component>--ads">
            <ul class="<name-of-component>--list">
                <li class="<name-of-component>--item">
                    <!-- Contents -->
                </li>
                <li class="<name-of-component>--item">
                    <!-- Contents -->
                </li>
                <li class="<name-of-component>--item">
                    <!-- Contents -->
                </li>
            </ul>
        </aside>
    </div>
</section>
<!-- Specific JS file declaration -->

Les -- indique une Sous-Partie du même Composant. Il est possible de créer autant de sous profondeur de Sous-Partie que souhaité.

Note : Un Composant doit avoir au moins deux balises HTML imbriquées pour le représenter afin de pouvoir être totalement habillé avec du CSS de toutes les manières possible. Voir ce CodePen (.<name-of-component>--inner est juste nommé .ui).

Boucler sur eux même

Un composant peut boucler sur lui même afin d’apparaître comme une liste ou de lier son contenu à une liste d'élément en base de donnée. Un <ul> est grandement recommandé dans ce cas puisque chaque item est censé être identique dans sa structure de Composant (mais pas forcément de Contenu).

Exemple de Composant

<!-- Specific CSS file declaration -->
<section class="<name-of-component> ui">
    <div class="<name-of-component>--inner">
        <ul class="<name-of-component>--multiple">
            <!-- start loop -->
            <li class="<name-of-component>--single">
                <h1><!-- Contents --></h1>
                <!-- All HTML Here -->
            </li>
            <!-- some items -->
            <li class="<name-of-component>--single">
                <h1><!-- Contents --></h1>
                <!-- All HTML Here -->
            </li>
            <!-- some items -->
            <li class="<name-of-component>--single">
                <h1><!-- Contents --></h1>
                <!-- All HTML Here -->
            </li>
            <!-- end loop -->
        </ul>
    </div>
</section>
<!-- Specific JS file declaration -->

Les Contenus

Les Contenus se trouvent uniquement dans les Composants, dans les zones de contenu. Seul les Patrons de conception (Class Pattern) doivent être utilisés dans ses parties et documentés pour être également à la disposition des Content Filler. C'est par exemple le cas de Bootstrap.

Il y a donc cinq zones de Contenu dans ce Composant

<section class="article ui">
    <div class="article--inner">
        <div class="article--text">
            <h2><!-- Content A Title --></h2>
            <!-- Contents A Text -->
            <div class="article--others">
                <ul class="article--others--list">
                    <li class="article--others--item">
                        <!-- Content B Image -->
                        <div class="article--others--text">
                            <h2><!-- Contents B Title --></h2>
                            <!-- Content B Text -->
                        </div>
                    </li>
                </ul>
            </div>
        </div>
    </div>
</section>

Les Contenus sont par exemple :

Content A Title

<span class="text-large reversed">I'am the<br>Main Title</span>

Content A Text

<h2 class="text-center">I'am a Subtitle</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<ul>
    <li>Lorem ipsum dolor sit amet</li>
    <li>Lorem ipsum dolor sit amet</li>
</ul>

Content B Image

<img src="source.png" alt="I'am the Image Description" class="img-responsive">

Content B Title

I'am an other Article

Content A Text

<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p><button class="btn btn-hugh">See more</button></p>

Variation de classe

Il y a beaucoup de prefixe de classe utilisés pour les variations de classe, en voici leur définition.

  • as-* : défini un comportement alternatif ou un rendu alternatif pour un Composant ou une Sous-Partie de composant. Exemple: class="<name-of-component> as-popup" peut signifier que le Composant standard <name-of-component> est affiché cette fois comme une popup. Ainsi en JavaScript, la classe .as-popup la possibilité d'afficher ou de masquer le Composant.

  • with-* : défini un comportement alternatif ou un rendu alternatif pour un Gabarit. Exemple: class="<name-of-layout> with-ads" peut signifier qu'un Background publicitaire peut-être affiché dans cette configuration.

  • is-* : défini un état alternatif sur un Composant ou une Sous-Partie de composant. Exemple: class="<name-of-component> is-opened" peut signifier que le Composant <name-of-component> est actuellement ouvert et visible. Ainsi en JavaScript, l'action du clique sur une zone précise peut enlever la classe .is-opened et par conséquent fermer le Composant.

  • has-* : défini un état alternatif sur un Composant ou une Sous-Partie de composant. Exemple: class="<name-of-component> has-more-one-item" peut signifier que le Composant <name-of-component> contient au moins deux éléments et qu'il doit s'afficher comme une liste.

  • for-* : défini un id sur un Composant parce que si un Composant est utilisé plus d'une fois dans le même Placeholder, il est important d'avoir une classe différente pour les différencier au besoin. Exemple: class="<name-of-component> for-article" peut signifier que le Composant est utilisé pour afficher du texte faisant office de contenu principale et class="<name-of-component> for-aside" peut signifier tout autre chose.

  • to-* : défini une étape d'attente en début de changement d'état pour manager une animation. Exemple: class="<name-of-component> to-open" peut signifier que <name-of-component> est en état d'ouverture depuis son état fermé vers son état ouvert et qu'il faut démarrer une augmentation de l'opacité. Quand l'animation est fini, le is-* peut-être mis. Un exemple ici.

  • from-* : défini une étape d'attente pour manager une animation du retour à l'état initial. Exemple: class="<name-of-component> from-open" peut signifier que le Composant <name-of-component> va être fermé et qu'il est temps de diminuer l'opacité de l'élément. Quand l'animation est finie, les classes is-* et from-* sont retirées. Un exemple ici.

Toutes les Variations de classe doivent être documentées pour être utilisable par les Back-end.

Lire et comprendre le nommage du DOM

  • Si vous voyez une classe sur body sans les autres prefix, C'est un nom de Gabarit.
  • Si vous voyez une classe immédiatement suivit de la classe .ui et sans les autres prefix, C'est un nom de composant.
  • Si vous voyez une classe finissant par *--multiple et *--single cela concerne un composant répétable.
  • Si vous voyez une classe contenant des --, c'est une Sous-Partie de composant.
  • Si vous voyez une classe avec le préfixe .as-, c'est un rendu et/ou un comportement alternatif sur un Composant par rapport à l'état standard.
  • Si vous voyez une classe avec le préfixe .with-, c'est un rendu et/ou un comportement alternatif sur un Gabarit par rapport à l'état standard.
  • Si vous voyez une classe avec le préfixe .is-, c'est la description d'un état de Composant ou Sous-Partie de composant alternatif.
  • Si vous voyez une classe avec le préfixe .has-, c'est la description d'un état de Composant ou Sous-Partie de composant alternatif.
  • Si vous voyez une classe avec le préfixe .for-, c'est pour un rendu ou comportement spécifique sur une unique version d'un Composant.
  • Si vous voyez une classe avec le préfixe .to-, c'est un état de transition vers un autre état.
  • Si vous voyez une classe avec le préfixe .from-, c'est un état de transition de retour à l'état initial.
  • Si vous ne voyez aucune des classes listées avant, c'est un Patron de conception.

Choisir la Sémantique de vos balise

Il n'y au plus qu'un seul <header> et <footer> sous le <body>. Le <h1> principale ce trouve également dans cette partie. Sous le <body> il y a une hiérarchie de <h1-h6> avec un unique <h1> jusqu'à ce que vous trouviez une balise :

  • <section>,
  • <article>,
  • <nav> ou
  • <aside>.

Chacune de ces balises arrête la propagation de la hierarchie <h1-h6> des balises parente et commence leur propre hiérarchie de <h1-h6> (avec seulement un <h1>). Chacune de ces balises peuvent aussi avoir leur propre <header> et <footer>.

De manière à pouvoir rendre déplaçable et réutilisable chaque Composant, nous avons deux manières d'organiser notre structure.

Sémantique sur le Gabarit

Ici on place <section>, <article>, <nav> et <aside> sur le Gabarit dans les aires réservées des PlaceHolder. Mais dans ce cas, seul des Composants commençant par des <div> peuvent être insérés.

Layout

<body class="products">
    <div class="layout">
        <section class="area for-overview">
            <!-- One Component Here -->
        </section>
    </div>
</body>

Component

<div class="product ui">
    <div class="product--inner">
        <header><!-- Content Title --></header>
        <!-- Content Text -->
    </div>
</div>

Contents

<h1>A product</h1>
<h2>Subtitle</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>

Sémantique dans le Composant

Ici on place <section>, <article>, <nav> et <aside> sur le Composant. Dans ce cas, un Composant ne peuvent pas être utilisé dans un autre type que celui pour lequel il est destiné.

Layout

<body class="products">
    <div class="layout">
        <div class="area for-overview">
            <!-- Multiple Component Here -->
        </div>
    </div>
</body>

Component

<section class="product ui">
    <div class="product--inner">
        <header><!-- Content Title --></header>
        <!-- Content Text -->
    </div>
</section>

Contents

<h1>A product</h1>
<h2>Subtitle</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>

Gommer les inconvénient des deux approches

Vous trouverez dans cet article des pistes et une solution exemple pour embarquer le type du Composant sur lui-même tout en pouvant en changer en fonction de vos envies.

Comment créer des Emails correctes ?

Pour permettre à vos emails HTML d'être lisible dans la plupard des clients mail, il est nécessaire de ne les réaliser qu'avec du HTML4 et d'utiliser des attributs HTML (voir des balises) à la place des attributs style.

N'utiliser pas non plus de <div> brut utilisez une cascade de <table>, <tr>, <td> etc.

Par exemple, n'utilisez pas <div style="text-align: center"> mais <center>. Pour créer une version HTML5 de vos emails HTML4 pour le web, vous pouvez les transformer automatiquement avec NodeAtlas par exemple.

Rôle et Utilisation du CSS

La Spécificité des Sélecteurs

C'est l'aspect le plus fondamental du fonctionnement CSS et c'est celui qui pause le plus problème. Parceque les règles CSS sont comme des Variables Globales Mutables en JavaScript, car une propriété :

  • Peut être redéfini.
  • Il y a un Niveau de Spécifité pour chaque sélecteur (voir les règles). En cas de spécificité égale sur deux règles s'appliquant sur le même élément : c'est la place des lignes qui compte.
  • L'Ordre de la CSS a une importance.

Exemple

Sélecteur Spécificité Description
* 0.0.0.0 tout
li 0.0.0.1 tag
ul li 0.0.0.2 tag + tag
li::first-letter 0.0.0.2 tag + pseudo-element
ul li div 0.0.0.3 tag+ tag + tag
.teal 0.0.1.0 class .
li:nth-child(1) 0.0.1.1 pseudo-class . tag
ul + *[rel=up] li 0.0.1.2 all + attribut . tag + tag
li.red.level 0.0.2.1 class + class . tag
ul.blue ol li.blue 0.0.2.3 class + class . tag + tag + tag
li.orange.orange.level 0.0.3.1 class + class + class . tag
#my-id 0.1.0.0 id . .
#my-id.teal 0.1.1.0 id . class
#my-id.teal 1.1.0.0 inline . id .

Voir des exemples Live

Et si le mot clé !important est ajouté à la fin de n'importe quelle valeur de propriété elle passe devant toute autre. Soyez prudent en l'utilisant (voici un cas d'utilisation justifié).

Vous pouvez voir la spécificiter des sélecteurs grace à cet outil.

Deux approches d'utilisation du CSS

Il y a deux façons d'appliquer des styles CSS sur du HTML. La manière CSS-Driven ou piloté par la feuille de style ou la manière HTML-Driven ou piloté par les Classes dans les balises HTML.

CSS-Driven

Nous avons une structure HTML figée. Nous nommons tous les éléments de notre structure

<section class="<name-of-component> ui">
    <div class="<name-of-component>--inner">
        <div class="<name-of-component>--text">
        </div>
    </div>
</section>

pour pouvoir y faire référence depuis la feuille CSS. C'est une approche type BEM.

.<name-of-component>--inner {
    font-weight: bold;
}

Nous venons d'habiller une partie HTML.

HTML-Driven

Nous avons une feuille CSS figée. Nous nommons nos règles de manière à créer des Patrons de conception (Class Pattern)

.bold {
    font-weight: bold;
}

de manière à pouvoir les appliquer comme on le souhaite sur une structure HTML éditable.

<p class="bold">Lorem ipsum</p>

Nous avons créer le Patron de conception .bold.

Comment appliquer chaque approche ?

Le plus important est de ne jamais enfermer les composants HTML dans des « Pattern emprisonnant » car vous ne pourrez pas l'habiller avec du sens dans un autre contexte.

D'un autre côté, parceque les Content Fillers ne doivent jamais toucher au fichier CSS, il est important de leur créer des Patrons de conception pour le contenu. Sachez que via des balises <style> ou attribut style il est tout à fait acceptable qu'un Content Filler altère de l'affichage dans une zone de contenu.

Pour finir, pour les Gabarits de page, les deux approches sont envisageable, cela dépend se si votre système gère plus facilement le changement de Gabarit (Layout) ou le changement de CSS si vous souhaitez utiliser différentes grilles.

Less/Stylus vous aide à utiliser Bootstrap

Votre meilleur ami pour la maintenance des feuilles CSS et pour garder votre HTML le plus clair possible est Less ou encore Stylus.

Il est possible de manager les deux avec NodeAtlas avec ses simples webconfig.json

{
    "enableLess": true,
    "assetsRelativePath": "/"
}

ou

{
    "enableStylus": true,
    "assetsRelativePath": "/"
}

placés à la racine de votre dossier de site. Et avec la commande CLI nodeatlas --browse.

Exemple de Less pour les Gabarits, Composants et Contenus

Dans le but d'habiller le HTML suivant :

<section class="follow-me ui <!-- Variation Class -->">
    <div class="follow-me--inner">
        <div class="follow-me--image">
            <!-- Content Image -->
        </div>
        <div class="follow-me--text">
            <!-- Content Text -->
        </div>
    </div>
</section>

vous pourrez utiliser l'implémentation Less suivante dans le fichier component.follow-me.less :

/* Import Bootstrap Class Pattern. */
@import (reference) 'bootstrap/bootstrap.less';

/* Import your own Class Pattern. */
@import (reference) 'common.less';

/* Name your Component. */
@componentName: e('.follow-me');

/* Outer Wrapper Design. */
@{componentName} {

    /* Initial State. */
    display: none;

    /* Opened State. */
    &.is-opened {
        display: block;
    }

    /* Clickable State. */
    &.is-closable {
        cursor: pointer;
    }

    /* ... */

    /* Overload of Class Pattern for this Component. */
    .arrowed {
        /* ... */
        span {
            /* ... */
        }
    }
    .gap {
        /* ... */
    }

    /* Inner Wrapper Design. */
    &--inner {
        /* ... */
    }

    /* Subcomponent Design. */

    &--image {
        .make-md-col(4);
        /* ... */
    }

    &--text {
        .make-md-col(8);
        /* ... */
    }

    &--close,
    /* Custom Component Class Pattern usable into Content part. */
    .close {
        /* ... */
    }

    /* Variation of this Component. */
    &.as-v1 {
        @{componentName}--inner {
            background-color: #da5daf;
        }
        @{componentName}--text {
            border: 1px solid #85214d;
        }
        /* ... */
    }
    &.as-v2 {
        @{componentName}--inner {
            background-color: #80a837;
        }
        @{componentName}--text {
            border: 1px solid #3e6618;
        }
        /* ... */
    }
    &.as-v3 {
        @{componentName}--inner {
            background-color: #ed9364;
        }
        @{componentName}--text {
            border: 1px solid #d6401c;
        }
        /* ... */
    }
}

et avec ce fichier common.less par exemple

/* Same design on all browser. */
@import 'helpers/normalize.less';

/* Use icones. */
@import 'font-awesome/font-awesome-regular.less';

/* Bootstrap Lib. */
@import (reference) 'bootstrap/bootstrap.less';

/* Font Awesome Lib. */
@import (reference) 'font-awesome/font-awesome.less';

/* Use more Font. */
@import (css) 'https://fonts.googleapis.com/css?family=Muli';

/* Just import Bootstrap Class Pattern you needed. */
.text-center {
    .text-center;
}
.text-left {
    .text-left;
}
.text-right {
    .text-right;
}
.img-responsive {
    .img-responsive;
    display: inline-block;
}

/* Your own Class Pattern for Content. */
.arrowed {
    /* ... */
    span {
        /* ... */
    }
}
.gap {
    /* ... */
}

/* Your Layout Implementation */
.products {
    &.for-overview {
        .make-sm-col(8);
    }
    &.for-aside {
        .make-sm-col(4);
    }
}

/* ... */

Inclusion par Référence

Pour Less, si vous importez en utilisant @import (reference) au lieu de simplement utiliser @import vous génèrerez dans le fichier de sortie uniquement le fragment de CSS souhaité en provenance de votre liste de Patron de conception. Par exemple avec la librairie Bootsrtap, vous pouvez importer tous les Patrons col-XX-YY par exemple. Cela peut grandement alléger vos feuilles CSS.

La spécificité d'un sélecteur ne doit pas être trop lourde

Sélectionner directement la cible

Vous devez accéder à un élément par sa classe :

@{componentName} {
    &--others--item {
        /* Apply style */
    }
}
// CSS
// .article--others--item { /* Apply style */ }

Sélectionner un seul élément sans classe par sélecteur

Il est possible qu'il y ai des éléments sans classes dans les Composants ou Sous-composants. Il devrait être permi de ne faire référence qu'à un, et un seul élément.

Ceci est autorisé

@{componentName} {
    &--others--item {
        a {
            /* Apply style */
        }
        span {
            /* Apply style */
        }
    }
}
// CSS
// .article--others--item a { /* Apply style */ }
// .article--others--item span { /* Apply style */ }

mais ceci n'est PAS autorisé...

@{componentName} {
    &--others--item {
        a {
            /* Apply style */
            span {
                /* Apply style */
            }
        }
    }
}
// CSS
// .article--others--item a { /* Apply style */ }
// .article--others--item a span { /* Apply style */ } // Too specified selector.

C'est mal. Vous devez ajouter dans ce cas des classes sur vos éléments pour directement pouvoir intéragir avec eux.

@{componentName} {
    &--others--item--span {
        /* Apply style */
    }
}
// CSS
// .article--others--item--span { /* Apply style */ }

ou (un élément sans classe autorisé)

@{componentName} {
    &--others--item {
        span {
            /* Apply style */
        }
    }
}
// CSS
// .article--others--item--link span { /* Apply style */ }

Ajouter des Variations de classe pour changer le design

Vous pouvez améliorer la précision de la sélection en utilisant une Variation de classe.

Sur un Composant

@{componentName} {
    &.is-opened {
        /* Apply style */
    }
}
// CSS
// .article.is-opened { /* Apply style */ }

ou un Sous-Composant

.is-opened {
    @{componentName}--others--item--link  {
        span {
            /* Apply style */
        }
    }
}
// CSS
// .is-opened .article--others--item--link span { /* Apply style */ }

Note : Le code ci-dessous produit la même sortie que celui au dessus.

.is-opened {
    @{componentName} {
        &--others
            &--item
                &--link {
                    span {
                        /* Apply style */
                    }
                }
            }
        }
    }
}
// CSS
// .is-opened .article--others--item--link span { /* Apply style */ }

La règle est la suivante : écrire en une seule ligne les composantes du sélecteur si il n'y a aucun sous-élément voisin utilisant le même chemin à référencer, sinon créer un nouveau sous bloque comme ci-dessous

.is-opened {
    @{componentName}--others--item {
        &--image  {
            /* A new sibling Subcomponent */
        }
        &--link  {
            span {
                /* Apply style */
            }
        }
    }
}
// CSS
// .is-opened .article--others--item--image { /* A new sibling Subcomponent */ }
// .is-opened .article--others--item--link span { /* Apply style */ }

Rôle et Utilisation du JS

Dans le but de produire du code performant et maintenable, vous devriez passer à travers un outil comme JSLint et obtenir une sortie sans erreur. Si vous utilisez Sublim Text, vous pouvez installer SublimeLinter-contrib-jslint pour éviter toute mauvaise pratique.

Règles et Patterns

Éviter la surchauffe de cerveau (Brain Overload)

Pour commencer, observer autant que possible les règles suivantes :

  • Un fichier ne doit pas dépasser les 1000 lignes (commentaires inclus).
  • Une fonction ne doit pas dépasser les 100 lignes (commentaires inclus).
  • La complexité d'une fonction ne doit pas dépasser 10 Tester la Complexité.
  • Il ne doit pas y avoir plus de 3 niveaux de if/else/swich/try/etc. sans qu'on ai recours à une fonction.

Déclarer tout ce que vous utilisez dans la page, et son usage

Dans tous les fichiers JavaScript Client, toutes les variables doivent être explicitement déclarée, même si vous savez que l'exécuteur JavaScript connait cette valeur par défaut ou qu'un autre fichier l'a déjà défini. Il est important de savoir ce qui va être utilisé à travers le fichier et de permettre le plus possible de pouvoir vous passer d'un ordre précis dans l'exécution des fichiers.

Exemple

/* jslint browser: true */
/* global $ */

// Define your usage of global object.
var website = website || {},

    // Define your usage of `jQuery window` shortcut for this file.
    $window = $window || $(window);

// Define your usage of `website.component` namespace.
website.components = website.components || {};

Observez que seulement un var est déclaré au sommet de chaque fonction, pas plus. Si vous définissez vos variables dans le Champ lexical globale (Global Scope), attachez les à window ou définissez les dans le var global de votre fichier.

Le Namespace global website va contenir tout le code JavaScript de votre site web. N'hésitez pas à utiliser app ou encore application si c'est une application web et non pas une simple page.

Utiliser des Closure pour garder le champ lexical global propre

Le code ci-dessus est défini dans le Champ lexical global. Si vous écrivez du code dans un fichier, n'écrivez jamais directement dans le Champ lexical global. Il y a deux manière de garder le Champ lexical global propre.

Avec une Closure Statique Anonyme

Si le code n'a pas besoin d'être instancié, faites juste comme ci-après. C'est le cas des controllers comme common.js.

// Create a closure to scope all variables or function invisible from global scope.
(function () {
    "use strict";

    var iAmAPrivateVariable_ForPublicExpositon = "",
        iAmAPrivateVariable = "";

    // No accessible in global scope.
    iAmAPrivateVariable = "i am only accessible in this closure";

    // Accessible to global scope with `website.iAmAPublicVariable`.
    website.iAmAPublicVariable = iAmAPrivateVariable_ForPublicExpositon;
}());

ou exposé votre variable publique quand vous créer la Closure (ici website est publique et publics n'est accessible que dans la closure).

// Pass an alias of `website` named `publics` only in this closure.
(function (publics) {
    "use strict";

    var privates = {};

    // Private Example
    privates.addClassIsLoaded = function (view) {
        view.classList.add("is-loaded");
    }

    // Public Example
    publics.loadComponents = function () {
        /* See this part later */
    };

}(website));
Avec new Class

S'il est important que le code puisse être instancié en fonction d'un contexte donné, créer une Classe est le mieux. Pour ne pas polluer le Champ lexical global, attachez la Class à un Namespace comme website.components.

window.website = window.website || {};
website.components = website.components || {};

// Create a Class for define JavaScript behavior of `<div class="lightbox"></div>`.
website.components.Lightbox = function callee(selector) {
    "use strict";

    // Create private namespace for instanciable function (publics)
    // and inner function (privates).
    var publics = this,
        statics = callee.prototype,
        privates = {};

    // Count number of instance for Lightbox.
    statics.nbrOfInstance = (statics.nbrOfInstance) ? statics.nbrOfInstance + 1 : 1;
    callee.nbrOfInstance = statics.nbrOfInstance;

    // Define the selector by default for the HTML view of this Class.
    publics.selector = selector || ".information";

    // Define all stuff to manage lightbox.
    privates.openLightbox = function () { /* ... */ };
    privates.moveLightbox = function () { /* ... */ };
    privates.closeLightbox = function () { /* ... */ };

    // Define a function to launch all mandatory behavior when a HTML view is parsed.
    publics.init = function () {

        // Set all function you want use to initialization.
        privates.openLightbox();
        privates.moveLightbox();
        privates.closeLightbox();
    };
};
website.components.Lightbox.nbrOfInstance = 0;

Et initialisez tous les <div class="lightbox"></div>

var Lightbox = website.components.Lightbox;
(new Lightbox()).init();

ou initialisez tous les <div class="popup"></div>

var Lightbox = website.components.Lightbox;
(new Lightbox(".popup")).init();

ou initialisez <div class="ads"></div>

var Lightbox = website.components.Lightbox,
    lightbox = new Lightbox();
lightbox.selector = "ads";
lightbox.init();

ou comptez le nombre d'instance avec

var Lightbox = website.components.Lightbox,
    lightbox = new Lightbox();
Lightbox.nbrOfInstance; // 4
lightbox.nbrOfInstance; // 4

ou créer une fonction tierce...

// This code is a focus on `website.loadComponents()` defined above.
publics.loadComponents = function () {
    var i, currentComponent, currentViews;

    // Loop on all components
    for (i in website.components) {

        // Never forgot to use `hasOwnProperty` to not display other thing that personal object key.
        if (website.components.hasOwnProperty(i)) {

            // Work on current Component.
            currentComponent = new website.components[i]();

            // Obtain all `<div class="<currentComponent.selector>"></div>`
            currentViews = document.querySelectorAll(currentComponent.selector + ":not(.is-opened)");
            if (currentViews.length > 0) {

                // Initialize Component.
                allComponentsInstance.push(currentComponent.init());

                // Tag this component as already initialize.
                [].forEach.call(currentViews, addClassIsLoaded); // see before for "addClassIsLoaded" definition.
            }
        }
    }
};

...pour charger tous les composants que vous avez créer de cette manière :

website.loadComponents();

Et si plutard, vous souhaitez ajouter <div class="lightbox"></div> au DOM par XMLHttpRequest (Ajax), vous serez capable d'appliquer le JavaScript suivant :

var Lightbox = website.components.Lightbox;
(new Lightbox(".information:not(.is-loaded)")).init();

et de charger dès lors tous les composants (non déjà chargé) avec :

website.loadComponents();

Use Strict

Le Champ lexical global (Global Scope) est une zone sauvage ou toutes les variables vont entrer en conflit ainsi la moindre chose décraré dans un Champ lexical dédié (Closure) est une bonne chose. C'est le but des fonctions anonyme comme :

(function () {
    "use strict";

    // ...
}())

mais pourquoi utiliser le « strict mode » avec "use strict"; ?

Les exécuteurs JavaScript acceptent toute sorte de manière d'écrire du JavaScript ainsi que du code qui n'a pas été standardisé, du code déprécié ou du code non optimisé dans le but de supporter du code JavaScript ancien. Pour ne pas permettre à l'exécuteur de faire tourner du vieux code étrange et de lever une Exception pour aider au développement, il faut ajouter "use strict"; et toutes les variables dans le champ lexical courant (le contexte d'exécution courant) ainsi que les contextes enfants vont lever une Exception si le code ne respecte pas les standards. C'est une bonne pratique de nos jours, ne l'oubliez pas pour ne pas exécuter du code démoniaque !

Note : vous pouvez aussi l'utiliser dans une fonction nommée ou à instancier ainsi que dans le champ lexical global si vous le souhaitez.

Plus de règle JavaScript

Pour respecter toutes les règles, suivez ces règles : Quality JavaScript Rules

Changement d'État des Éléments

Si vous souhaitez changer l'état d'un élément, vous pouvez le faire en suivant les étapes suivantes :

Standard

  • Créer une classe .is-* avec tout le changement de design associé dans votre fichier CSS (ou Less/Stylus).
  • Ajouter la classe is-* ou retirez là de l'élément en question pour changer son état.
  • Vérifier l'existance de is-* pour savoir dans quel état est actuellement l'élément.

Avec Animation

  • Utiliser avant la classe is-* une classe to-* pour démarrer l'animation de transition entre deux états.
  • Utiliser après la classe is-* une classe from-* s'il existe une transition de retour à l'état précédent.
  • Utiliser les propriétés CSS3 keyframe, animation, transform, etc. pour créer des animations sur des éléments dont les paramètres sont statics. Utiliser une librairie d'animation si les valeurs sont variables en fonction du contexte.

Exemple

Trouvez dans cet exemple comment permettre un changement d'état animé : Change Element State.

Validation de Formulaire

Pour valider un formulaire il n'est pas nécéssaire d'écrire une seule ligne de JavaScript dans la majorité des cas. Utilisez juste ses trois fichiers :

Note: ce couple de fichier fonctionne ensemble mais si vous utilisez la dernière version pour l'un, en cas de problème, utilisez la dernière version pour les deux autres.

Vous trouverez ici un cas d'usage complet de l'utilisation de chaque champ : Exemple d'utilisation avec chaque champ

Et si un champ est injecté plus tard dans le DOM, après l'inclusion du fichier unobstrusive, utilisez $.validator.unobtrusive.parse(<selectorForm>); pour faire fonctionner les nouveaux formulaires/champs avec unobstrusive.

Si une validation est plus complexe, vous pouvez trouver la documentation de jQuery Validate ici ou vous tourner vers un Data Binder comme Angular.

Étendre une Classe

Quand vous souhaitez étendre une classe depuis une autre (héritage), vous devez en plus d'étendre la classe, bien penser a étendre les fonctions de son prototype. Si une classe doit hériter de plusieurs classe, alors ce que vous souhaitez est une interface (voir plus loin).

Quand votre classe est un model pour une vue HTML, ajoutez la dans website.components mais si c'est purement une classe de model de donnée, ajoutez là dans website.models.

Essayons donc d'étendre cette classe par celle-ci :

Class Person

/* jslint browser: true */

window.website = window.website || {};
website.models = website.models || {};

website.models.Person = function (firstname, lastname) {
    this.firstname = firstname || "No firstname";
    this.lastname = lastname || "No lastname";
};
website.models.Person.prototype.age = "No age";

Class User

/* jslint browser: true */

window.website = window.website || {};
website.models = website.models || {};

website.models.User = function (id, password, firstname, lastname) {
    website.models.Person.call(this, firstname, lastname);

    this.id = id;
    this.password = password;
};
website.models.User.prototype = Object.create(website.models.Person.prototype);
website.models.User.prototype.constructor = website.models.User;

Controller

/* jslint browser: true */

window.website = window.website || {};
website.models = website.models || {};

(function () {
    var user = new website.models.User("Haeresis", "myPassword");
    user.age = 18;

    user.id; // "Haeresis"
    user.password; // "myPassword"
    user.firstname; // "No firstname"
    user.lastname; // "No lastname"
    user.age; // 18
}());

Implementer un Mixin

Quand une fonction est utilisable par plus d'une fonction anonyme, n'hésitez pas à la placer dans le namespace website comme loadComponents dans l'exemple précédent.

Mais quand une fonction est partagée par plus d'une classe, vous pouvez créer cette fonction dans des interfaces ou mixins et l'implémenter dans les différentes classes. Un mixin est comme une classe mais son usage est uniquement d'étendre une classe, jamais d'être instanciée elle-même.

Ainsi nous pouvons implémenter ces deux mixins dans cette classe.

Interface Age

/* jslint browser: true */

var website = window.website || {};
website.mixins = website.mixins || {};

website.mixins.Age = function () {
    this.age = "18";
};

Interface Firstname

/* jslint browser: true */

window.website = window.website || {};
website.mixins = website.mixins || {};

website.mixins.Firstname = function () {
    this.firstname = "Bruno";
};

Class Person

/* jslint browser: true */

window.website = window.website || {};
website.models = website.models || {};
website.mixins = website.mixins || {};

website.classes.Person = function () {
    website.mixins.Firstname.call(this);
    website.mixins.Age.call(this);

    var publics = this;

    publics.lastname = "Lesieur";
};

Note : si un même nom de propriété ou de fonction existe entre deux interfaces, c'est la dernière interface chargée qui remporte le conflit !

Controller

window.website = window.website || {};
website.models = website.models || {};

(function () {
    var person = new website.models.Person();

    person.lastname; // "Lesieur"
    person.firstname; // "Bruno"
    person.age; // 18
}());

Travailler sans Namespace

Si nous attachons tout dans le namespace website c'est pour ne pas entrer en conflit avec des classes déjà existantes. Le pattern pour éviter les conflits est d'exposer une méthode noConflict() dans la classe directement définie dans le scope global.

Voir plus bas:

(function (factory) {
    var initialClass = window.MyClass,
        api = window.MyClass = factory;
    api.noConflict = function () {
        window.MyClass = initialClass;
        return api;
    };
}(function () {
    // Standard definition of class here.
}));

et si la classe globale MyClass existe déjà, utilisez un nouveau nom de classe avec la méthode noConflict() pour restaurer l'objet MyClass d'origine.

var MyClass2 = MyClass.noConflict();

Classe pour environnement Multiple

Dans le but de permettre à la classe MyClass de fonctionner aussi bien avec Vanilla JS, un loader de module JS ou Node.js, créez une factory comme ci-après.

(function (root, factory) {
    var root.MyClass = factory;
    if (typeof define === "function" && define.amd) {
        define(function () {
            return factory;
        });
    }
    if (typeof module === "object" && module.exports) {
        module.exports = factory;
    }
}(this, function () {
    // Normal definition of class here.
}));

Autres Comportements

Getter, Setter et Chaînage

Un pattern de chaînage (populaire en jQuery par exemple), consiste à fournir une référence aux propriétés privées d'une classe instanciée depuis l’extérieur. La méthode utilisée renvoi la valeur souhaitée en mode Getter et permet de la modifier en mode Setter. En mode Setter, l'objet alors retourné n'est pas la valeur mais l'objet instancié pour permettre « le chaînage ».

Class

window.website = window.website || {};
website.models = website.models || {};

website.models.Person = function (firstname, lastname) {
    var publics = this,
        privates = {};

    privates.firstname = (firstname) ? publics.firstname(firstname) : "No firstname";
    privates.lastname = (lastname) ? publics.lastname(lastname) : "No lastname";

    publics.firstname = function (firstname) {
        if (typeof firstname === "undefined") {
            return privates.firstname;
        } else {
            privates.firstname = firstname;
            return publics;
        }
    };

    publics.lastname = function (lastname) {
        if (typeof lastname === "undefined") {
            return privates.lastname;
        } else {
            privates.lastname = lastname;
            return publics;
        }
    };
};

Controller

window.website = window.website || {};
website.models = website.models || {};

(function () {
    var person = (new website.models.Person())
        .firstname("Bruno")
        .lastname("Lesieur");

    console.log(person.firstname()); // "Bruno"
    console.log(person.lastname()); // "Lesieur"
}());

Forcer le contexte d'exécution

Vous pouvez forcer une classe a ne jamais permettre un autre contexte d'exécution que le sien d'être utilisé. Cela vous permet de ne pas utiliser le mot clé new depuis un appel extérieur.

Class

var website = window.website || {};
website.models = website.models || {};

website.models.Person = function callee(firstname, lastname) {
    if (!(this instanceof callee)) {
        return new callee();
    }

    this.firstname = firstname || "No firstname";
    this.lastname = lastname || "No lastname";
};

Controller

var website = window.website || {};
website.models = website.models || {};

(function () {
    (new website.models.Person() instanceof website.models.Person); // true
    (website.models.Person() instanceof website.models.Person); // true
}());

Gérer les erreurs

Il y a 3 manières de proprement lever les erreurs, pour un code synchrone, un code asynchrone ou un événement : voir par ici.

Documentation

De manière à documenter votre code, utilisez le JSDoc npm CLI. Vous trouverez tout ce qu'il vous faut pour la documentation ici.

Par exemple la page « Home » possède le composant « Hero » qui est documenter pour le CSS et pour le JS.

Première parution : le 08 Octobre 2015 à 17h48