Explications et sources des exemples de l'article sur WebObjects
paru dans Linux Magazine France 62

L'article paru dans LMF présente une vue d'ensemble de WebObjects, GNUstepWeb et NGObjWeb, ainsi que des exemples simples d'utilisation. Comme il est difficile (et peu lisible) de tout mettre dans un article de quelques pages, cette page web contient des explications plus detaillées et le code source de ces exemples.

Préalable

Vous devez avoir installé un serveur web (Apache 1.x ou 2.x) et avoir installé GNUstepWeb. GNUstepWeb est disponible à partir du cvs GNUstep (dans dev-libs/gsweb). Consultez le fichier INSTALL pour l'installation de GNUstepWeb.

À noter : dans les exemples, j'ai utilisé le préfixe WO* pour les classes, de façon à être compatible avec WebObjects ou NGObjWeb. Cependant, par défaut GNUstepWeb se compile en utilisant le préfixe GSW*. Au moment de configurer GNUstepWeb, pensez donc à spécifier l'option gswnames :

./configure --with-gswnames=wo

L'utilisation de NGObjWeb nécessite quelques légères modifications dans le makefile et dans la fonction main.

Il est enfin nécessaire d'indiquer dans votre httpd.conf quelques lignes pour GNUstepWeb :

LoadModule GSWeb_Module /etc/apache2/mod_gsweb.so
GSWeb_ConfigFilePath /etc/httpd/conf/gsweb.conf
<Location /GSWeb*>
        SetHandler GSWeb
</Location>

En particulier la ligne GSWeb_ConfigFilePath qui indique le fichier de configuration utilisé par GNUstepWeb, fichier que l'on modifiera. La directive Apache "Location" permet de rediriger toute requête ayant /GSWeb vers GNUstepWeb. Ainsi, notre application (appellée TestLMF) sera accessible par 127.0.0.1/GSWeb/TestLMF .

Composants HTML

Dans l'article, il y a deux classes d'exemples; les exemples utilisant des composants "purement" html, sans code Objective-C associé, et ceux utilisant du code Objective-C. Dans un premier temps, nous allons créer une application utilisant simplement les composants html.

Structure d'une application WebObjects

Une application GNUstepWeb est simplement une application GNUstep, utilisant le framework GNUstepWeb, permettant d'utiliser l'application sur internet. La structure est donc semblable à celle d'une application normale -- on retrouve un makefile GNUstep (GNUmakefile), associé à des fichiers sources (Objective-C ou C). La différence principale est bien évidemment la possibilité de définir des composants web (qui sont en pratique des dossiers contenant les fichiers nécessaires au composant).

Pour "démarrer" notre application, on a besoin de définir deux objets -- un objet héritant de WOApplication, en charge donc de l'application, et un objet héritant de WOSession, permettant de travailler au niveau des sessions utilisateurs. Enfin, comme tout programme C/Objective-C, on a besoin d'une fonction main().

#import <WebObjects/WebObjects.h>

@interface TestLMF : WOApplication
{
}
@end

@implementation TestLMF
@end

@interface Session:WOSession
{
}
@end

@implementation Session
@end

int main(int argc, char **argv, char **env) {
  int ret=0;
  NSAutoreleasePool *arp = [NSAutoreleasePool new];
  ret = WOApplicationMain(@"TestLMF", argc, (void*)argv);
  [arp release];
  return ret;
}

Ce fichier TestLMF.m se contente de définir un objet TestLMF héritant de WOApplication, ainsi qu'un objet Session héritant de WOSession. Enfin, une fonction main se charge d'enregistrer l'application auprès de GNUstepWeb. Les objets TestLMF et Session ne contiennent rien, notre application étant simplissime.

Nous allons ensuite définir des composants web. Nous allons avoir :

Le composant Personne

Comme indiqué précédemment, on utilise un composant Personne. Pour le définir, nous devons créer un répertoire "Personne.wo" qui contiendra les divers fichiers de ce composant.

mkdir Personne.wo ; cd Personne.wo

Personne.html

créons ensuite le fichier template html de ce composant (fichier "Personne.html") et mettons y le contenu suivant :

Prenom : <#prenom/><br/>
Nom : <#nom/><br/>
Activite : <#activite/>

Personne.wod

Ajoutons le fichier de liaison Personne.wod, avec le contenu suivant :

prenom : WOString { value = "Jean-Paul" }
nom : WOString { value = "Liégeois" }
activite : WOString { value = "jeune lecteur du Var" }

Composant sans code...

