HTML Orienté Objet avec CSS

Ou Comment avoir (presque) l’impression de manipuler de l’objet en CSS.

HTML sémantique

Pour cela, il faut partir d’un code HTML sémantiquement fort. Les règles que j’utilise sont les suivantes :

  1. différencier les types de structures génériques des données spécifiques ;
  2. réfléchir à la forme HTML la plus appropriée pour chaque structure générique ;
  3. rester simple.

La première règle s’applique lorsqu’il n’existe pas de balises HTML existante permettant de décrire exactement le type de la structure. Un paragraphe est un <p>, une liste est un <ul>, mais un menu ? Un fil d’Ariane ? Une propriété d’un formulaire ?
Il faut de plus différencier ces structures génériques de leur réalité. Par exemple, une même page peut avoir plusieurs menus (même structure générique) mais l’un est le menu principal, l’autre le menu secondaire, etc. De la même façon, il existe plusieurs propriétés dans un formulaire mais chacune désigne une certaine donnée : identifiant, mot de passe, nom, prénom…

À chaque structure non existante, il est nécessaire de réfléchir à la structure la plus appropriée, à la fois sémantiquement et hiérarchiquement. Communément un menu est représenté par une liste d’éléments (<ul> et <li>). Un fil d’Ariane pourrait l’être par une liste ordonnée (<ol>). Une propriété d’un formulaire pourrait l’être par une liste de définitions (<dl>, <dt> pour le label, <dd> pour le champ). Il n’y a rarement qu’une seule bonne solution.
Lorsque les propriétés seules ne sont pas suffisantes, une pratique courante est d’augmenter le sens d’une balise existante grâce à un attribut HTML, notamment avec l’attribut class qui permet d’être différencié en CSS (voir les microformats). Ainsi, on pourra par exemple ajouter la classe menu sur le <ul> pour désigner un menu (<ul class="menu">), ou encore la classe “breadcrumb” sur un <ol> pour désigner un fil d’Ariane.

Enfin, il est essentiel de rester simple dans la construction du HTML : pas de profusion de <div>, de <p> et de <span> ! Pas besoin d’encapsuler un <h1> dans un <div class="title"> : le <h1> EST un titre.
Tant que c’est raisonnablement possible, fusionner les <div>, <p> et <span> avec les balises porteuses de sens et s’appuyer sur le contexte pour pouvoir distinguer les balises. Ainsi, on peut transformer <div class="ContactPage"><div class="ContactForm"><form>... en <div class="ContactPage"><form>..., voire en <form class="ContactPage>.

Après cette introduction, nous pouvons entrer dans le vif du sujet en commençant par utiliser de manière orientée objet les attributs HTML.

Classification et identification du HTML

Deux attributs permettent de s’approcher de la notion d’objets en HTML : class et id. Class peut être utilisé pour représenter la ou les classes (au sens POO du terme). De la même façon qu’un objet peut être de la classe Student qui hérite de User, un <div> a comme attribut class="Student User". L’attribut id sert quant à lui à identifier une instance d’un objet HTML : la <div id="student3188" class="Student"> correspond ainsi à une instance (appelée student3188) d’une classe Student.

Chaque partie du code HTML peut être décrite de cette manière. Par exemple, une liste d’utilisateurs peut être écrite sous la forme :

<ul class="Users">
   <li class="User" id="johndoe">
      <p class="Name">John Doe</p>
      <p class="Age"><span class="Value">25</span> years old</p>
   </li>
   <li class="User" id="janedoe">
      <p class="Name">Jane Doe</p>
      <p class="Age"><span class="Value">27</span> years old</p>
   </li>
</ul>

Une fois que le code HTML du site est organisé de cette manière, il est possible d’utiliser le CSS pour accéder aux différentes parties.

Écriture des sélecteurs CSS

L’écriture du code CSS s’appuie fortement sur la caractérisation des balises HTML expliquée précédemment. Grâce aux attributs class (sans balise HTML) il va être possible de mettre en forme de manière générique les données. L’attribut id servira quant à lui au besoin pour différencier les cas.
On peut donc écrire par exemple :

.Users {
   margin: 15px;
}
   .Users .User {
      border: 1px solid grey;
      padding: 10px;
   }
      .Users .User .Name {
         font-weight: bold;
      }
      .Users .User .Age {
         font-size: 0.8em;
      }
         .Users .User .Value {
            font-size: 1.5em;
         }
   .Users .User#janedoe {
      border-color: blue;
   }

Cette notation en classes donne l’impression d’écrire des accès à des variables en programmation orientée objet. En effet, les langages utilisent souvent le caractère . (point) pour accéder à la propriété d’un objet. Ainsi, on accède à l’attribut name de l’objet user par user.name. L’utilisation d’une organisation du code HTML et de sélecteurs CSS basés sur les classes permet d’approcher syntaxiquement cela : .User .Name.
Le fait de s’appuyer quasi uniquement sur l’arborescence de classes permet notamment :

  • d’éviter les conflits d’identifiant ;
  • de pouvoir accéder de manière lisible et compréhensible (sémantique) aux éléments ;
  • de réduire les soucis de priorité des sélecteurs CSS ;
  • de rester suffisamment souple et précis pour pouvoir tolérer des modifications du code HTML sans retouche du code CSS (encapsulation dans un autre bloc, modification du type de balise HTML…).

