Structurer le JavaScript de son site avec ou sans Framework

On me demande souvent quelle est la structure JavaScript que j'utilise pour développer mes sites web. C'est une question à laquelle je ne sais jamais si un simple « jQuery » suffit ou si l'on s'attend à m'entendre répondre « Backbone », « Knockout », « AngularJS », « Aurelia » ou encore, « RequireJS » ou je ne sais quelle autre composant/librairie/framework JavaScript Front-end extraordinaire !

Au delà du fait que l'utilisation de ses ressources précédemment citées dépend du fait que l'on souhaite réaliser un site web ou un outil web ou une application web etc. je finis toujours par expliquer que j'utilise ma propre architecture JavaScript à travers toutes les différentes pages d'un site web et qu'au cas par cas, je charge les javascript dont j'ai besoin (parfois il est plus judicieux de charger de manière asynchrone un ckeditor sur l'unique page ou il est utile que de ce le trimbaler partout sur le site !).

Je vais donc vous livrer à travers cet article une architecture JavaScript pas à pas, page par page. Il existe également une approche plutôt orienté composant que j'aborde ici.

Afin de la comprendre pas à pas, j'utiliserai comme fil conducteur la réalisation d'un site vitrine. Ma façon de structurer le JavaScript client n'est pas absolue mais elle vous permettra de comprendre la logique derrière certains de mes sites dont vous trouverez les sources sur GitHub (comme pour ce blog) ou même la logique du moteur de site node.js : NodeAtlas.

Architecture JavaScript d'un site web

Pour commencer, j'estime qu'il y a quatre zones de JavaScript par page que je nomme Boot, Framework, Common et Specific. Aucune ligne JavaScript ne doit se trouver en dehors de ces quatre zones. Ces quatre zones, dans un site en production sont matérialisées par quatre (au maximum) fichiers.

En voici l'exemple sur une page d'accueil :

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>La page d'accueil</title>
        <!-- Meta / Feuilles de style -->
        <script type="text/javascript" src="javascript/boot.min.js"></script>
    </head>
    <body class="home">
        <!-- Contenu de la page -->
        <script type="text/javascript" src="javascript/framework.min.js"></script>
        <script type="text/javascript" src="javascript/home.min.js"></script>
        <script type="text/javascript" src="javascript/common.min.js"></script>
    </body>
</html>

La zone de Boot

Cette zone est facultative. Elle est la seule à ralentir le chargement de la page car les fichiers JavaScript de cette zone sont chargés et exécutés avant le rendu visuel final de la page, avant même que la balise body ne soit chargée. C'est pour cela que les bonnes pratiques demandent au maximum de charger les fichiers en pied de page et que ce qui sera chargé dans le head fasse un poids minimal en taille de fichier et en temps d'exécution JavaScript. Le type de script qui se trouve ici est celui qui va modifier la structure HTML au dessus du body à savoir sur l'élément html ou dans le head. Cela en vu d'éviter le phénomène de FOUC.

On retrouve donc ici, avant minification et rassemblement des JavaScript, des scripts comme « Modernizr » ou « HTMLShiv » qui vont impacter le rendu des balises avec les feuilles CSS. C'est ici qu'on laissera une ligne de code pour injecter par exemple une classe js dans la balise html afin d'avertir les futurs scripts ou les sélecteurs CSS que JavaScript est activé. Si aucun de ces scripts ne vous intéressent pour votre site, la zone de Boot peut donc être ignorée.

La zone de Framework

C'est ici que vont se trouver le chargement de toutes vos librairies/frameworks JavaScript utile à l'intégralité du site. Vous chargerez également des plugins pour vos librairies ou même vos propres fonctions JavaScript. Bref, ici c'est tout ce que vous utilisez Cross-site.

Cela peut ressembler à ça dans un environnement de développement :

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <!-- Titre / Meta / Feuilles de style -->
    </head>
    <body class="home">
        <!-- Contenu de la page -->
        <script type="text/javascript" src="javascript/jquery/date.js"></script>
        <script type="text/javascript" src="javascript/jquery/string.js"></script>
        <script type="text/javascript" src="javascript/jquery/jquery-2.1.0.js"></script>
        <script type="text/javascript" src="javascript/jquery/jquery.ba-hashchange.js"></script>
        <script type="text/javascript" src="javascript/jquery-ui/jquery-ui-1.10.4.js"></script>

        <script type="text/javascript" src="javascript/ckeditor/ckeditor.js"></script>
        <script type="text/javascript" src="javascript/ckeditor/config.js"></script>
        <script type="text/javascript" src="javascript/ckeditor/lang/fr.js"></script>
        <!-- Autres JavaScript -->
    </body>
</html>

La zone Common

C'est la première des deux zones que nous allons décortiquer plus bas dans cette article (les zones précédentes étant dans 95% des cas du code générique). Ici, tout va être dédié à l'interaction des scripts de la zone Framework avec le DOM du site. Cependant, ce fichier unique en version de développement (dans la majorité des cas) comme en version de production sera chargé sur toutes les pages du site comme les deux premières zones. Ne sera donc déclarées ici que les fonctions qui seront appelées sur plus d'une page.

La zone Specific

Cette dernière zone est facultative et spécifique à chaque page. Elle se matérialise par un fichier portant le nom du template de page associé à lui. Sur la page d'accueil ce fichier est donc home.js tandis que sur la page de contact il s'appelle contact-us.js. Il ne doit contenir que du code unique à la page courante.

Si le code de cette zone pour un template spécifique donne un fichier bien trop petit en poids pour qu'il soit négligeable à côté du common.js, il peut être intéressant de mettre quelques lignes normalement spécifique dans la partie commune. Nous verrons cela plus loin.

Site web de fil rouge

Pour commencer, nous allons imaginer un petit site web embarquant diverses zones HTML (des composants) qui auront chacune des fonctions JavaScript propres à travers différentes pages. Voici sans plus attendre les différents types de pages embarquant nos divers composants :

Liste des templates de page

La page d'accueil

  • Le composant Menu principal
  • Le composant Aperçu des pages
  • Le composant Aperçu des produits
  • Le composant Formulaire d'inscription à la newsletter
  • Le composant Retour en haut de page

Contenu de home.htm

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>La page d'accueil</title>
    </head>
    <body class="home">
        <nav class="main-nav"><!-- Zone Menu principal --></nav>
        <nav class="page-overview"><!-- Zone Aperçu des pages --></nav>
        <nav class="product-overview"><!-- Zone Aperçu des produits --></nav>
        <form class="newsletter-form"><!-- Zone Formulaire d'inscription à la newsletter --></form>
        <nav class="back-to-top"><!-- Zone Retour en haut de page --></nav>
    </body>
</html>

Les pages de produits

  • Le composant Menu principal
  • Le composant Aperçu des produits
  • Le composant Description d'un produit

Contenu de products/<name-of-product>.htm

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>La page de produit</title>
    </head>
    <body class="product">
        <nav class="main-nav"><!-- Zone Menu principal --></nav>
        <nav class="product-overview"><!-- Zone Aperçu des produits --></nav>
        <article class="product-content"><!-- Zone Description d'un produit --></article>
    </body>
</html>

Les page d'articles

  • Le composant Menu principal
  • Le composant Aperçu des articles
  • Le composant Contenu d'un article
  • Le composant Retour en haut de page

Contenu de articles/<title-of-article>.htm

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>La page d'article</title>
    </head>
    <body class="article">
        <nav class="main-nav"><!-- Zone Menu principal --></nav>
        <nav class="article-overview"><!-- Zone Aperçu des articles --></nav>
        <article class="article-content"><!-- Zone Contenu d'un article --></article>
        <nav class="back-to-top"><!-- Zone Retour en haut de page --></nav>
    </body>
</html>

La page F.A.Q.

  • Le composant Menu principal
  • Le composant Liste de Question/Réponse
  • Le composant Retour en haut de page

Contenu de faq.htm

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>La page de la faq</title>
    </head>
    <body class="faq">
        <nav class="main-nav"><!-- Zone Menu principal --></nav>
        <section class="questions-answers-list"><!-- Zone Liste de Question/Réponse --></section>
        <nav class="back-to-top"><!-- Zone Retour en haut de page --></nav>
    </body>
</html>

La page de contact

  • Le composant Menu principal
  • Le composant Formulaire d'envoi d'un email

Contenu de contact-us.htm

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>La page de contact</title>
    </head>
    <body class="contact-us">
        <nav class="main-nav"><!-- Zone Menu principal --></nav>
        <form class="contact-us-form"><!-- Zone Formulaire d'envoi d'un email --></form>
    </body>
</html>

Liste des composants

On peut donc repérer un certain nombre de composant sur lesquels nous allons appliquer des interactions JavaScript.

Le composant Menu principal

  • Celui-ci à des sous-menus qui ne s'affichent qu'avec des interactions par clique qui vont nécessiter du JavaScript. Il dispose également d'une barre de recherche pour trouver une page dans le site qui nécessitera également du JavaScript.

Le composant Aperçu des pages

Celui-ci sera un slider complet (flèches, swipe tactile, barre de progression) avec effets de transition qui va nécésiter du JavaScript.

Le composant Aperçu des produits

  • Celui-ci sera un slider affichant une liste d'éléments en ligne qui partent en overflow horizontal. Ceci va nécessiter du JavaScript.

Le composant Formulaire d'inscription à la newsletter

  • Celui-ci collectera les informations du formulaire pour ensuite les envoyer en AJAX si il passe les tests de validation. Ceci va nécessiter du JavaScript.

Le composant Retour en haut de page

  • Celui-ci renverra en haut de page avec un effet de défilement doux qui nécessitera du JavaScript.

Le composant Description d'un produit

  • Celui-ci sera découpé en trois parties, chacune cachées derrière des onglets. Passer d'une partie à l'autre nécessitera du JavaScript.

Le composant Aperçu des articles

  • Même comportement que pour Aperçu des produits.

Le composant Contenu d'un article

  • Pas de JavaScript prévu pour ce composant.

Le composant Liste de Question/Réponse

  • Celui-ci comportera une liste de question/réponse dont la réponse sera initialement cachée. Afficher les réponses nécessitera du JavaScript.

Le composant Formulaire d'envoi d'un email

  • Celui-ci collectera les informations du formulaire pour ensuite les envoyer en AJAX s'il passe les tests de validation. Ceci va nécessiter du JavaScript.

Aménager le fichier Common et les fichiers Specific

Le plus simple en terme de structure serait de faire un fichier à plat contenant l'intégralité de notre JavaScript. Il nous faudra de quoi parcourir le DOM aisément aussi j'utiliserai jQuery (ce qui pour la structure final n'est pas obligatoire, mais aidera à la compréhension tout au long de l'article).

Cette structure ne tiendra pas la route si le site doit grandir, mais c'est une base que je vais vous exposer pour vous expliquer la suite (qui elle tiendra la route !).

Contenu de javascript/common.js

var temp;
var timer;

function openMenu() { /* Code... */ }

function launchSearch() { /* Code... */ }

$(".main-nav" /* handler */).bind(/* event */, function () {
    openMenu();
    /* ... */
});

$(".main-nav" /* handler */).bind(/* event */, function () { 
    launchSearch();
    /* ... */
});

/* ... */


/* Aperçu des page */

function createSlider() { /* ... */ }
function changeStepSlider() { /* ... */ }
function startAutoSlider() { /* ... */ }

createSlider();
changeStepSlider();
startAutoSlider();

/* ... */

La plupart des variables se retrouvent dans le contexte d'exécution global ce qui peut faire des conflits. Si vous souhaitez enlever des fonctionnalités, il faut mettre sous commentaire. Segmenter votre code revient à lire entre les lignes etc. Bref ça manque d'une structure.

Un namespace commun et par page

Pour commencer, il faut absolument isoler les parties de code pour éviter les conflits, pouvoir utiliser du JavaScript en Strict Mode là où ça nous chante et pouvoir rapidement repérer du code en fonction de sur quelle page il doit intervenir.

Isolation et exécution

Pour isoler du code en JavaScript, il faut le faire tourner dans des contextes d'exécution différents. Les contextes d'exécution sont créer uniquement à l'intérieur des fonctions. Il va donc falloir créer des fonctions d'isolation mais tout de même organisées à travers un système de namespace de manière à ce que n'importe quelle fonction soit accessible n'importe où dans le code, et sans conflit.

Contenu de javascript/common.js (changement 1)

// Création d'un objet unique accessible de partout.
var website = website || {}; // Si « website » a déjà été crée dans un précédent fichier on le reprend. Sinon on le créer.

// Création d'un context d'execution isolant.
// Ce contexte d'exécution représente le code exécuté sur toutes les pages du site.
(function (publics) { // Tout ce qui devra être accessible en dehors de ce contexte d'execution sera accroché à « publics » et accessible via « website ».
    "use strict"; // Mode Strict pour ce contexte d'execution.

    var privates = {}; // Tout ce qui ne devra pas quitter le contexte d'execution sera accroché à « privates » et accessible uniquement via « privates ».

    /* Attributs / Fonctions privées */

    /* Attributs / Fonctions publiques */

    publics.init = function () {
        // Le code ici sera exécutable via « website.init() ».
    };
}(website));

// On exécute le code commun sur la page courante.
website.init();

À partir de notre site exemple, créons les fonctionnalités qui seront appelées sur le template home.

Structure publique / privée

Voici par exemple ce que pourrait contenir le fichier common.js pour notre site s'il n'était composé que de la page au template home et de rien d'autre.

Contenu de javascript/common.js (changement 2)

var website = website || {};

(function (publics) {
    "use strict";

    var privates = {};

    /* Menu principal */
    publics.openMenu = function () {
        // Permet d'ouvrir les sous menus.
        // ...
    };
    publics.search = function () {
        // Permet de lancer une recherche.
        // ...
    };

    /* Aperçu des pages */
    privates.initSlider = function () {
        // Transforme le DOM pour convenir au mécanirme.
        // Création de barre de progression, de flèches, etc...
        // ...
    };
    privates.changeSlider = function () {
        // Permet de changer de slide.
        // ...
    };
    privates.manageSliderArrows = function () {
        // Permet de gérer les flèches.
        // ...
    };
    privates.manageSliderProgressDot = function () {
        // Permet de gérer la barre d'état.
        // ...
    };
    privates.startSlider = function () {
        // Permet de faire tourner le slider.
        // ...
        privates.timerSlider = setInterval(/* fonction */, 5000);
        // ...
    };
    privates.stopSlider = function () {
        // Permet d'arrêter le slider.
        // ...
        clearInterval(privates.timerSlider);
        // ...
    };
    publics.slider = function () {
        // Permet de générer le slider.
        privates.initSlider();
        privates.manageSliderArrows();
        privates.manageSliderProgressDot();
        privates.startSlider();
    };

    /* Aperçu des produits */
    privates.initOverview = function ($component) {
        // Transforme le DOM pour convenir au mécanirme.
        // Création de flèches.
        // ...
    };
    privates.defilOverview = function ($component) {
        // Permet de faire défiler les items.
        // ...
    };
    privates.manageOverviewArrows = function ($component) {
        // Permet de gérer les flèches.
        // ...
    };

    // Ici on spécifie un paramètre « component » car la fonction « overview » est destinée 
    // à fonctionner avec plusieurs composants à savoir les composants « Aperçu des produits » 
    // et « Aperçu des articles » qui ont le même comportement. C'est à l'appel de la fonction
    // que l'on passera la bonne structure.

    publics.overview = function ($component) {
        // Permet de générer le slider.
        privates.initOverview($component);
        privates.defilOverview($component);
        privates.manageOverviewArrows($component);
    };

    /* Formulaire d'inscription à la newsletter */
    private.validateForm = function (/* formulaire */, callback) {
        // Permet de vérifier les erreurs du formulaire.
        // ...
        callback();
    };
    publics.newsletterRegistration = function () {
        // Enregistre une nouvelle personne à la newsletter.
        private.validateForm(/* formulaire */, function () {
            // ...
        });
    };

    /* Retour en haut de page */
    publics.backToTop = function () {
        // Retour en haut de page.
        // ...
    };

    // On initialise les fonction que l'on souhaite utiliser.
    publics.init = function () {
        website.openMenu();
        website.search();
        website.slider();
        website.overview();
        publics.newsletterRegistration();
        website.backToTop();
    };
}(website));

// On execute le code commun sur la page courante.
website.init();

Segmentation

Si votre partie common est trop grande, rien ne vous empêche de la scinder en plusieurs parties. Vous pouvez également décrocher une grosse partie de fonction commune à un mécanisme pour en faire un fichier volant plus facilement réutilisable tel quel dans un autre site.

Contenu de javascript/common.js (changement 3)

var website = website || {};

(function (publics) {
    var privates = {};

    /* Menu principal */
    publics.openMenu = function () { /* ... */ };
    publics.search = function () { /* ... */ };

    /* Aperçu des produits */
    privates.initOverview = function ($component) { /* ... */ };
    privates.defilOverview = function ($component) { /* ... */ };
    privates.manageOverviewArrows = function ($component) { /* ... */ };
    publics.overview = function ($component) { /* ... */ };

    /* Formulaire d'inscription à la newsletter */
    private.validateForm = function (/* formulaire */, callback) { /* ... */ };
    publics.newsletterRegistration = function () { /* ... */ };

    /* Retour en haut de page */
    publics.backToTop = function () { /* ... */ };
}(website));

Contenu de javascript/slider.js

var website = website || {};

(function (publics) {
    var privates = {};

    /* Aperçu des pages */
    privates.initSlider = function () { /* ... */ };
    privates.changeSlider = function () { /* ... */ };
    privates.manageSliderArrows = function () { /* ... */ };
    privates.manageSliderProgressDot = function () { /* ... */ };
    privates.startSlider = function () { /* ... */ };
    privates.stopSlider = function () { /* ... */ };
    publics.slider = function () { /* ... */ };
}(website));

Contenu de javascript/run.js

var website = website || {};

(function (publics) {
    // On initialise les fonction que l'on souhaite utiliser.
    publics.init = function () {
        website.openMenu();
        website.search();
        website.slider();
        website.overview();
        publics.newsletterRegistration();
        website.backToTop();
    }
}(website));

// On execute le code commun sur la page courante.
website.init();

Vous remarquerez par la même occasion que slider.js ressemble étrangement à une sorte de « plugin ». Effectivement il serait possible d'utiliser un plugin jQuery à la place de notre propre slider. Bien entendu dans ce cas il serrait rattaché à $ et non plus à website.

Common vs Specific

Dans des fichiers séparés

Nous allons continuer avec un common unique et mettre en place le fichier spécifique du template de page home.

Contenu de javascript/common.js (changement 4)

var website = website || {};

(function (publics) {
    "use strict";

    var privates = {};

    /* Menu principal */
    publics.openMenu = function () { /* ... */ };
    publics.search = function () { /* ... */ };


    // Aperçu des pages étant uniquement sur la home, il part dans le fichier spécifique home.js.


    /* Aperçu des produits */
    privates.initOverview = function ($component) { /* ... */ };
    privates.defilOverview = function ($component) { /* ... */ };
    privates.manageOverviewArrows = function ($component) { /* ... */ };
    publics.overview = function ($component) { /* ... */ };


    // Le validateur de formulaire va nous resservir pour la page de contact. Il est donc commun à tout le site.
    // Cependant le JavaScript spécifique à la newsletter est déporté dans « home.js ».
    // Pour que « validateForm » soit accessible maintenant en dehors de la zone commune, il passe publique.

    /* Validateur de formulaire */
    publics.validateForm = function (/* formulaire */, callback) { /* ... */ };


    /* Retour en haut de page */
    publics.backToTop = function () { /* ... */ };

    // On initialise les fonction que l'on souhaite utiliser.
    publics.init = function () { 
        website.openMenu();
        website.search();
    };
}(website));

// On execute le code commun sur la page courante.
website.init();

Pour le JavaScript spécifique de home, on va déporter l'ensemble des fonction dans website.home.

Contenu de javascript/home.js

var website = website || {};

(function (publics) {
    "use strict";

    var privates = {};

    /* Aperçu des pages */
    privates.initSlider = function () { /* ... */ };
    privates.changeSlider = function () { /* ... */ };
    privates.manageSliderArrows = function () { /* ... */ };
    privates.manageSliderProgressDot = function () { /* ... */ };
    privates.startSlider = function () { /* ... */ };
    privates.stopSlider = function () { /* ... */ };
    publics.slider = function () { /* ... */ };

    /* Formulaire d'inscription à la newsletter */
    publics.newsletterRegistration = function () {
        // Enregistre une nouvelle personne à la newsletter.
        website.validateForm(/* formulaire */, function () {
            // ...
        });
    };

    // On initialise les fonction que l'on souhaite utiliser.
    publics.init = function () {
        website.overview($(".product-overview"));
        website.backToTop();
        website.home.slider();
        website.home.newsletterRegistration();
    };

}(website.home = {})); // On accroche le JavaScript de la home dans un namespace (attribut) spécifique.

// On execute le code destiné à la page home.
website.home.init();

Et la page du template home serait alors :

Contenu de home.htm (Changement 1)

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>La page d'accueil</title>

        <!-- Script de Boot -->
    </head>
    <body class="home">
        <nav class="main-nav"><!-- Zone Menu principal --></nav>
        <nav class="page-overview"><!-- Zone Aperçu des pages --></nav>
        <nav class="product-overview"><!-- Zone Aperçu des produits --></nav>
        <form class="newsletter-form"><!-- Zone Formulaire d'inscription à la newsletter --></form>
        <nav class="back-to-top"><!-- Zone Retour en haut de page --></nav>

        <!-- Scripts de Framework -->
        <script type="text/javascript" src="javascript/home.js"></script>
        <script type="text/javascript" src="javascript/common.js"></script>
    </body>
</html>

Imaginons à présent que nous nous penchions sur le JavaScript spécifique du template des pages article dans le namespace de website.article. Nous remarquons vite qu'à part de l'initialisation, cette page ne possède pas de code spécifique pour sa page contrairement à home. Voyez plutôt :

Contenu de javascript/article.js

var website = website || {};

(function (publics) {
    "use strict";

    // On initialise les fonctions que l'on souhaite utiliser.
    publics.init = function () {
        website.overview($(".article-overview"));
        website.backToTop();
    };

}(website.article = {})); // On accroche le JavaScript de article dans un namespace (attribut) spécifique.

// On execute le code destiné à la page article.
website.article.init();

Ce template ressemblerait alors à ceci :

Contenu de articles/<title-of-article>.htm (Changement 1)

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>La page d'article</title>
        ><!-- Script de Boot --></nav>
    </head>
    <body class="article">
        <nav class="main-nav"><!-- Zone Menu principal --></nav>
        <nav class="article-overview"><!-- Zone Aperçu des articles --></nav>
        <article class="article-content"><!-- Zone Contenu d'un article --></article>
        <nav class="back-to-top"><!-- Zone Retour en haut de page --></nav>
        <!-- Scripts de Framework -->
        <script type="text/javascript" src="javascript/article.js"></script>
        <script type="text/javascript" src="javascript/common.js"></script>
    </body>
</html>

Dans le fichier common

Comme vu avec le template article, le code est tellement pauvre qu'il peut être placé dans common.js, ce qui donnerait quelque chose comme :

Contenu de javascript/common.js (changement 5)

var website = website || {};

// Partie Commune
(function (publics) {
    "use strict";

    var privates = {};

    /* ... */

    /* ... */

    publics.init = function () { /* ... */ };
}(website));

// Partie article tellement petite qu'il vaut mieux l'inclure dans common.
(function (publics) {
    "use strict";

    publics.init = function () { /* ... */ };
}(website.article = {}));

// Lancement des deux parties.
website.init();
website.article.init();

Cependant cela signifirait que le code website.article.init() serait exécuté sur toutes les pages alors qu'il n'aurait besoin que d'être exécuté sur article. C'est là que nous allons créer un lanceur de init() conditionnel au template de page qui appel les scripts !

Exécution conditionnelle au template

Quand le contenu des fichiers spécifiques est séparé et appelé uniquement sur le template de page auquel il est destiné, on est sur que l'exécution de code dans ce fichier ne se ferra pas sur d'autres pages (puisqu'il ne seront pas inclus). Hors ce n'est pas le cas, comme vous pouvez le constater, avec le code spécifique de la page article qui a été inclus dans le fichier de code commun (pour des questions de performance). Il suffit simplement de conditionner l'exécution de tous les init() des fichiers spécifiques avec la présence ou non d'une classe associée au template sur lequel il sont sensé s'exécuter. common.js deviendrait donc :

Contenu de javascript/common.js (changement 6)

var website = website || {};

// Partie Commune
(function (publics) {
    "use strict";

    var privates = {};

    /* ... */

    /* ... */

    publics.init = function () { /* ... */ };
}(website));


// Partie article tellement petite qu'il vaut mieux l'inclure dans common.
(function (publics) {
    "use strict";

    publics.init = function () { /* ... */ };
}(website.article = {}));


// On permet de placer les fichiers dans n'importe qu'elle ordre.
// Cette zone est l'unique zone ou l'on exécutera l'intégralité du JavaScript de tout le site.
$(function () {
    "use strict";

    // On vérifie quel est le template courant
    // en vérifiant le nom de la première classe du body.
    // Effectivement chaque body de chaque template de page à
    // sa classe qui lui est propre.

    var specific = $("body") // On réclame le body.
        .attr("class") // On lit ses classes.
        .split(" ")[0] // On récupère la première.
        .replace(/-/g, ""); // On la formate en une chaine identique au namespace !

    // Pour la page home, « var specific = "home" » et le namespace est « website.home ».
    // Pour la page article, « var specific = "article" » et le namespace est « website.article ».
    // Pour la page contact-us, « var specific = "contactus" » et le namespace est « website.contactus ».

    // On lance le code commun quoi qu'il arrive.
    website.init();

    // Ici on lance le code spécifique si un namespace correspondant existe.
    if (website[specific] !== undefined) {
        website[specific].init();
    }
});

Tous les autres init() disparaissent des autres fichiers et seul common.js est alors habilité à exécuter du code spécifique en fonction du template ou il est appelé.

Structure JavaScript du fil rouge

Nous y sommes : voici la structure JavaScript utilisée pour le site fil rouge de la partie Site web de fil rouge avec les spécificités évoquées dans la partie Amennager le fichier Common et les fichiers Specific. Voici ce que nous imaginons ; tous les scripts sont assez petits pour être dans common.js sauf home.js et contact-us.js qui seront des fichiers séparés.

Gardez à l'esprit que vous pouvez toujours très bien garder vos JavaScript dans des fichiers séparés peu importe leurs taille et de les associer dans les mêmes fichiers au besoin uniquement pour les versions minifiées.

common, product, faq, article, run

Contenu de javascript/common.js (changement final)

var website = website || {};

/***********/
/* website */
/***********/
(function (publics) {
    "use strict";

    var privates = {};

    /* Menu principal */
    publics.openMenu = function () { /* ... */ };
    publics.search = function () { /* ... */ };

    /* Aperçu des produits */
    privates.initOverview = function ($component) { /* ... */ };
    privates.defilOverview = function ($component) { /* ... */ };
    privates.manageOverviewArrows = function ($component) { /* ... */ };
    publics.overview = function ($component) { /* ... */ };

    /* Validateur de formulaire */
    publics.validateForm = function (/* formulaire */, callback) { /* ... */ };

    /* Retour en haut de page */
    publics.backToTop = function () { /* ... */ };

    publics.init = function () { 
        website.openMenu();
        website.search();
    };
}(website));



/*******************/
/* website.product */
/*******************/
(function (publics) {
    "use strict";

    /* Description d'un produit */
    publics.switchTab = function () { /* ... */ };

    publics.init = function () {
        website.overview($(".product-overview"));
        website.product.switchTab();
    };
}(website.product = {}));



/***************/
/* website.faq */
/***************/
(function (publics) {
    "use strict";

    /* Liste de Question/Réponse */
    publics.slideDown = function () { /* ... */ };

    publics.init = function () {
        website.faq.slideDown();
        website.backToTop();
    };
}(website.faq = {}));



/*******************/
/* website.article */
/*******************/
(function (publics) {
    "use strict";

    publics.init = function () {
        website.overview($(".article-overview"));
        website.backToTop();
    };
}(website.article = {}));



/*******/
/* Run */
/*******/
$(function () {
    "use strict";

    var specific = $("body").attr("class").split(" ")[0].replace(/-/g, "");

    website.init();

    if (website[specific] !== undefined) {
        website[specific].init();
    }
});

Contenu de products/<name-of-product>.htm (Changement final)

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>La page de produit</title>

        <!-- Script de Boot -->
    </head>
    <body class="product">
        <nav class="main-nav"><!-- Zone Menu principal --></nav>
        <nav class="product-overview"><!-- Zone Aperçu des produits --></nav>
        <article class="product-content"><!-- Zone Description d'un produit --></article>

        <!-- Scripts de Framework -->
        <script type="text/javascript" src="javascript/common.js"></script>
    </body>
</html>

Contenu de articles/<title-of-article>.htm (Changement final)

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>La page d'article</title>

        <!-- Script de Boot -->
    </head>
    <body class="article">
        <nav class="main-nav"><!-- Zone Menu principal --></nav>
        <nav class="article-overview"><!-- Zone Aperçu des articles --></nav>
        <article class="article-content"><!-- Zone Contenu d'un article --></article>
        <nav class="back-to-top"><!-- Zone Retour en haut de page --></nav>

        <!-- Scripts de Framework -->
        <script type="text/javascript" src="javascript/common.js"></script>
    </body>
</html>

Contenu de faq.htm (Changement final)

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>La page de la faq</title>

        <!-- Script de Boot -->
    </head>
    <body class="faq">
        <nav class="main-nav"><!-- Zone Menu principal --></nav>
        <section class="questions-answers-list"><!-- Zone Liste de Question/Réponse --></section>
        <nav class="back-to-top"><!-- Zone Retour en haut de page --></nav>

        <!-- Scripts de Framework -->
        <script type="text/javascript" src="javascript/common.js"></script>
    </body>
</html>

Home

Contenu de javascript/home.js

var website = website || {};

/****************/
/* website.home */
/****************/
(function (publics) {
    "use strict";

    var privates = {};

    /* Aperçu des pages */
    privates.initSlider = function () { /* ... */ };
    privates.changeSlider = function () { /* ... */ };
    privates.manageSliderArrows = function () { /* ... */ };
    privates.manageSliderProgressDot = function () { /* ... */ };
    privates.startSlider = function () { /* ... */ };
    privates.stopSlider = function () { /* ... */ };
    publics.slider = function () { /* ... */ };

    /* Formulaire d'inscription à la newsletter */
    publics.newsletterRegistration = function () {
        website.validateForm(/* formulaire */, function () { /* ... */ });
    };

    publics.init = function () {
        website.overview($(".product-overview"));
        website.backToTop();
        website.home.slider();
        website.home.newsletterRegistration();
    };
}(website.home = {}));

Contenu de home.htm (Changement final)

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>La page d'accueil</title>

        <!-- Script de Boot -->
    </head>
    <body class="home">
        <nav class="main-nav"><!-- Zone Menu principal --></nav>
        <nav class="page-overview"><!-- Zone Aperçu des pages --></nav>
        <nav class="product-overview"><!-- Zone Aperçu des produits --></nav>
        <form class="newsletter-form"><!-- Zone Formulaire d'inscription à la newsletter --></form>
        <nav class="back-to-top"><!-- Zone Retour en haut de page --></nav>

        <!-- Scripts de Framework -->
        <script type="text/javascript" src="javascript/home.js"></script>
        <script type="text/javascript" src="javascript/common.js"></script>
    </body>
</html>

contact-us

Contenu de javascript/contact-us.js

var website = website || {};

/*********************/
/* website.contactus */
/*********************/
(function (publics) {
    "use strict";

    /* Formulaire d'inscription à la newsletter */
    publics.sendAMessage = function () {
        website.validateForm(/* formulaire */, function () { /* ... */ });
    };

    publics.init = function () {
        website.backToTop();
        website.contactus.sendAMessage();
    };
}(website.contactus = {}));

Contenu de contact-us.htm (Changement final)

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>La page de contact</title>

        ><!-- Script de Boot --></nav>
    </head>
    <body class="contact-us">
        <nav class="main-nav"><!-- Zone Menu principal --></nav>
        <form class="contact-us-form"><!-- Zone Formulaire d'envoi d'un email --></form>

        <!-- Scripts de Framework -->
        <script type="text/javascript" src="javascript/contact-us.js"></script>
        <script type="text/javascript" src="javascript/common.js"></script>
    </body>
</html>