blog.pagesd.info

Aller au contenu | Aller au menu | Aller à la recherche

jeudi 27 janvier 2011

Une chose à la fois

Via le blog de Scott Watermasysk :

If you’re starting a new company, the best thing you can do is keep your feature set small and focused. Do one thing as best as you possibly can. Your users will beg and beg for more functionality. They will tell you their problems and ask you to fix it. My philosophy is that they’re right if their feature request is right only if it works for 80% of your customers. Until you have a lot of resources, stay focused on your core competency.

Pourquoi est-ce que Dropbox est plus populaire que d'autres services identiques ? La réponse d'Isaac Hall, co-fondateur de Syncplicity, un concurrent de Dropbox (inscrivez-vous, je gagne des mégas).

mardi 11 janvier 2011

C'est un métier

Un problème ?

Good Code

La solution ?

Horizontal vs. vertical complexity

jeudi 8 octobre 2009

Tuer du code pour réussir

Dans les mois à venir, je vais avoir à participer à la re-écriture en ASP.NET MVC d'une application existante développée depuis des années en ASP.NET 1.1.

Après de longues tergiversations, il a bien fallu reconnaitre qu'une simple migration en ASP.NET 3.5 et de simples rustines n'étaient ni suffisantes ni envisageables (même pas en rêve mon ami !).

Le problème avec cette application, c'est qu'elle est le résultat d'un empilement continuel de nouvelles fonctionnalités (une fuite en avant perpétuelle ?) et qu'à aucun moment il a été accepté de prendre des pauses pour réaliser les consolidations nécessaires si on voulait que l'ensemble puisse évoluer (voire fonctionner) sereinement.

Ce qui fait qu'aujourd'hui, on a la chance d'avoir une grosse ... avec dans le tas des trucs certainement utiles mais aussi des trucs complètements stupides, des trucs réalisées pour faire plaisir à des gens qui n'ont jamais mis les pieds dans l'application, des trucs développés et toujours pas passés en production 1 an près, des trucs pour que "ça se fait tout seul y a rien à faire", des trucs pour contourner d'autres trucs qui ne marchent pas comme on veut qui faudrait que ça marche, des trucs qu'on est pas sûr que ça serve mais yaka les laisser, des trucs qui prennent une éternité pour faire 3 fois rien, des trucs qui ont été demandés parce qu'on sait jamais ça pourrait toujours servir un jour ou l'autre...

Pour résumer ma pensée, il est plus qu'indispensable de commencer par un énorme travail de réflexion sur le contenu de ce ramassis avant même de penser à démarrer un Visual Studio. Et par réfléchir j'entends supprimer ce qui ne sert pas, supprimer ce qui ne marche pas, supprimer ce qui n’est pas indispensable, supprimer ce qui n’est pas faisable, supprimer ce qui prendra trop de temps à faire, etc… En bref éviter de re-développer des fonctionnalités en pagaille et prendre le risque de re-venir à notre point de départ...

Mon souci c'est que le spectre d'une migration iso-fonctionnelle flotte encore dans l'air : on part de tout ça et au final on veut toujours tout ça (et un peu plus) mais en mieux. Et comme on est très conciliants, on pourrait à la rigueur accepter de sacrifier quelques écrans à condition qu'on retrouve leurs fonctionnalités dans d'autres écrans...

Voilà donc un de mes stress du moment. Mais ce matin, qu'est-ce que je découvre dans mon Bloglines ? Un article des créateurs de Scout qui explique qu'en supprimant des tonnes de code (y compris des fonctionnalités), ils ont amélioré leur application tant d'un point de vue performance (jusqu'à x 10 sur les traitements les plus couteux) que satisfaction de leurs clients. Et en plus ils nous nous livrent les deux leçons que l'on peut en retirer :

1. Arrêter de vouloir faire simple pour l'utilisateur final au prix d'une tarabiscotisation dans le code, mais chercher la simplicité dans la façon de faire (et perso, je pense que si c'est simple à faire, c'est évident à utiliser).

It turns out that simplicity and elegance for the end user can mean some awfully complicated stuff behind the scenes to make it work.

