L'APPLICATION WEB MONOPAGE

Introduction

L'objectif ici est de réfléchir à la structure d'une application web face à un problème donné. Le code proposé en solution est donc simplifié autant que possible. On ne se préoccupe guère, ici, de la présentation.

Une application web monopage, on dit aussi application One-Page, single-page application ou SPA, est une application web accessible via une page web unique. Le but est d'éviter le chargement d'une nouvelle page à chaque action demandée, et de rendre ainsi l'application plus rapide et plus agréable d'utilisation. Deux méthodes existent pour ce faire :

Une page HTML comporte généralement des liens. Ces liens peuvent appartenir à un menu à onglets. Les deux méthodes ci-dessus seront donc illustrées avec cet exemple.

Première méthode

Problématique

Une solution classique pour réaliser un site internet avec un menu à onglets consiste à créer autant de pages qu'il y a d'onglets. Chaque onglet comporte un lien pointant vers la page correspondante.

Cette solution présente l'avantage que l'option JavaScript du navigateur n'ait pas besoin d'être activée. Le site fonctionne sans JavaScript.

Mais elle présente aussi un inconvénient. Dans le cas d'une connexion à internet lente, il faut patienter quelques secondes pendant le chargement d'une nouvelle page. Ce temps d'attente est peu agréable dans le cadre d'une application web.

Exemple de solution

L'idée est donc de regrouper toutes les pages en une seule. Toute l'application web est téléchargée en une seule fois.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script>
      function voir_page(num) {
        for (var i=1; i<5; i++) {
          document.getElementById("page"+i).style.display="none";
          document.getElementById("onglet"+i).className="deselect";
	}
	document.getElementById("page"+num).style.display="block";
	document.getElementById("onglet"+num).className="select";
      }
    </script>
    <style>
      #page1,#page2,#page3,#page4 { display: none; }
      .select { color: red; }
      .deselect { color: blue; }
    </style>
  </head>
  <body onload="voir_page(1);">
    <noscript>Activez l'option JavaScript du navigateur.</noscript>
    <div>
      <span id="onglet1" onclick="voir_page('1')" class="select">Page 1</span>
      <span id="onglet2" onclick="voir_page('2')" class="deselect">Page 2</span>
      <span id="onglet3" onclick="voir_page('3')" class="deselect">Page 3</span>
      <span id="onglet4" onclick="voir_page('4')" class="deselect">Page 4</span>
    </div>
    <div id="page1">C'est la page 1!</div>
    <div id="page2">C'est la page 2!</div>
    <div id="page3">C'est la page 3!</div>
    <div id="page4">C'est la page 4!</div>
  </body>
</html>

Variante

L'objectif ici est de faciliter le travail de différentes personnes sur un même site partageant un menu commun. Chaque personne est chargée de produire une page particulière du site.

Pour cela, on utilise la balise iframe, permettant d'insérer une page HTML dans une autre page HTML.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script>
      function voir_page(num) {
        for (var i=1; i<5; i++) {
          document.getElementById("page"+i).style.display="none";
          document.getElementById("onglet"+i).className="deselect";
        }
        document.getElementById("page"+num).style.display="block";
        document.getElementById("onglet"+num).className="select";
      }
    </script>
    <style>
      #page1,#page2,#page3,#page4 { display: none; width: 100%; }
      .select { color: red; }
      .deselect { color: blue; }
    </style>
  </head>
  <body onload="voir_page(1);">
    <noscript>Activez l'option JavaScript du navigateur.</noscript>
    <div>
      <span id="onglet1" onclick="voir_page('1')" class="select">Page 1</span>
      <span id="onglet2" onclick="voir_page('2')" class="deselect">Page 2</span>
      <span id="onglet3" onclick="voir_page('3')" class="deselect">Page 3</span>
      <span id="onglet4" onclick="voir_page('4')" class="deselect">Page 4</span>
    </div>
    <iframe id="page1" src="page1.html">
      Votre navigateur ne supporte pas l'élément iframe.
    </iframe>
    <iframe id="page2" src="page2.html"></iframe>
    <iframe id="page3" src="page3.html"></iframe>
    <iframe id="page4" src="page4.html"></iframe>
  </body>
</html>

Autre variante

