Web dynamique avec Domino : une nouvelle approche ?
L'un de mes plus gros regrets avec Domino, en tant que plateforme de développement web, est l'impossibilité d'inclure nativement des données dynamiques dans un masque ou dans un document, comme il est si naturel de le faire avec la plupart des autres langages orientés web (instructions "include" ou "require" avec PHP, "<jsp:include page=".."/>" avec JSP, etc). C'est pourtant la base de l'architecturation d'une application web que de pouvoir découper une page en unités fonctionnelles statiques (entête, menu, pied de page, etc) et dynamiques (actualités, annuaire, fils de discussion.. bref, tout ce qui est vivant dans une application).
Inclusion de données dynamiques : état des lieux
L'inclusion de données statiques ne pose aucun problème avec Domino et peut se faire aisément avec des champs partagés ou encore des sous-masques (calculés ou non). Toutefois, pour ce qui est de l'inclusion de données dynamiques, et à moins de pouvoir se contenter d'une (et une seule) vue, c'est une toute autre paire de manches, et de toutes les méthodes que j'ai pu tester, utiliser et recenser, aucune ne m'a jamais vraiment satisfaite. En voici la liste et mes commentaires respectifs :
- Vue : la limite théorique d'une vue embeddée par masque (en pratique et avec quelques bidouilles, on peut en insérer 2 ou 3, mais bon) écarte d'office cette solution pourtant très satisfaisante en terme de performances (puisque les vues sont indexées). Il reste toutefois possible de simuler des inclusions multiples à l'aide d'iframes, mais cela rend le document peu (pour ne pas dire pas du tout) accessible, multiplie le nombre de requêtes http (et donc le temps d'accès pour peu que la connexion lagge), et pose de nombreux soucis en terme de mise en page (ajustement de la taille de l'iframe en fonction de son contenu) et bien vite on sombre dans une usine à gaz en javascript pour que ça tienne a peu près la route (oui, c'est du vécu).
- Document profil : bien tenté, mais la mise à jour de documents profils ne fonctionne pas bien sur le web (et ce pour de sombres raisons de cache des documents dans la session HTTP). Cela dit, les documents profils sont tout à fait adaptés aux accès en lecture uniquement, et délivrent d'ailleurs à cet effet d'excellentes performances.
- Agent au
WebQueryOpen(ou appel direct d'un agent) : à proscrire, c'est le meilleur moyen de faire tomber son serveur pour peu qu'on ait plus de 20 ou 30 utilisateurs. @Dblookup: c'est mieux, mais ça reste très gourmand. En mettre une poignée peut passer, en mettre 40 devient tout de suite plus problématique. Bien entendu, tout ceci sans évoquer la limite des 64k.@GetDocField: méthode plus satisfaisante du point de vue des performances (d'après certains tests, 25% plus rapide qu'un@Dblookup), mais nécessite de jouer avec les UNID des documents, ce qui peut devenir un casse-tête sans fin pour peu que l'application doive tourner dans plusieurs environnements (dev, recette et prod par exemple). Et la limite des 64k reste toujours présente.- Agent qui recalcule les documents (
ComputeWithForm) auWebQuerySave: très satisfaisant en terme de performances, mais hélas peu évolutif, chaque nouvelle page / section / rubrique nécessitant une évolution de l'agent pour prendre en compte ladite nouvelle page / section / rubrique. Et pour les masques, ça revient à faire la même chose qu'un@Dblookupou@GetDocField. XmlHttpRequest: le procédé consiste à générer la page web en javascript (coté client, donc) en récupérant plusieurs documents Domino à l'aide de l'objetXmlHttpRequest. Pour l'avoir essayé, ça marche plutôt pas mal, mais j'ai naturellement tendance à me méfier des solutions reposant sur des paramètres que je ne maîtrise pas (en outre, le navigateur et la configuration du poste client).
Cette liste est probablement loin d'être exhaustive ; c'est tout ce que je connais et/ou qui me vient à l'esprit pour le moment. Si vous connaissez d'autres méthodes, merci d'en faire part en commentaires.
Quoi qu'il en soit, étant clairement insatisfait par cette situation, je suis depuis des mois en quête de la méthode ultime, et il s'avère que mes dernières expérimentations m'ont amené vers une piste qui se montre, pour le moment, plutôt prometteuse. Explications.
Inclusion statique de données dynamiques : la technique ultime ?
L'idée de base est toute bête : puisque aucune méthode d'inclusion dynamique n'est satisfaisante, et puisqu'il existe moult méthodes très au point d'inclusions statiques, essayons de faire de l'inclusion statique de données dynamiques. Techniquement parlant, cela se traduit de la façon suivante : inclure un sous-masque (soit une inclusion tout à fait statique) dont le contenu sera modifié, périodiquement ou sur demande, par traitement : le contenu du sous-masque devient dès lors dynamique.
Imaginez un peu les possibilités offertes par un tel procédé : construire une application web type portail, constituée de "blocs indépendants" au contenu entièrement dynamique, dans laquelle il serait possible de définir la présence ou encore la disposition desdits blocs via une interface d'administration spécifique, devient un véritable jeu d'enfant : cela reviendrait à créer un masque intégrant, selon la configuration choisie, un nombre variable de sous-masques (sous-masques calculés déterminés selon leurs formules de calcul) ; il ne resterait qu'à mettre à jour en cas de besoin (ajout ou modification de données, changement de jour, etc) le contenu des sous-masques en question. Bien évidemment, puisque contenues dans le design de la base, ces données seraient directement et implicitement mises à jour dans tous les documents ouverts avec le masque en question. Et tout ceci, signalons-le, pour une consommation de ressources minimale, les données étant, de par la nature de cette méthode, "cachées" dans le design de la base.
C'est ce que promet cette méthode, pour peu qu'elle se montre stable à l'usage. En effet, modifier la structure d'une base à la volée n'est pas anodin ni sans risque, et avant d'arriver à un premier résultat fonctionnel, un bon nombre de mes tests ont corrompu les éléments de design quand ce n'était pas directement la base, voire même littéralement fait planter le serveur Domino (et c'était d'ailleurs la première fois que je voyais Domino planter à la simple ouverture d'un document). Aussi, je vous mets en garde : il s'agit, pour le moment, d'un concept qui ne demande qu'à être validé. N'envisagez pas de le déployer dans une application critique !
Mise en oeuvre
Je vais tâcher de détailler la mise en oeuvre de cette technique au travers d'un exemple simple : un système de brèves tels que vous pouvez en trouver sur de nombreux sites (Nofrag par exemple).
Tout d'abord, il nous faut :
- Un serveur de test (relire quelques centimètres au dessus si vous vous demandez pourquoi).
- Une base vierge (même raison).
Puis, commençons par créer :
- Un sous-masque, que nous nommerons
"breves", qui contiendra le code HTML des brèves. - Une vue, que nous appellerons
"subforms", qui nous permettra de retrouver, par programmation, le sous-masque des brèves. Elle doit posséder une unique colonne ayant pour formule"$Title". - Un masque, qui sera notre point d'accès avec le navigateur et qui inclura donc le sous-masque des brèves (sinon aucun intérêt, hein).
Les éléments de design, comme n'importe quel autre élément de Notes, sont des documents, dans le sens où ils possèdent un UNID, des champs, et peuvent être listés au travers de vues (ce qui est totalement implicite dans le Designer). Par défaut, les vues que l'on crée ne sont configurées que pour lister des documents. Il faut donc les modifier afin de pouvoir lister un ou plusieurs types d'éléments de design.
C'est ce que nous allons faire à l'aide d'un agent LotusScript (que vous aurez la joie d'exécuter après chaque modification de la vue), dont ce sera l'unique tâche : modifier notre vue "subforms" pour qu'elle puisse lister les sous-masques :
Sub Initialize
Dim session As New NotesSession
Dim db As NotesDatabase
Dim view As NotesView
Dim doc As NotesDocument
Set db = session.CurrentDatabase
Set view= db.GetView("subforms")
Set doc = db.GetDocumentByUNID(view.UniversalID)
doc.~$FormulaClass = "4"
doc.Save True, False
End Sub
Toute l'astuce réside donc dans le champ "$FormulaClass", qui spécifie quel(s) type(s) de données liste la vue. Pour ceux qui souhaiteraient plus de détails sur sujet, je vous invite à consulter cet article de DominoPower : Fun with $FormulaClass.