2. Arrêter de perdre son temps sur l'ajout de nouvelles fonctionnalités soi-disant incontournables qui vont cannibaliser notre temps et notre énergie juste pour les mettre au point

If you’re running a web application and the majority of your work is spent delivering the service you advertised to your customers, you’re probably in bad shape.

Ca, ça me plait !

mercredi 24 juin 2009

Une nouvelle boite de recherche

Jusqu'à présent, pour gérer la recherche sur un site Altrr-Press, il était possible de créer une boite de type Google Search. Ce type de boite employait l'interface SOAP de Google Search pour obtenir le résultat d'une recherche effectuée par Google. Dans la pratique, il y a tellement peu de sites Altrr-Press qui utilise ce type de boite qu'il aurait aussi bien pu passer à la trappe comme cela avait été le cas pour les boites Msn Search et Yahoo Search.

Ca faisait quelque temps que je savais que Google allait mettre définitivement fin au support de son interface SOAP à partir du 31 août, mais étant donné le peu d'utilité que j'en avais, j'avais un peu de mal à me plonger dans leur API de recherche en AJAX. Heureusement pour moi, ce que Google prend d'une main, il le redonne de l'autre et grâce aux tous nouveaux Google Web Elements, je vais pouvoir faire évoluer mon CMS sans trop me casser la tête.

Pour commencer, rendez-vous sur la page des Google Web Elements ou il suffit de cliquer sur le lien "Custom Search" pour obtenir immédiatement le code javascript à utiliser pour insérer une boite de recherche sur son site. Etant donné que que je cherche à disposer d'un système de recherche à l'intérieur de mon site exclusivement, cela me convient et je peux donc utiliser ce code tel quel :

<!-- Google Custom Search Element -->
<div id="cse" style="width:100%;">Loading</div>
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">
  google.load('search', '1');
  google.setOnLoadCallback(function(){
    new google.search.CustomSearchControl().draw('cse');
  }, true);
</script>

Si je le souhaite, Google me propose aussi d'utiliser ma clé Google AdSense pour valoriser les recherches effectuées sur mon site. Pour cela, il suffit de remplacer le choix "Automatically search my site" par "Search my site and use AdSense for Search". Le code javascript proposé est alors légèrement modifié pour faire apparaitre ma clé Google AdSense. avec la ligne suivante :

<!-- Google Custom Search Element -->
<div id="cse" style="width:100%;">Loading</div>
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">
  google.load('search', '1');
  google.setOnLoadCallback(function(){
    var cse = new google.search.CustomSearchControl();
    cse.enableAds('pub-5475403929650645');
    cse.draw('cse');
  }, true);
</script>

Dans la pratique, avec Altrr-Press il est tout de même préférable de modifier la ligne de code avec la clé en dur de façon à utiliser la macro qui renvoie la clé enregistrée globalement au niveau du site internet :

    cse.enableAds('[%AP.AdSenseKey%]');

C'est vraiment pas compliqué à faire et ça marche. Le seul petit problème, c'est que ça ouvre les liens dans une nouvelle fenêtre !!!

Finalement je vais quand même devoir faire un détour par les APIS AJAX de Google et plus particulièrement l'API AJAX consacrée à la recherche qui va me permettre de résoudre ce léger problème. Et après quelques petites recherches, je tombe enfin sur ce qu'il me faut, à savoir la méthode setLinkTarget(linkTarget).

This method is called to set the link target used for links embedded in search results. The default value is google.search.Search.LINK_TARGET_BLANK which specifies that links will open in a new browser window. When this method is called, a search control wide link target setting is established. This effects all searchers that are currently attached to the search control as well as all searchers that are subsequently added to the control.

Et selon la documentation de référence de Google, linkTarget peut prendre une des valeurs suivantes :

  • google.search.Search.LINK_TARGET_BLANK - links will open in a new window, e.g., <A href=... target=_blank ...>
  • google.search.Search.LINK_TARGET_SELF - links will open in the same window and frame, e.g., <A href=... target=_self ...>
  • google.search.Search.LINK_TARGET_TOP - links will open in the topmost frame, e.g., <A href=... target=_top ...>
  • google.search.Search.LINK_TARGET_PARENT - links will open in either the topmost frame, or replace the current frame, e.g., <A href=... target=_parent ...>
  • anything-else - links will open in the specified frame or window, e.g., <A href=... target=anything-else ...>