L'objectif, maintenant, est double :

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script>
      function voir_page(num) {
        for (var i=0;i<document.getElementsByTagName("a").length;i++) {
          document.getElementsByTagName("a")[i].style.color="blue";
          document.getElementsByTagName("div")[i].style.display="none";
        }
        document.getElementsByTagName("a")[num].style.color="red";
        document.getElementsByTagName("div")[num].style.display="block";
      }
      onload=function() {
        for (var i=0;i<document.getElementsByTagName("a").length;i++) {
          document.getElementsByTagName("a")[i].indice=i;
          document.getElementsByTagName("a")[i].onclick=function() {
            voir_page(this.indice);
          };
        }
        document.getElementsByTagName("nav")[0].style.display="block";
        voir_page(0);
      };
    </script>
  </head>
  <body>
    <noscript>Activez l'option JavaScript du navigateur.</noscript>
    <nav><a>Page 1</a><a>Page 2</a><a>Page 3</a><a>Page 4</a></nav>
    <div>C'est la page 1!</div>
    <div>C'est la page 2!</div>
    <div>C'est la page 3!</div>
    <div>C'est la page 4!</div>
  </body>
</html>

Exemple de présentation

Par rapport au premier exemple précédent, la feuille de style a été améliorée.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script>
      function voir_page(num) {
        for (var i=1; i<5; i++) {
          document.getElementById("page"+i).style.display="none";
          document.getElementById("onglet"+i).className="deselect";
        }
        document.getElementById("page"+num).style.display="block";
        document.getElementById("onglet"+num).className="select";
      }
    </script>
    <style>
      body {
        margin: 0px;
        padding: 0px;
        background-color:#dddddd;
      }
      h1 {
        margin: 0px;
        padding: 10px;
        background-color:#eeeeee;
        text-align: center;
        font-size: 1.2em;
      }
      #page1,#page2,#page3,#page4 {
        display: none;
        padding: 15px;
      }
      #menu {
        margin: 0px;
        padding: 0px;
        padding-left:10%;
        background-color:#eeeeee;
        border-bottom : 1px solid #888888;
      }
      #menu a {
        color: #444444;
        padding: 16px;
        padding-top: 8px;
        padding-bottom: 6px;
        display:inline-block;
        border-top : 1px solid #888888;
        border-right : 1px solid #888888;
        border-left : 1px solid #888888;
        border-radius : 8px 8px 0px 0px;
      }
      #menu a.deselect {
        background-color: #aaaaaa;
        background-image: linear-gradient(#dddddd, #bbbbbb);
      }
      #menu a.select {
        padding-top:12px;
        background-color: #dddddd;
        position: relative;
        top:1px;
      }
      #menu a:hover {
        padding-top:12px;
        background-color: #dddddd;
        background-image: linear-gradient(#bbbbbb, #dddddd);
      }
    </style>
  </head>
  <body onload="voir_page(1);">
    <h1>Le titre...</h1>
    <div id="menu">
      <a id="onglet1" onclick="voir_page('1');" class="select">Onglet 1</a>
      <a id="onglet2" onclick="voir_page('2');" class="deselect">Onglet 2</a>
      <a id="onglet3" onclick="voir_page('3');" class="deselect">Onglet 3</a>
      <a id="onglet4" onclick="voir_page('4');" class="deselect">Onglet 4</a>
    </div>
    <div id="page1">C'est la page 1!</div>
    <div id="page2">C'est la page 2!</div>
    <div id="page3">C'est la page 3!</div>
    <div id="page4">C'est la page 4!</div>
    <noscript>Activez l'option JavaScript du navigateur.</noscript>
  </body>
</html>

Résultat obtenu :

Deuxième méthode

Problématique

Si le site web comporte de nombreuses pages, on ne va pas le précharger dans son intégralité. Il est préférable de ne charger que les pages qui seront effectivement visualisées. Pour la deuxième méthode, le recours à ajax s'impose.

Solution avec l'objet XMLHttpRequest

Dans la solution proposée ci-dessous :

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script>
      function voir_page(num) {
        for (var i=1; i<5; i++) {
          document.getElementById("onglet"+i).className="deselect";
        }
        document.getElementById("onglet"+num).className="select";
        document.getElementById("page").innerHTML='<img src="chargement.gif">';
        xhr=new XMLHttpRequest();
        xhr.open("GET", "page"+num, false);
        xhr.send(null);
        document.getElementById("page").innerHTML=xhr.responseText;
      }
    </script>
    <style>
      .select { color: red; }
      .deselect { color: blue; }
    </style>
  </head>
  <body onload="voir_page(1);">
    <div>
      <span id="onglet1" onclick="voir_page('1')" class="select">Page 1</span>
      <span id="onglet2" onclick="voir_page('2')" class="deselect">Page 2</span>
      <span id="onglet3" onclick="voir_page('3')" class="deselect">Page 3</span>
      <span id="onglet4" onclick="voir_page('4')" class="deselect">Page 4</span>
    </div>
    <div id="page"></div>
  </body>