Un composant est normalement associé à un objet Objective-C; comme ce n'est pas le cas ici, on doit indiquer dans un fichier Personne.gswi que notre composant hérite de la classe WOComponent :

{
        superClassName = "WOComponent";
}

Recapitulons

Vous devez à ce point avoir un composant Personne (un répertoire Personne.wo) contenant 3 fichiers :

Le composant Lecteurs

On définit le composant Lecteurs de la meme façon que pour Personne; on crée un répertoire Lecteurs.wo où on ajoute un fichier template Lecteurs.html et un fichier de liaison Lecteurs.wod . On doit également ajouter un fichier Lecteurs.gswi identique au Personne.gswi, car là encore on n'utilise pas de code Objective-C.

Fichier Lecteurs.html :

<h1>Liste des Lecteurs </h1>
<#personne/>

Fichier Lecteurs.wod :

personne : Personne {}

Le composant Page

Pour le moment, les deux composants que l'on a défini ne génèrent pas de pages html complètes, seulement une partie. On a donc besoin d'un composant définissant une page html, que l'on appellera de façon assez originale "Page". On le définit comme les précèdents en créant un répertoire "Page.wo" et en y ajoutant les fichiers Page.gswi (identique aux precedents..), Page.html et Page.wod.

Fichier Page.html :

<html>
<head>
<title> Exemple WebObjects </title>
</head>
<body>
	<#contenu/>
</body>
</html>

Fichier Page.wod :

contenu : WOComponentContent {}

Le composant Main

Enfin, il nous reste à définir le composant Main, qui est le composant appellé par défaut par l'application. Là encore, on crée un répertoire Main.wo contenant les fichiers Main.html, Main.wod et Main.gswi. Ce composant Main va simplement afficher une page html; on le définit en utilisant un composant Lecteurs englobé dans un composant Page.

Fichier Main.html :

<#page>
        <#lecteurs/>
</#page>

Fichier Main.wod :

lecteurs : Lecteurs {}
page : Page {}

Ajout de notre application dans le fichier de configuration de GSWeb

Pour tester notre application, il est nécessaire d'informer GSWeb, en indiquant quelles instances de notre application sont disponibles, sur quelles machines et en écoute sur quels ports. Ajoutons donc dans le fichier gsweb.conf, dans la liste des applications, un descriptif pour TestLMF:

TestLMF = {
        // List of its instances
        instances = {
                1 = {
                        host = csviking;
                        port = 9001;
                        parameters = {
                                transport = socket;
                        };
                };
        };
};

Le makefile

Maintenant que tout est prêt... ou presque, il nous reste à écrire notre makefile.

GNUmakefile

Voici ce que vous devez avoir dans le GNUmakefile :

include $(GNUSTEP_MAKEFILES)/common.make
include $(GNUSTEP_MAKEFILES)/Auxiliary/gsweb_wo.make

GSWAPP_NAME = TestLMF

TestLMF_PRINCIPAL_CLASS=TestLMF
TestLMF_HAS_GSWCOMPONENTS=YES
TestLMF_GSWAPP_INFO_PLIST=Info-TestLMF.plist
TestLMF_COMPONENTS = Personne.wo Lecteurs.wo Page.wo Main.wo
TestLMF_OBJC_FILES = TestLMF.m

include $(GNUSTEP_MAKEFILES)/gswapp.make

Explication pas à pas

On utilise un makefile GNUstep. On inclu donc d'abord les scripts :

include $(GNUSTEP_MAKEFILES)/common.make
include $(GNUSTEP_MAKEFILES)/Auxiliary/gsweb_wo.make

On indique ensuite le nom de notre application web :

GSWAPP_NAME = TestLMF

On définit ensuite la classe principale et que l'on utilise des composants :

TestLMF_PRINCIPAL_CLASS=TestLMF
TestLMF_HAS_GSWCOMPONENTS=YES

On indique le fichier plist utilisé (ce fichier contient divers parametres) :

TestLMF_GSWAPP_INFO_PLIST=Info-TestLMF.plist

On indique maintenant les composants utilisés dans notre application :

TestLMF_COMPONENTS = Personne.wo Lecteurs.wo Page.wo Main.wo

On ajoute ensuite le fichier TestLMF (contenant nos objets applications et session ainsi que la fonction main) :

TestLMF_OBJC_FILES = TestLMF.m

Enfin on termine en incluant le script gswapp :

include $(GNUSTEP_MAKEFILES)/gswapp.make

Le fichier info plist

Voici le contenu de ce fichier plist (fichier Info-TestLMF.plist) :

defaults = {
	GSWAdaptor = WODefaultAdaptor;
	GSWHost = "csviking";
	GSWPort = 9001;
	GSWSessionTimeOut = 1200;
};