Après quelques essais, notamment à l'aide de l'excellent Code Playground et je finis par obtenir le code javascript suivant :

<!-- Google Custom Search Element -->
<div id="cse" style="width:100%;">Loading</div>
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">
  google.load('search', '1');
  google.setOnLoadCallback(function(){
    var cse = new google.search.CustomSearchControl();
    cse.enableAds('[%AP.AdSenseKey%]');
    cse.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
    cse.draw('cse');
  }, true);
</script>

Comme je suis un peu pointilleux, il me restait encore un détail mineur à régler : Google insère un lien libellé "Autres résultats »" juste après la pagination des résultats. Et quand on clique dessus (en pensant dans mon cas aller sur la page suivante des résultats), cela quitte le site et redirige sur une page chez Google qui reprend la même recherche. Ce n'est pas bien génant, mais j'ai quand même préféré cacher ce lien une ligne de CSS :

<style>.gsc-trailing-more-results {display:none !important;}</style>

Et donc, au jour d'aujourd'hui, pour placer une boite de recherche Google dans un site construit avec Altrr-Press, il suffit de copier / coller le code suivant dans une boite RawContent :

<!-- Google Custom Search Element -->
<div id="cse" style="width:100%;">Loading</div>
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">
  google.load('search', '1');
  google.setOnLoadCallback(function(){
    var cse = new google.search.CustomSearchControl();
    cse.enableAds('[%AP.AdSenseKey%]');
    cse.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
    cse.draw('cse');
  }, true);
</script>
<style>.gsc-trailing-more-results {display:none !important;}</style>

Avec en prime une démonstration en direct-live sur la nouvelle page de recherche pour le site de Saint-Privat.

lundi 22 septembre 2008

Récapitulatif un peu long

Ce billet est avant tout une sorte de message de service qui est essentiellement destiné à tenir deux collègues au courant de tout ce qui s'est passé sur PI et AP ces derniers temps.