</html>

Exemple de fichier, ci-dessous nommé page2 :

Le début de la page page 2.<br>
La suite de la page 2...<br>
La fin de la page 2.

Solution avec la balise iframe

Une requête ajax peut se faire de plusieurs manières. Le recours à la balise iframe est une autre solution.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script>
      function voir_page(num) {
        for (var i=1; i<5; i++) {
          document.getElementById("onglet"+i).className="deselect";
        }
        document.getElementById("onglet"+num).className="select";
        document.getElementById("chargement").style.display="block";
        document.getElementById("page").style.display="none";
        document.getElementById('page').src="page"+num;
        document.getElementById('page').onload=function() {
          document.getElementById("chargement").style.display="none";
          document.getElementById("page").style.display="block";
        };
      }
    </script>
    <style>
      .select { color: red; }
      .deselect { color: blue; }
    </style>
  </head>
  <body onload="voir_page(1);">
    <div>
      <span id="onglet1" onclick="voir_page('1')" class="select">Page 1</span>
      <span id="onglet2" onclick="voir_page('2')" class="deselect">Page 2</span>
      <span id="onglet3" onclick="voir_page('3')" class="deselect">Page 3</span>
      <span id="onglet4" onclick="voir_page('4')" class="deselect">Page 4</span>
    </div>
    <div id="chargement"><img src="chargement.gif"></div>
    <iframe id="page">
      Votre navigateur ne supporte pas l'élément iframe.
    </iframe>
  </body>
</html>

Réalisation : Feuilles de calculs

Problématique

Considérons une application web constituée de plusieurs feuilles de calcul. Ces feuilles de calcul, toutes présentées de la même manière, diffèrent peu les unes des autres.

Une solution simple consiste à réaliser une feuille de calcul, puis, celle-ci étant terminée, à faire des copier-coller pour les feuilles de calcul suivantes. Un inconvénient est que si l'on souhaite apporter une modification, par exemple à la présentation, il faut reprendre chaque feuille de calcul, une par une, ce qui peut s'avérer très long si celles-ci sont nombreuses.

Une autre solution consiste à créer un constructeur "C_feuille_de_calcul", à partir de duquel on obtient autant de feuilles de calcul que nécessaire. Cette solution, séduisante au premier abord, pose toutefois quelques difficultés de programmation.

Exemple de solution

La solution proposée ci-dessous consiste à créer une fonction qui génère une page de calcul selon les paramètres fournis. Elle est décomposée en trois parties, conformément à l'architecture modèle-vue-contrôleur. Cette solution de base permet de créer des feuilles de calcul avec plusieurs entrées mais une seule formule de calcul et un seul résultat.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script>
      // Le modèle
      donnee=[];
      C_donnee=function(a) {
        this.titre=a;
        this.entree=[];
        this.formule="";
        this.resultat="";
        this.ajouter_entree=function(a) { this.entree[this.entree.length]=a; };
      }
      function titre(a) { donnee[donnee.length]=new C_donnee(a); }
      function entree(a) { donnee[donnee.length-1].ajouter_entree(a); }
      function formule(a) { donnee[donnee.length-1].formule=a; }
      function resultat(a) { donnee[donnee.length-1].resultat=a; }
      // Première feuille de calcul
      titre("Cylindre");
      entree("d");
      entree("l");
      formule("Vcyl=Math.PI*d*d*l/4");
      resultat("Vcyl");
      // Deuxième feuille de calcul
      titre("Sphère");
      entree("r");
      formule("Vsph=4*Math.PI*r*r*r/3");
      resultat("Vsph");
      // La vue
      function commencer() {
        var ch="<p>Calcul du volume</p><ul>";
        for (var i=0; i<donnee.length; i++) {
          ch+="<li onclick='calcul("+i+");'> "+donnee[i].titre;
        }
        ch+="</ul>";
        document.getElementById("page").innerHTML=ch;
      }
      function calcul(n) {
        var ch="<form name='f'><h2>"+donnee[n].titre+"</h2>";
        for (var i=0; i<donnee[n].entree.length; i++) {
          ch+=donnee[n].entree[i];
          ch+=" = <input type='text' name='"+donnee[n].entree[i];
          ch+="'> mm<br>";
        }
        ch+="<br><button type='button' onclick='valider("+n+");'>";
        ch+="CALCULER LE VOLUME</button><br><br>"
        ch+=donnee[n].resultat;
        ch+=" = <input type='text' name='"+donnee[n].resultat+"'>";
        ch+=" mm<sup>3</sup>";
        ch+="<br><br><button type='button' onclick='commencer();'>";
        ch+="ACCUEIL</button><br><br>";
        ch+="</form>";
        document.getElementById("page").innerHTML=ch;
      }
      // Le contrôleur
      function valider(n) {
        for (var i=0; i<donnee[n].entree.length; i++) {
          ch=donnee[n].entree[i];
          ch+=" = document.f."+donnee[n].entree[i]+".value*1;";
          eval(ch);
        }
        eval(donnee[n].formule);
        ch="document.f."+donnee[n].resultat+".value="+donnee[n].resultat;
        eval(ch);
      }
    </script>
  </head>
  <body onload="commencer();">
    <noscript>Activez l'option JavaScript du navigateur.</noscript>
    <div id="page"></div>
  </body>