De cette manière les codes HTML et CSS approchent les qualités de modularité et de structuration de la programmation orientée objet.

Utiliser switch() pour gérer des processus

Voici une façon d’utiliser l’instruction switch présente sur de nombreux langages, notamment en PHP :

switch (false) {
   case doStep1(): print("Error at step 1"); break;
   case doStep2(): print("Error at step 2"); break;
   case doStep3(): print("Error at step 3"); break;
   default: print("Success!"); break;
}

Cette méthode permet notamment de gérer les retours en cas d’erreur lors du déroulement du processus. Les étapes vont être exécutées une à une et si l’une d’elles échoue (return false par exemple), le traitement associé à cette étape est exécuté puis le processus est terminé.
Si doStep1() retourne true, la comparaison avec le false du switch échoue et le programme tente avec le prochain case défini. Par contre, si doStep1() retourne false, la comparaison correspond (false == false) et le traitement associé au case est donc exécuté – en l’occurrence le traitement de l’erreur.

En somme, le fonctionnement n’est pas différent de :

if (doStep1()) {
   if (doStep2()) {
      if (!doStep3()) {
         print("Success!");
      } else {
         print("Error at step 3");
      }
   } else {
      print("Error at step 2");
   }
} else {
   print("Error at step 1");
}

ou encore de :

if (!doStep1()) {
   print("Error at step 1");
} else if (!doStep2()) {
   print("Error at step 2");
} else if (!doStep3()) {
   print("Error at step 3");
} else {
   print("Success!");
}

L’avantage de la notation en switch réside principalement dans sa lisibilité. En effet, chacune des notations précédente a au moins un défaut. Pour la première, il sont évidents : la cascade de conditions (indentation) et la séparation symétrique entre la condition et le traitement d’erreur (le traitement de l’étape 1 est à la fin, le message de succès est au milieu) entraînent une très mauvaise lisibilité du processus. Pour la deuxième, le processus reste lisible mais les conditions peuvent l’être moins puisqu’elles testent la non-validité de l’étape. Ainsi la compréhension des étapes est polluée par la négation de la condition : par le signe ! en préfixe dans l’exemple, mais éventuellement par des conditions plus tordues (!a || !b || !c…).
La structure en switch, pour peu qu’on la connaisse, offre une lisibilité adaptée au déroulement d’un processus : chaque étape se situe sur la même colonne, chaque traitement d’erreur ou de succès est proche de sa cause et les étapes sont lisibles. En faisant abstraction du langage en lui-même, la structure correspond à :

PROCESS:
   STEP1: ERROR1
   STEP2: ERROR2
   STEP3: ERROR3
   END: SUCCESS

Cette structure favorise également la décomposition de chaque étape en une fonction claire. Ainsi, au lieu d’avoir une accumulation de traitements mélangés comme c’est le cas lors de l’utilisation d’un if, la structure incite (en tout cas encourage) à nommer clairement chaque étape et à réaliser la fonction associée :

switch (false) {
   case enterLogin(): ... break;
   case enterPassword(): ... break;
   case validateForm(): ... break;
   case checkAuthentication(): ... break;
   ...
}

Lorsque quelqu’un relit le code, il peut très simplement comprendre le processus en lisant le nom de chaque fonction.

Enfin, le switch offre un léger avantage sur le if : l’instruction break. Cette dernière permet d’interrompre le processus à tout moment et peut éviter des constructions complexes. On peut par exemple écrire :

switch (false) {
   case doStep1():
      if (verbose == 0) break;
      print('Error');
      if (verbose == 1) break;
      print(error_msg());
      break;
   ...
   default:
      if (...) {
         break;
      }
      ...
}

Ce fonctionnement est moins naturel à réaliser avec un if.

Pour terminer, voici un exemple en syntaxe PHP d’une structure switch (false) qui mélange vérification et processus :

switch (false) {
   // checking if the process can be executed
   case doesTheUserWantToExecuteThisProcess():
      break;

   // checking some general conditions
   case isTheUserAuthenticated():
      redirectToLoginPage(); break;

   // checking variables
   case isPosted('title'):
      printError('The title is mandatory.'); break;
   case isPosted('text'):
      printError('The text is mandatory.'); break;
   case isPosted('category'):
      printError('The category is mandatory.'); break;

   // executing the process
   case connectToDatabase():
      printError('Database connection failed.'); break;
   case $article = Article::create(posted('title'), posted('text')):
      printError('The creation of the article failed!'); break;
   case moveArticleToCategory($article, posted('category')):
      printError('The category does not exist.'); break;
   default:
      printSuccess('Your article has been added to the category!');
}