Les modifications de PI (Pages d'Infos)

Les cinémas

J'ai fait pas mal de mises à jour sur les programmes de cinéma et il y a maintenant 3 présentations pour les séances :

  • affiches avec un tableau des séances hebdomadaires (jour par jour)
  • affiches avec un tableau des séances quotidiennes (heure par heure)
  • seulement les affiches

Et quand c'est disponible, la description et le réalisateur du film sont aussi affichés.

Pour l'Ardèche, j'utilise le tableau hebdo avec les cinémas où il y a peu de films et le tableau jour pour les autres.

Jusqu'à présent, les anciennes séances de dir_Seances étaient conservées seulement pendant 2 semaines (14 jours). J'ai passé ça à 5 semaines (35 jours) pour éviter de perdre trop vite les descriptions et les informations liées au film dans la table dir_Movies (et donc d'avoir à les re-saisir quand le film passe enfin dans les petites salles de l'Ardèche). En effet, on supprime les films de dir_Movies dès qu'ils n'apparaissent plus dans dir_Seances.

Et enfin, j'ai ajouté un champ "information" à la table dir_Cinemas pour ajouter une information ponctuelle à la page d'un cinéma. Si ce champ commence par un "!", il ne s'affiche que dans le cas où il n'y a pas de séances connues : je m'en suis servi pour afficher les périodes de fermeture des cinémas pendant l'été.

Les communes

Le template _PlaceTemplate.txt employé pour les communes gère maintenant une macro {@place.urlOffice} pour afficher un lien vers l'office de tourisme. Pour l'Ardèche, c'est un lien vers la page interne consacrée à l'office de tourisme.

Pour cela, le champ urlOffice de la table dir_Places contient :

<a href="[%AP.Path.Application%]annuaire/office-tourisme/mon-village.aspx" title="Office-tourisme">OT de Mon Village**</a>

(admirez l'utilisation des macros Altrr-Press :)

Pour vous, vous pouvez mettre directement quelque chose comme :

<a href="http://www.ot-mon-village.fr/"> title="Office tourisme">OT de Mon Village **</a>

J'ai aussi ajouté la macro {@place.weather} pour insérer l'encart météo juste au moment du rendu de la page d'une commune.

Avec la nouvelle macro {@place.htmlDescription} (ou {@place.description}), ce qui m'a permis de créer un encart avec :

  • un lien vers office de tourisme
  • la météo de la commune
  • une mini-description de la commune

La macro {@place.description} renvoie juste le contenu du champ description de la table dir_Places. Par contre, la macro {@place.htmlDescription} génère du code html autour pour intégrer la description :

  • <p class="infodesc">la description de la commune ...</p>
  • <blockquote><p>une citation sur la commune ...<cite>l'auteur de la citation</cite></p></blockquote>

La macro {@place.htmlDescription} détermine automatiquement que le champ description contient une citation lorsqu'il y a un séparateur pipe | entre la citation et l'auteur.

Par ailleurs, la description brute est utilisée pour la balise meta description de la page. C'est ce qui est conseillé par les outils Google pour les webmasters pour avoir moins de pages avec la même description.

Les liens

Je ne fais plus apparaitre de publicité à l'intérieur des liens lorsqu'ils sont affichés sous forme minimisée. Parce que ça faisait trop de publicité dans la page par rapport au contenu texte. Ce qui faisait que dans certains cas, Google laissait vide le bandeau latéral sur la droite.

Il y a maintenant un champ displayMode dans la table dir_Tags pour choisir comment présenter la liste des liens d'un tag :

  • Défaut : initialisé à 0 pour les tags qui ont moins de 100 liens
  • Par commune : initialisé à 1 pour les tags avec au moins 100 liens

La présentation par commune affiche les tags en les classant par commune, comme on peut par exemple le voir dans la page des restos de l'Ardèche. Les pages concernées sont beaucoup plus légères qu'avant et je trouve que c'est plus pratique à lire et pour y trouver d'un coup d'oeil les sites d'une commune.

En vrac

Il est maintenant possible de modifier un marché (avant il fallait aller taper dans la base de données).

J'ai modifié la présentation des évènements. Maintenant ça fait vraiment trop comme dans les sites internet pour de vrai ! (mais y'a que moi qui en profite).

J'ai optimisé pas mal de code en remplaçant de nombreuses variables string par des StringBuilder.

La très vielle AjaxBox (3 ans et demi quand même) a complètement été évincée au profit du tout nouveau AutoComplete (en attendant jQuery peut être un jour ou l'autre).

Sinon, j'avais commencé à ajouter du code pour gérer la version 2 des Pages d'Infos (avec notamment une nouvelle charte graphique plus moderne), mais ça fait un bon bout de temps que je n'ai rien fait dessus.

Les modifications de AP (Altrr-Press)

Moins de code

Pour résumer, l'objectif général de toutes les modifications apportées à Altrr-Press c'est de diminuer la quantité de code (si besoin en réduisant les fonctionnalités) et d'améliorer la qualité du code restant (ou en tout cas de le simplifier au maximum). Tout ça en vue d'arriver à faire une version 1 avant de passer très vide à une version 2 qui tournerait sous ASP.NET 2.

J'ai définitivement supprimé les boites FlashFile, Image, Redirect, Sitemap et Sitemenu qui ne sont normalement plus utilisées depuis pas mal de temps (ou alors vous êtes vraiment à la traine).

J'ai aussi supprimé la boite SqlRepeat qui ne servait que sur le site de Saint-Privat (et la preuve que je suis prêt à tout pour diminuer la quantité de code, c'est que j'ai utilisé du XML pour la remplacer).

Pour plus d'explications, vous pouvez vous reporter au billet Régime XML.

Un nouveau Wysiwyg

J'ai aussi définitivement remplacé l'éditeur wysiwyg widgEditor par Altrr-Editt, un super nouveau éditeur wysiwyg que j'ai fait moi-même en reprenant des morceaux de widgEditor à la sauce jQuery.

Il est plus mieux bien, vous pouvez me faire confiance, même s'il faudra attendre encore un petit peu pour que je trouve le temps de vous expliquer pourquoi.

Mise à jour des macros

Désormais, toutes les macros de AP sont sous la forme [%AP.Xxxxxx.Yyyyyy%]. Pour rappel, les macros c'est les trucs qu'on peut utiliser dans les chartes graphiques ou les boites RawContent. Avant, on avait vraiment de tout et n'importe quoi :

  • {@department.xxxx} pour les informations sur le département
  • <%= siteMacro("xxxxx") %> dans les chartes graphiques
  • et même {@public.start} et {@public.end} pour encadrer les scripts de statistiques

Pour l'instant, le source de default.aspx.cs contient encore les fonctions siteMacro() et RenderLegacy() pour gérer toutes les anciennes syntaxes, mais je compte bien m'en débarrasser le plus vide possible.

A noter : à part {@department.xxxx} qui est devenu [%AP.Department.Xxxxx%], toutes les autres macros de PI utilisées dans les templates /data/##/_xxxxx.txt sont restées sous la forme {@objet.propriete}.

Et accessoirement, les macros ne sont plus statiques (je me demande bien comment il pouvait y avoir du statique là dedans ?). A mon avis, il y a une petite chance que ça permette de résoudre les plantages pour le Gîte de Julie.

J'ai aussi ajouté les macros [%AP.Link.Next%] et [%AP.Link.Previous%] pour générer un lien vers la page suivante ou la page précédente à l'intérieur d'un même niveau d'arborescence. Vous pouvez aller voir les 2 galeries de photos sur le site de Saint-Privat pour voir ce que ça donne. Au passage, ces 2 galeries contiennent du XML, saurez-vous le retrouver ?

Poliçage du code

J'ai aussi essayé de passer les sources d'Altrr-Press à la moulinette StyleCop (c'est un programme made in Microsoft pour valider l'organisation et le style des sources C#), mais c'est pas simple parce qu'il faut faire les corrections à la main et qu'en plus ils prônent un style qui n'est pas très "AP like" (surtout pour les { et les } sur des lignes séparées !!!).

Malgré tout, il y a quelques trucs intéressants :

  • utiliser string.Empty au lieu de ""
  • mettre les using à l'intérieur du namespace (et pas une fois l'un et une fois l'autre)
  • commenter beaucoup plus (tout commenter en fait)
  • espacer pour que ça soit plus clair (ajouter des lignes vides)
  • utiliser this.xxxx
  • return xxxx; et pas return (xxxx);
  • etc...

Sur BDHlper.cs où j'ai fait le plus de travail il y a encore 392 avertissements (heureusement y'en a beaucoup qui sont liés les uns aux autres).

Pour la suite

J'ai le début d'un autre billet en préparation sur tout ce qu'il est nécessaire de prévoir pour faire passer Altrr-Press en ASP.NET 2. Et j'ai peur que ça implique pas mal de trucs...

Et il faudrait aussi graver dans le blog comment je compte faire évoluer Altrr-Press :

  • verrouiller le périmètre de ce qui sera dans la version 1 (idéalement ne plus rien ajouter)
  • version 2 (périmètre identique sous ASP.NET 2) : comment s'y prendre et comment tester
  • les priorités / les indispensables à incorporer dans une version 2.1
  • les idées pour plus loin...

Mais en attendant, il faut aussi que je m'occupe de finaliser les scripts SQL qui permettront aux retardataires de faire une mise à jour quasi sans douleur par rapport à toutes les modifications répertoriées dans ce billet.

vendredi 19 septembre 2008

Regroupement de code

Ces derniers temps (mais pas que finalement), j'avais ajouté vite fait des morceaux de code par ci et par là et petit morceau par petit morceau j'ai fini par me retrouver avec des fonctions qui font un peu peur à voir.

C'est comme ça que la fonction SiteMenu() était devenu un véritable fourre-tout qui servait pour générer beaucoup de choses :

  • les menus : ça ça parait normal
  • le plan du site : une espèce de menu général
  • le sitemap pour Google : un menu général mais généré pas pareil

A l'origine, cette fonction parcourait la liste des écrans et représentait le menu d'une page sous forme de blocs ul / li en tenant compte des autorisations définies.

A côté de ça, il y avait aussi 3 autres procédures qui parcouraient la liste des écrans pour proposer une liste de liens possibles, sauf qu'on y gérait un peu moins bien les autorisations :

  • dans le contrôle SelectLink utilisé dans certaines boites
  • lors de l'initialisation de l'éditeur wysiwyg
  • pour l'interface Ajax de l'éditeur wysiwyg

Au lieu de tout ça, il y a maintenant une nouvelle classe Browse qui est utilisée partout, ce qui permet :

  • de parcourir les écrans de la même façon dans tous les cas,
  • de diminuer la quantité de code (1 procédure au lieu de 4),
  • de dissocier la génération de menu de celle du plan du site et de celle du sitemap XML,
  • de corriger un bug dans l'imbrication des écrans.

Dans le même genre, il y avait aussi plusieurs procédures destinées à parcourir les répertoires et à proposer une liste de fichiers existants :

  • dans les contrôles SelectFile et SelectLink utilisés dans certaines boites
  • pour la liste des images et des documents de l'éditeur wysiwyg
  • pour l'interface Ajax de l'éditeur wysiwyg

Et là aussi, c'était pas géré tout à fait pareil dans chacun des cas.

Même problème => même solution : la classe Browse a été complétée pour permettre aussi de parcourir les fichiers et ainsi remplacer les différentes procédures par une seule façon de faire.

A final, au lieu de plusieurs morceaux de codes dispersés un peu partout, tout est rassemblé dans la classe Browse où on n'a plus que :

  • ArrayList Browse.ScreenList () : liste des écrans filtrés en fonction des autorisations
  • StringBuilder Browse.HtmlSitemap () : génère un plan du site à base de blocs ul /li
  • StringBuilder Browse.XmlSitemap () : génère un sitemap XML pour les moteurs de recherche
  • void Browse.ScreenToList () : initialise les listes d'écrans attendues par les contrôles de saisie
  • void Browse.FileToList () : initialise les listes de fichiers attendues par les contrôles de saisie

Et pour être tout à fait complet, il resterait à fignoler la mise au propre de la fonction SiteMenu() et pourquoi pas l'intégrer directement à la classe Browse.

vendredi 22 août 2008

Régime XML

Cet été, j'ai eu envie d'un Altrr-Press tout beau, tout propre pour qu'il soit encore plus simple à utiliser et aussi un peu plus prêt pour le grand jour du passage sous .Net 2.0 (ou plus) : moins de code = moins de problèmes lors de la conversion.

Pour commencer, je me suis enfin décidé à supprimer les sources d'un certain nombre de boites devenues inutiles mais dont les codes sources avaient été épargnés (voire un peu oubliés) en attendant que tous les sites existant soient mis à jour.

  • FlashFile : insérait une animation flash (remplacé par IncludeFile)
  • Image : insérait une image (remplacé par IncludeFile)
  • Redirect : redirige le client vers une autre URL (remplacé par la macro [%AP.Redirect:url%])
  • Sitemap : insérait le plan du site (remplacé par la macro [%AP.Site.Map%])
  • Sitemenu : menu ou sous-menu de navigation (remplacé par la macro [%AP.Site.Menu:level1-level2%])

Note : si vous connaissez des sites qui ont encore besoin de ces boites, 20071029_update.sql et 20071030_update.sql sont vos amis.

Pour continuer sur ma lancée, j'ai aussi cherché comment éliminer la boite SqlRepeat qui prend le résultat d'une requête sous forme de DataReader pour générer un tableau HTML à l'aide d'un système de template très primaire.

D'abord, il y a le template d'en-tête :

<table class='repertoire'>

Puis le template pour le détail des lignes :

<tr>
  <td class='col1'>{0}<br /></td>
  <td class='col2'>{1} {3} {2}<br />{4}</td>
  <td class='col3'>Tél:&amp;nbsp; {7}<br />Fax: {8}</td>
</tr>

Et pour finir celui pour la fin du tableau :

</table>

Et côté code, en simplifiant ça donne à peu près ça :

StringBuilder html = new StringBuilder();
html.Append(templateHead);
DataReader dr = Data.Base.ExecuteReader(sqlQuery);
while (dr.Read()) {
  html.Append(ToHtmlRow(dr, templateLoop));
}
dr.Close();
html.Append(templateFoot);

Pour chaque ligne du DataReader, la fonction ToHtmlRow() génère une ligne en remplaçant les marqueurs {#} par la colonne correspondante dans la ligne en cours DataReader.

Le but étant de supprimer une boite, il faut donc trouver par quoi la remplacer. Le plus simple est de passer par la boite à tout faire RawContent et d'y coller le tableau HTML final. Etant donné que cette boite n'est utilisée que sur le site de Saint-Privat, cela semble une solution raisonnable. Malgré tout, la boite RawContent est plutôt destinée aux petits bouts de codes de quelques lignes et là les tableaux HTML dépassent la toise.

Alors j'ai cherché s'il n'y aurait pas une autre méthode. Et TILT : XML. Ah ben ZUT alors!

Normalement, je préfère éviter d'utiliser le XML (et surtout le XSLT) parce que si c'est toujours assez facile quand on commence, on se retrouve vite avec des sacs de noeuds pour faire trois fois rien. (Ou alors il faut recourir à un collègue qui parle le XML couramment mais que quand il a fini j'ai pas toujours compris.) Mais là, l'envie de me débarrasser d'une boite quasiment inutilisée étant la plus forte, j'ai succombé. Et en fait, ça n'a pas été aussi compliqué que ce que je craignais.

Un premier bon point, c'est qu'il existe déjà une boite XmlFile qui prend un fichier XML pour générer du HTML en lui appliquant un fichier XSLT. C'est un truc qui existe depuis toujours dans IBuySpy et que j'ai adapté dans Altrr-Press. Dans la pratique, je ne m'en sers presque jamais, sauf de temps en temps pour afficher des fils RSS ou Atom ou bien des liens enregistrés dans Blogmarks.

Dans le cas présent, il fallait donc que je me débrouille pour prendre le résultat de ma requête comme source XML et surtout que je réussisse à créer un fichier XSLT qui transforme ce résultat en tableau HTML.

Pour la source XML, c'était vraiment trop facile : quelques minutes à passer dans l'aide et 2 lignes de codes ont suffit (merci le .Net) :

DataSet ds = Data.Base.ExecuteDataSet(sqlQuery);
string xml = ds.GetXml();

Ce qui donne :

<NewDataSet>
  <Table>
    <categories>Electricien</categories>
    <organization>Ets Martin</organization>
    <legalName />
    <givenName />
    <streetAddress>Le Pré-Vert</streetAddress>
    <postalCode>12345</postalCode>
    <placeName>Enville</placeName>
    <workPhone>01 02 03 04 05</workPhone>
    <faxPhone />
  </Table>
  ...
</NewDataSet>

Puis en partant de fichiers XSLT existants et après pas mal d'essais et de messages d'erreurs, j'ai réussi à obtenir un fichier XSLT qui reproduisait le résultat de mon futur ex-système de template :

<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
  <xsl:template match="/">
    <table class='repertoire'>
    <xsl:for-each select='NewDataSet/Table'>
      <tr>
        <td class='col1'>
          <xsl:value-of select='categories'/>
          <br />
        </td>
        <td class='col2'>
          <xsl:value-of select='organization'/>
          <xsl:if test="givenName != ''">
            <xsl:if test="organization != ''">
              <xsl:text> - </xsl:text>
            </xsl:if>
            <xsl:value-of select='givenName'/>
            <xsl:text> </xsl:text>
            <xsl:value-of select='legalName'/>
          </xsl:if>
          <br />
          <xsl:value-of select='streetAddress'/>
        </td>
        <td class='col3'>
          Tél: <xsl:value-of select='workPhone'/>
          <br />
          <xsl:if test="faxPhone != ''">Fax: <xsl:value-of select='faxPhone'/></xsl:if>
        </td>
      </tr>
    </xsl:for-each>
    </table>
  </xsl:template>
</xsl:stylesheet>

Et même que grâce à l'instruction <code><xsl:if test="faxPhone != ''"> ... </xsl:if></code>, le résultat obtenu est encore mieux que l'original. Si je ne me retenais pas, je passerais tout en XML.

Mais pour l'instant, je vais me contenter de supprimer la boite SqlRepeat et ses 3 fichiers sources devenus inutiles. C'est toujours ça de pris (et au final ça fait quand même 6 boites de moins !).