</html>

Autre réalisation : Diaporama

Problématique

Les logiciels pour créer les diaporamas, comme Impress ou PowerPoint, ne sont pas parfaitement compatibles entre eux. De plus, ils sont mal adaptés à une publication des documents sur internet. L'idée est donc d'utiliser les possibilités le JavaScript pour faire défiler diverses pages, au format HTML, les unes après les autres.

Exemple de solution

A l'aide d'un logiciel quelconque, l'utilisateur va créer un certain nombre de pages, les enregistrer au format HTML, les ranger dans un dossier. Dans cet exemple, les pages "doc1.html" à "doc4.html" sont rangées dans le dossier "pages".

Il faut préciser à l'application l'ordre dans lequel doivent défiler les pages. Cela se fait avec le fichier "donnees.js", dans lequel la fonction "page();" est appelée autant de fois que nécessaire.

page("pages/doc1.html");
page("pages/doc2.html");
page("pages/doc3.html");
page("pages/doc4.html");

Il ne reste plus qu'à ouvrir le fichier ci-dessous avec un navigateur. Les pages défilent à l'aide des raccourcis clavier, ou en cliquant sur les boutons prévus à cet effet.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
      iframe {
        border: 0;
        width: 100%;
        height: 100%;
      }
      #pages {
        position: absolute;
        top: 0px;
        bottom: 0px;
        left: 0px;
        right: 0px;
      }
      #menu {
        position: absolute;
        bottom: 0px;
        left: 0px;
        right: 0px;
        padding: 15px;
        text-align: right;
      }
      .montre { display : block; }
      .cache { display : none; }
    </style>
    <script>
      // Le modèle
      url=[];
      page=function(a) { url[url.length]=a; }
      // La vue
      initialiser=function() {
        var ch='<iframe id="page0" src="'+url[0]+'" class="montre"></iframe>';
        for (var i=1;i<url.length;i++) {
          ch+='<iframe id="page'+i+'" src="'+url[i]+'" class="cache"></iframe>';
        }
        document.getElementById("pages").innerHTML=ch;
      };
      // Le contrôleur
      num_page_vue=0;
      voir=function(n) {
        if ((n>=0)&&(n<url.length)) {
          document.getElementById("page"+num_page_vue).className="cache";
          num_page_vue=n;
          document.getElementById("page"+num_page_vue).className="montre";
        }
      };
      // Raccourcis clavier
      window.onkeydown=function(event) {
        if(event.keyCode=="8") voir(0);
        if(event.keyCode=="37") voir(num_page_vue-1);
        if(event.keyCode=="39") voir(num_page_vue+1);
      };
    </script>
  </head>
  <body onload="initialiser();">
    <noscript>Activez l'option JavaScript du navigateur.</noscript>
    <div id="pages"></div>
    <div id="menu">
      <button type="button" onclick="voir(0);"> ☐ </button>
      <button type="button" onclick="voir(num_page_vue-1);"> ◁ </button>	
      <button type="button" onclick="voir(num_page_vue+1);"> ▷ </button>
    </div>
    <script src="donnees.js"></script>
  </body>
</html>