On y indique le nom du serveur (ici csviking) et le port où notre application sera en écoute.

Test de notre application

Ouf ! normalement, tout est défini, il ne reste plus qu'à compiler cette application.

Pensez éventuellement à sourcer GNUstep.sh si ce n'est pas déja fait, une application GNUstepWeb restant une application GNUstep :

 . /usr/GNUstep/System/Makefiles/GNUstep.sh

Il peut egalement être nécessaire de définir la variable globale GNUSTEP_STRING_ENCODING, donc ajoutez dans votre .bashrc ou faites un export :

export GNUSTEP_STRING_ENCODING=NSISOLatin1StringEncoding

Un simple make suffit ensuite à compiler l'application. Lancez-la :

./TestLMF.gswa/TestLMF

L'application doit normalement se lancer et être en écoute. Utilisez ensuite votre navigateur favori pour aller sur http://127.0.0.1/GSWeb/TestLMF ... le résultat devrait être :

L'archive contenant le projet entier est disponible ici.

Beaucoup de travail pour un résultat pas forcèment impressionant... non ? :-)

Cependant, le plus dur est fait -- il est maintenant facile de faire évoluer notre application. Modifions par exemple notre composant Personne de façon à être réellement dynamique !

Composants dynamiques

Modifions le composant Personne

Il nous suffit d'ajouter un fichier Personne.m à notre projet, contenant la définition d'un objet Personne :

#import <WebObjects/WebObjects.h>
@interface Personne : WOComponent
{
        NSString* nom;
        NSString* prenom;
        NSString* activite;
}
@end

@implementation Personne
@end

... et d'ajouter ce fichier à notre makefile :

TestLMF_OBJC_FILES = TestLMF.m Personne.m

Modifions maintenant le fichier de liaison Personne.wod :

prenom : WOString { value = prenom }
nom : WOString { value = nom }
activite : WOString { value = activite }

Ainsi, notre composant utilisera directement les valeurs de l'objet Personne. Il est alors possible de modifier le composant Lecteurs, qui utilise notre composant Personne, pour spécifier des valeurs différentes (fichier Lecteurs.wod) :

personne : Personne {
        nom = "Portes";
        prenom = "Guillaume";
        activite = "Milliardaire";
}

Ô miracle, une fois l'application recompilée et relancée, votre navigateur affichera :

L'archive contenant le projet entier est disponible ici.

Présentations de données...

Puisque nous travaillons sur le composant Personne, modifions légèrement l'exemple du magazine pour traiter non pas différents liens, mais simplement afficher une liste de lecteurs...

Objet DonneesPersonne

De quoi avons nous besoin ? d'abord, d'un objet stockant une "personne". Utiliser le composant Personne ne serait pas ideal, car il s'agit d'un composant web. Définissons donc simplement un objet DonneesPersonne.

Fichier DonneesPersonne.h :

#import <WebObjects/WebObjects.h>

@interface DonneesPersonne : NSObject
{
        NSString* nom;
        NSString* prenom;
        NSString* activite;
}
- (NSString*) nom;
- (NSString*) prenom;
- (NSString*) activite;
- (void) setNom: (NSString*) n;
- (void) setPrenom: (NSString*) p;
- (void) setActivite: (NSString*) a;
@end

Fichier DonneesPersonne.m :

#import "DonneesPersonne.h"

@implementation DonneesPersonne
- (NSString*) nom { return nom; }
- (NSString*) prenom { return prenom; }
- (NSString*) activite { return activite; }
- (void) setNom: (NSString*) n { ASSIGN (nom, n); }
- (void) setPrenom: (NSString*) p { ASSIGN (prenom, p); }
- (void) setActivite: (NSString*) a { ASSIGN (activite, a); }
@end

Modifions notre objet Application

Admettons que nous voulions stocker une liste de lecteurs au niveau même de l'application. On va alors modifier notre objet TestLMF pour ajouter une variable list (un simple tableau) :

#import "DonneesPersonne.h"

@interface TestLMF : WOApplication
{
        NSArray* list;
}
@end

La variable "list" contiendra la liste des personnes.

Ajoutons donc une methode "init" à notre objet TestLMF pour créer notre liste de personnes :

- (id) init {

        if (self = [super init])
        {
                id p1 = [DonneesPersonne new];
                id p2 = [DonneesPersonne new];

                [p1 setNom: @"Liegeois"];
                [p1 setPrenom: @"Jean-Pierre"];
                [p1 setActivite: @"jeune lecteur du Var"];

                [p2 setNom: @"Portes"];
                [p2 setPrenom: @"Guillaume"];
                [p2 setActivite: @"Milliardaire"];

                list = [[NSArray alloc] initWithObjects: p1, p2, nil];

                [p1 release];
                [p2 release];
        }
        return self;
} 