On note le champ $FormulaClass modifié..

.. et la vue liste maintenant les sous-masques de la base !
Voilà, notre vue "subforms" liste maintenant les sous-masques (et accessoirement les masques). Le sous-masque "breves" étant créé, il n'y a rien de plus à faire le concernant ; il contiendra le code HTML des brèves, qui sera généré par un autre agent LotusScript (de manière programmée ou à l'ajout/modification de données (WebQuerySave)).
Cet agent va générer le code HTML, récupérer le sous-masque "breve" à l'aide de la vue "subforms" (tout aussi normalement que si l'on récupérait un document depuis une vue), réinitialiser son champ RTF "$Body" (qui représente, à l'instar des mémos, tout le contenu du sous-masque), injecter dedans le code HTML (en HTML relai) préalablement généré, puis enfin enregistrer et signer le sous-masque :
Sub Initialize
Dim session As New NotesSession
Dim db As NotesDatabase
Dim view As NotesView
Dim doc As NotesDocument
Dim html As String
Dim key As String
Dim i As Integer
Dim rtitem As Variant
Dim style As NotesRichTextStyle
Dim rtnavBegin As NotesRichTextNavigator
Dim rtnavEnd As NotesRichTextNavigator
Dim rtrange As NotesRichTextRange
Set db = session.CurrentDatabase
' *************************************
' ** GENERATION DU CODE HTML DU BLOC **
' *************************************
html = "<div><h3>Brèves</h3><ul>"
For i=1 To 5
html = html + "<li>ma brève "+cstr(i)+"</li>"
Next
html = html + "</ul></div>"
' *******************************************
' ** INJECTION DU CODE DANS LE SOUS MASQUE **
' *******************************************
' la vue des masques et sous masques
Set view= db.GetView("subforms")
' on récupère le sous-masque des brèves
key = "breves"
Set doc = view.GetDocumentByKey(key, True)
' On récupère le champ RTF $Body (qui s'avère être le contenu du sous masque)
Set rtitem = doc.getFirstItem("$Body")
rtitem.isSigned = False
' on supprime tout le contenu du RTF (seule technique qui semble pas tout faire planter..)
While (Len(rtitem.text) > 0)
Set rtnavBegin = rtitem.CreateNavigator
Set rtnavEnd = rtitem.CreateNavigator
rtnavBegin.FindFirstelement(RTELEM_TYPE_TEXTPARAGRAPH)
rtnavEnd.FindLastelement(RTELEM_TYPE_TEXTPARAGRAPH)
Set rtrange = rtitem.CreateRange
rtrange.SetBegin(rtnavBegin)
rtrange.SetEnd(rtnavEnd)
Call rtrange.Remove()
Wend
' On définit un style permettant de passthru-er le code HTML
Set style = session.CreateRichTextStyle
style.PassThruHTML = True
' On colle le style PUIS le code HTML dans le RTF (désormais vierge)
rtitem.appendStyle(style)
rtitem.appendText(html)
' Enfin, on enregistre et on signe le sous-masque ! 0wnage !
Call doc.save(True, False)
Call db.Sign(DBSIGN_DOC_FORM, False, key, False)
End Sub