Au démarrage de notre application, l'objet list est donc initialisé et deux personnes sont ajoutées.

Lecteurs

Le composant Lecteurs affiche pour le moment un seul lecteur, défini "en dur". Modifions le pour qu'il utilise la liste créée précédemment.

On va utiliser un objet WORepetition, qui permet d'itérer sur une liste. WORepetition permet également d'accèder à l'objet courant lors du parcours de la liste. La liste existe déja, et est accessible par l'objet application. Nous avons juste besoin d'avoir un objet "stockant" l'objet courant. Ajoutons donc à notre composant Lecteurs une donnée membre "current" :

#import <WebObjects/WebObjects.h>
#import "DonneesPersonne.h"

@interface Lecteurs : WOComponent
{
        DonneesPersonne* current;
}
@end

@implementation Lecteurs
@end

Modifions maintenant le template html du composant Lecteurs :

<h1> Liste des Lecteurs </h1>
<ul>
        <#repetition>
                <li><#personne/></li>
        </#repetition>
</ul>

"repetition" est un composant WORepetition; il va répèter autant de fois qu'il y a d'items dans la liste son contenu -- ici, <li><#personne/></li> . Voyons comment est fait le fichier de liaison Lecteurs.wod pour utiliser ce composant :

personne : Personne {
        nom = current.nom;
        prenom = current.prenom;
        activite = current.activite;
}

repetition : WORepetition {
        list = application.list;
        item = current;
}

On indique simplement que la liste utilisée par répétition est accessible à travers l'objet application; et que la valeur courante de la liste doit être mise dans la variable current du composant Lecteurs. On modifie ensuite personne de façon à indiquer que les différents attributs sont à aller récupérer dans la variable current.

N'oubliez pas d'ajouter les fichiers DonneesPersonne.m et Lecteurs.m au makefile :

TestLMF_OBJC_FILES = TestLMF.m Personne.m DonneesPersonne.m Lecteurs.m

Enfin, recompilez et relancez l'application. Vous devez obtenir :

L'archive contenant le projet entier est disponible ici.

NOTE: normalement, plutôt que de définir une liste "à la main" comme dans cet exemple, il est bien évidemment conseillé d'utiliser une base de donnée et une bibliothèque tel que SQLClient, ou mieux, GDL2 (qui en plus de fournir une abstraction à la base de données, permet de faire un mapping objet sur une base de données relationelles). Pour un futur article...

Continuons un peu...

Allons un petit peu plus loin que l'article LMF, et modifions notre application pour afficher non plus une liste, mais un tableau. Nous pourrions d'ailleurs faire l'ensemble de l'affichage dans le composant Lecteurs plutot que d'utiliser un composant Personne; il suffirait de modifier le template html et le fichier de liaison wod de la façon suivante :

<h1> Liste des Lecteurs </h1>
<ul>
        <#repetition>
                <li>
                	Prenom : <#prenom/><br/>
			Nom : <#nom/><br/>
                	Activite : <#activite/>
		</li>
        </#repetition>
</ul>
nom : WOString { value = current.nom }
prenom : WOString { value = current.prenom }
activite : WOString { value = current.activite }

repetition : WORepetition {
        list = application.list;
        item = current;
}

Utiliser un tableau reviens simplement alors à changer le template html :

<h1> Liste des Lecteurs </h1>
<table border="1px">
        <tr>
		<th>Prenom</th>
		<th>Nom</th>
		<th>Activite</th>
	</tr>
        <#repetition>
                <tr>
                	<td><#prenom/></td>
			<td><#nom/></td>
                	<td><#activite/></td>
		</tr>
        </#repetition>
</table>

Ajoutons simplement ce template plutôt que de remplacer le précédent; l'affichage obtenu est alors :

Voila, ce petit exemple commence à montrer l'intérêt d'utiliser une architecture un peu detaillée comme WebObjects. Certes, pour un simple affichage de liste, cela pourrait être fait en quelques lignes de php. Cependant, l'architecture plus poussée de WebObjects permet au final une bien meilleure evolutivité, présentation et code étant clairement séparés et basés sur des composants.

À noter qu'une feuille css ne ferait pas de mal pour rendre les choses plus agréables à l'oeil, mais cet article s'est concentré sur les principes WebObjects.


Nicolas Roard -- nicolas a roard point com