Le code HTML injecté dans le sous-masque "breves" !
Enfin, il ne reste qu'à créer un masque et à y inclure notre sous-masque "breves", et c'est terminé !

Un masque, incluant notre fameux sous-masque "breves"
Bien entendu, l'idée n'est pas de générer "en brut" le code HTML dans l'agent, mais de le générer depuis des données stockées dans des documents, renseignés, pourquoi pas, depuis une interface d'administration spécifique. Je n'ai pas considéré toute cette partie dans l'exemple, puisqu'au final se révèle tout à fait dispensable dans la mise en oeuvre de cette technique. Dans cette optique, on aurait pu également envisager de zapper l'étape de la vue "subforms" et de se contenter d'un simple "NotesDatabase.getDocumentByUNID" sur l'UNID du sous-masque "breves", j'ai toutefois préféré la garder, tout d'abord parce que jouer avec les UNID dans le code c'est mal, d'autre part parce que la manipulation permettant de lister des éléments de design dans une vue n'est pas forcément évidente de prime abord et s'avérerait indispensable pour un projet un tant soit peu plus poussé.
Conclusion
Les possibilités offertes par cette technique sont, il me semble, immenses, et je suis persuadé qu'il y a bien des usages que je suis encore très loin de soupçonner. Les quelques essais que j'ai pu faire se sont montrés très concluants, tant du point de vue du fonctionnement que des performances. Rester à espérer qu'elle se montrera viable et stable dans le temps. Mais pour le savoir, il faut bien tester, c'est pourquoi je vais la déployer dans le projet que je réalise en ce moment, j'espère ne pas avoir de mauvaise surprise, mais je reste plutôt confiant ; les opérations réalisées ne relèvent pas tant que ça du bidouillage. Je ne manquerai pas d'en reparler dans un prochain billet.
De votre coté, n'hésitez pas à tester et à me dire ce que vous en pensez, toute suggestion, remarque ou amélioration étant bien évidemment la bienvenue !
Par YoGi, mercredi 29 décembre 2004 à 00:49 :: Lotus Notes / Domino :: #90 :: rss
Commentaires
1. Le mercredi 29 décembre 2004 à 20:32, par aquanotes
2. Le mercredi 29 décembre 2004 à 21:26, par YoGi
3. Le jeudi 13 janvier 2005 à 10:36, par Nicolas :: site
4. Le samedi 15 janvier 2005 à 15:51, par YoGi
5. Le lundi 17 janvier 2005 à 09:08, par Nicolas :: site
6. Le mardi 18 janvier 2005 à 23:48, par YoGi
7. Le mercredi 19 janvier 2005 à 14:26, par Nicolas
8. Le mercredi 19 janvier 2005 à 14:46, par YoGi
9. Le jeudi 20 janvier 2005 à 07:46, par Nicolas
10. Le mardi 15 mars 2005 à 16:05, par Tr@nji
11. Le samedi 21 janvier 2006 à 08:33, par Julien :: site
12. Le samedi 21 janvier 2006 à 12:42, par YoGi
Ajouter un commentaire
Les commentaires pour ce billet sont fermés.