blog.pagesd.info

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

lundi 26 avril 2010

Nouvelle CSS pour AP

Ca fait une éternité que j'ai envie de donner un coup de jeune à la charte graphique de 07 Ardèche qui date des tous premiers jours du site et qui n'a quasiment jamais évolué depuis, si ce n'est pour gérer de plus en plus de trucs.

L'idéal ça aurait été de la remplacer complètement, mais ça demande beaucoup de travail. Et à chaque fois que j'essaie de m'y mettre, je n'ai pas suffisamment de temps libre pour m'occuper de tous les aspects du site, ce qui fait que malgré plusieurs démarrages, rien ne bouge...

Par conséquent, ce coup-ci j'ai préféré prendre le problème à l'envers et plutôt que de repartir à zéro pour tout changer, j'y suis allé par petites touches même si le résultat final est moins spectaculaire.

Améliorer la lisibilité

Pour commencer, j'ai défini une couleur fond différente pour le body (gris très clair) et la partie contenu (blanc). Pour cela, j'ai utilisé une image de fond un peu plus large que le contenu ce qui donne l'impression qu'il existe une bordure assez grande autour du contenu. Le but c'est d'essayer de donner un effet un peu moins tassé aux pages.

Pour continuer à "alléger" les pages et rendre leur contenu un peu plus lisible, j'ai cherché à mieux faire ressortir les différents types de contenu. Jusqu'à présent, il n'y avait en gros que 3 couleurs dans tout le site :

  • du noir pour le texte
  • du bleu pour les liens et certains titres
  • du vert pour quelques titres

J'ai donc décidé de revenir à plus de simplicité et d'être plus strict en réservant le bleu aux liens. Et pour les titres, je suis parti avec du gris qui est ma couleur du moment pour tout ce qui est titre. Après avoir appliqué ce même gris aux deux barres de séparation en haut et en bas de page, le contenu des pages me parait un peu moins fouillis.

Toujours pour que le texte soit plus simple et j'espère plus lisible, j'ai fait disparaitre tous les effets de décalage et d'alinéa qui pouvaient exister sur certaines pages et j'ai un peu dé-complexifié les styles pour la navigation dans la partie blogue.

J'ai terminé en simplifiant le contenu de la feuille de style. Globalement, cela a consisté à supprimer tout plein de vieux code CSS qui ne servait plus trop à rien mais qui continuait à trainer dans la feuille de style :

  • l'ancien système de pictogrammes utilisé dans les premiers mois du site
  • des bidouilles issues de Skidoo Lean pour gérer au mieux IE 5, IE 5.5, IE Mac et FF 0.#
  • la mise en évidence des mot-clés recherchés quand on arrivait d'un moteur de recherche (là j'ai aussi dû modifier le traitement dans les sources du programme)

Augmenter les espacements

Après avoir mis ces premières modifications en production et vérifié pendant quelque jours que ça ne posait pas de problème, j'ai pu m'attaquer à une seconde vague d'évolutions.

Jusqu'à présent, les différents contenu étaient un peu collés les uns aux autres. Par exemple, il n'y avait qu'un espacement de 10 pixels entre le contenu principal des pages et le bandeau latéral qui contient les pictogrammes, les sous-menus et autres publicités.

Au fil du temps, j'avais donc plus ou moins mis en place les 3 types de disposition suivantes :

  • 2 colonnes =>
    • contenu principal (790 pixels) + espacement (10 pixels) + bandeau latéral (160 pixels)
    • 1° demi colonne (390 pixels) + espacement (10 pixels) + 2° demi colonne (390 pixels) + bandeau latéral (160 pixels)
  • 3 colonnes => 1° colonne (480 pixels) + espacement (10 pixels) + 2° colonne (300 pixels) + espacement (10 pixels) + bandeau latéral (160 pixels)

Le premier truc, pour aérer les pages et faire moins tassé, c'était d'augmenter tous les espacements de 10 à 20 pixels, en essayant néanmoins de respecter deux impératifs :

  • rester dans la limite des 960 pixels
  • commencer à aller vers un système de grid

Finalement, après beaucoup de calculs et la tentation de dépasser les 960 pixels, j'ai réussi à opter pour un système de grid avec des colonnes de 180 pixels :

  • 180 px + 20 px + 180 px + 20 px + 180 px + 20 px + 180 px + 20px + 160px

J'ai donc dû un peu tricher avec la dernière colonne mais de toute façon 160 pixels c'est pile ce dont j'ai besoin pour le bandeau AdSense vertical.

Et ça me permet de gérer le mode 2 colonnes très simplement :

  • contenu principal (780 pixels) + espacement (20 pixels) + bandeau latéral (160 pixels)
  • 1° demi colonne (380 pixels) + espacement (20 pixels) + 2° demi colonne (380 pixels) + bandeau latéral (160 pixels)

L'avantage avec ça, c'est que j'arrive même à avoir les 4 colonnes nécessaires pour afficher certaines pages de l'annuaire où les sites sont classés par commune et se présentent alors sur 4 colonnes.

Malgré tout, il reste encore une disposition un peu exotique pour les pages sur 3 colonnes :

  • 1° colonne (480 pixels) + espacement (20 pixels) + 2° colonne (280 pixels) + espacement (20 pixels) + bandeau latéral (160 pixels)

Une fois ces nouvelles mesures décidées, il a fallu mettre à jour les CSS pour que tout rentre dans le moule. Ca n'a pas été facile facile à faire. J'ai dû y aller petit morceau par petit morceau et m'y reprendre à plusieurs fois avant d'en venir à bout. Et j'ai dû passer par des tas de background-color: pink, yellow, green, blue ... pour bien vérifier que chaque morceau était bien cadré comme il fallait et que rien ne sortait des clous.

Après ça, je me suis un peu reposé en me contentant d'espacer légèrement l'en-tête et le pied de page et en simplifiant certains bouts de CSS par ci ou par là.

Fignoler la présentation

Pour finir, je suis revenu sur le système que j'avais utilisé au début pour donner un effet de large bordure autour du contenu des pages.

La largeur des pages est fixée à 960 pixels, ce qui ne laisse pas de place pour des marges de 20 pixels à gauche et à droite. Par conséquent, pour simuler ces marges, j'ai donc utilisé une image de fond de 1000 pixels de large que j'applique à l'élément body :

body
{
        background: #eee url(bg1000.gif) repeat-y center top;
        color: #000;
        margin: 0;
        padding: 0;
        text-align: center;
}

#pageWrapper
{
        margin: auto;
        padding: 0;
        width: 960px;
        text-align: left;
}

L'inconvénient, c'est qu'avec ce système le fond blanc commence en haut de l'écran pour finir tout en bas de l'écran alors que j'aurais bien voulu avoir un petit espacement d'une dizaine de pixels en haut et en bas.

Après avoir essayé de jouer sans succès avec un effet de margin ou de padding vertical sur l'élément body, j'avais bricolé les border pour parvenir à ce que je souhaitais faire :

body
{
        background: #eee url(bg1000.gif) repeat-y center top;
        border-bottom: solid 10px #eee;
        border-top: solid 10px #eee;
        color: #000;
        margin: 0;
        padding: 0;
        text-align: center;
}

Le problème, c'est qu'avec les écrans 21 pouces ou plus, on avait le border à la fin du contenu (sous le pied de page) puis on retrouvait l'image de fond qui se répétait jusqu'en bas de l'écran :). J'ai donc dû changer de méthode et finalement, je me suis rendu compte qu'il suffisait de gérer cette bordure au niveau de l'élément html.

html
{
        background-color: #eee;
        color: #000;
        padding: 10px 0;
}

Idées pour la suite

Même si à première vue les nombreuses modifications apportées ne sautent pas aux yeux, je suis malgré tout extrêmement satisfait des résultats que j'ai obtenus :

  • le contenu est moins tassé et plus lisible qu'avant,
  • la structure et la taille des pages sont plus homogènes,
  • le code CSS a été pas mal nettoyé.

Pour l'avenir, j'aimerai faire rentrer le mode 3 colonnes dans le rang en faisant en sorte que la colonne du milieu passe de 280 pixels à 180 pixels. Pour cela, il faudra aussi revoir la façon de l'utiliser et très certainement le code source de l'application pour qu'elle gère moins de contenu et qu'elle ait plus un rôle de sous-menu géant.

Mais à priori, cela devrait être tout à fait jouable pour les différentes parties du site qui ont besoin de cette disposition en 3 colonnes :

  • la météo
  • les offices de tourisme
  • les randonnées

Une autre évolution, ça serait de voir si maintenant que mon code est un peu plus carré je peux réussir à remplacer la base Skidoo Lean qui commence à dater par un framework CSS plus récent.

Et si en plus de tout ça je continue à simplifier suffisamment mon code CSS et à gérer moins de cas particuliers, peut-être que j'arriverai un jour à faire un changement de charte graphique beaucoup plus radical et visible.

lundi 19 octobre 2009

Amélioration du blog de l'Ardèche

Cette fois-ci, j'ai cherché à améliorer le blog de l'Ardèche. Jusqu'à présent, c'était presque plus proche d'une saisie de pages à la chaine que d'un véritable blog digne de ce nom.

Dans un premier temps, j'ai fait évoluer la partie administration du blog. J'ai amélioré le contrôle utilisateur employé pour le wysiwyg afin qu'il soit plus adapté lors de la saisie du résumé des billets. Et j'en ai profité pour gérer de façon plus cohérente le contrôle du code html saisi, notamment en ce qui concerne la vérification des urls relatives.

Comme pour l'instant j'utilise surtout le blog pour saisir des informations destinées à l'agenda, j'ai décidé d'ajouter la saisie d'une période de dates à chaque billet. Ce n'est peut-être pas très orthodoxe, mais au moins je peux faire ressortir cette période lors de l'affichage des billets.

J'ai aussi fait quelques essais pour générer en automatique le résumé des billets en fonction des premières lignes du billet complet. Mais il faudrait que je revoie ça autrement. Mais au moins, je suis certain de toujours avoir un résumé (ou plutôt une accroche) que je peux utiliser pour définir la balise <description> de la page dans laquelle est publiée le billet.

Après m'être occupé de mes besoins, je me suis consacré à ceux des visiteurs. Pour commencer (et pour me faire un peu plaisir), j'ai mis en place le format hAtom pour publier les billets du blog.

<div class="hentry" id="post-23">
  <h3 class="entry-title">
    <a href="/blog/2009/10/2eme-festival-soupes-pays-ardeche-meridionale.aspx" rel="bookmark">2ème Festival des Soupes du Pays de l'Ardèche Méridionale</a>
  </h3>
  <p class="published" title="2009-10-17T14:25:32">Publié le samedi 17 octobre 2009, 14:25.</p>
  <div class="entry-content">
    ...
  </div>
</div>

Puis j'ai travaillé à la navigation. Jusqu'à présent, on pouvait consulter le contenu d'un billet mais une fois là, il fallait savoir se débrouiller pour poursuivre la consultation du blog ou du site. C'est maintenant de l'histoire ancienne puisque j'ai ajouté des liens vers le billet précédent et vers le billet suivant ainsi qu'un lien qui permet de revenir à l'index des billets du blog.

Et en ce qui concerne cette page principale du blog, comme elle commençait à atteindre une certaine longueur, je lui ai ajouté un système de pagination pour présenter une dizaine de billets dans chaque page. Et puis finalement, j'ai préféré faire apparaitre les billets complets dans les pages d'archives comme c'est le cas avec Dotclear. Par conséquent, j'ai dû réviser le nombre de billets par page à la baisse et pour l'instant j'en fait apparaitre cinq par page.

Et pour finir, j'ai ajouté un fil Atom des billets du blog. J'ai fait ça un peu à la barbare (à coup de StringBuilder) et pour compenser, j'ai utilisé FeedBurner pour le publier : http://feeds.feedburner.com/07-ardeche. Après ça, je n'ai eu qu'à ajouter la ligne suivante à mon template pour activer la découverte automatique de mon nouveau fil Atom :

<link rel="alternate" type="application/atom+xml" title="Atom 1.0" href="http://feeds.feedburner.com/07-ardeche" />

jeudi 15 octobre 2009

Faire un site pour rien

Jusqu'à présent, quand on essayait de remonter à la racine des sites hébergés sur le domaine pagesd.info, le navigateur renvoyait une erreur indiquant qu'il ne parvenait pas à trouver le site. Ce qui était plutôt normal puisqu'il n'existait pas de site défini pour l'adresse http://www.pagesd.info/.

Mais tout ça c'est du passé, parce que ce soir je me suis accordé une petite pause pour coder une page qui évite de faire mauvais effet avec une erreur de DNS. Après avoir trouvé une jolie image sur le site de Dry Icons, j'ai maintenant la joie d'avoir un nouveau site en production :

Ceci n'est pas un site

Blogmarks du 14/10/2009

  • The IBuySpy Portal architecture (PDF) :

    In this book, we'll be using the freely available IBuySpy Portal as a starting point for our intranet development. We'll look at both why we are modifying an existing intranet application rather than creating our own, and why we choose the IBuySpy Portal in particular. Once we have covered the basics, we'll take a tour of its features, looking at the files and types it consists of, how they function and fit together, and general principles behind the site. Finally, we'll take a brief look at how security is handled.

    .net - pour-memoire - cms.net

lundi 5 octobre 2009

Mise à jour des OT de l'Ardèche

Toujours dans l'optique d'améliorer le contenu de mon site sur l'Ardèche et faire plaisir à Google pour qu'il m'envoie toujours plus de visiteurs, je me suis cette fois-ci attelé à la cinquantaine de pages consacrées aux offices de tourisme de ardéchois.

Pour commencer, je suis repassé sur les quelques 50 écrans pour ajouter une description qui soit spécifique à chacun d'entre eux. Pour cela, j'ai fait au plus simple avec des variations autour de "Présentation de (l'antenne de) l'office de tourisme Xxxxxx à / dans la région de Xxxxxxx : informations, coordonnées, horaire d'ouverture..."

Dans un second temps, j'ai repris la page principale des offices de tourisme pour la faire évoluer en lui apportant quelques petites modifications :

  • mise à jour de son contenu pour tenir compte du fait que l'ex "Comité du Tourisme" s'est transformé en "Agence de Développement Touristique" (ça fait un peu ex pays de l'Est), ce qui s'est accompagné d'un changement de logo et d'une nouvelle adresse mél.
  • ajout d'un petit texte de présentation de chaque "Territoire d'Accueil et de Consommation Touristique" (ça fait ...) ou TACT, qui regroupe les différents offices de tourisme de chacune des 4 grandes régions touristiques de l'Ardèche

En attendant de réaliser des pages spécifiques pour ces 4 TACT, j'ai d'ores et déjà fait évoluer la fiche de chaque office du tourisme pour indiquer à quel TACT il est rattaché (même si pour l'instant je ne fait rien du tout de cette information). Comme pour cela j'ai eu à faire évoluer le formulaire de mise à jour des OT, j'en ai également profité pour transformer la façon de saisir la présentation et les heures d'ouverture de l'OT :

  • agrandissement des colonnes destinées à enregistrer ces deux informations
  • ajout d'un mini formulaire dédié qui propose un éditeur wysiwyg permettant de modifier ces deux infos uniquement, sans avoir à passer par la fiche complète de l'OT

Une fois cette mise à jour réalisée, j'ai attaqué la partie difficile, à savoir procéder à la mise à jour de ma base de données des OT ardéchois. Pour cela, j'ai fait une synthèse entre mes données actuelles, les informations disponibles dans les documentations fournies par l'ADT et les renseignements proposés sur les sites internet des différents OT.

Pour l'instant, je me suis concentré sur la mise à jour des renseignements suivants :

  • le nom de l'OT,
  • une petite description pour présenter la région,
  • le TACT auquel appartient l'OT,
  • le heures d'ouverture,
  • la liste des communes adhérentes.

Au passage, c'est quand même assez fou le nombre de fois où il est difficile (voire impossible) de trouver les coordonnées ou les heures d'ouvertures sur leur site internet...

Ca n'a pas été facile, mais au final j'ai maintenant une base de données asse cohérente (au moins jusqu'à l'année prochaine) et j'ai même réussi à récupérer 4 ou 5 photos supplémentaires pour illustrer la situation de l'office de tourisme. Après ce travail un peu fastidieux, j'ai reporté ces mises à jour dans la table des communes en re-générant le lien vers l'office de tourisme qui est enregistré dans les fiches communes.

Pour la suite, j'espère avoir bientôt un peu plus de temps pour compléter le fichier des offices de tourisme avec des informations sur les visites guidées qu'ils organisent et éventuellement sur le catalogue de topo-guides de randonnées que l'on peut y trouver. Mais ça sera peut être un peu difficile à caser car cela risque de prendre beaucoup de temps.

Ce qui semble plus donc réalisable, c'est de créer les 4 pages spéciales pour chacun des TACT, où dans un premier temps je pourrais faire apparaitre la liste des OT concernés, avec peut-être une mini carte du secteur... Une fois cet ajout terminé, je pourrais presque supprimer la liste de tous les OT qui apparait actuellement sur toutes les pages et la remplacer par une mini-liste qui ne contiendrait que les quatre TACT avec leur logo respectif.

jeudi 1 octobre 2009

ASP.NET 2.0 et Googlebot

Suite à mes différentes petites améliorations, je commence à avoir quelques résultats. Déjà, il n'y a plus de pages avec des titres en double et le nombre de pages ayant la même description commence à diminuer.

Par contre, il semblerait que Google ne parvient pas à accéder aux pages du blog et qu'il se retrouve à chaque fois avec une erreur du type "Network unreachable".

Au début, je ne me suis pas trop méfié parce que lors de la première installation, il y avait eu une petite erreur pour générer le sitemap. Mais étant donné que cela continue de se produire, j'ai quand même fini par essayer de voir d'où cela pouvait provenir.

Après quelques recherches, il semble que ce problème soit lié à l'url-rewriting et au au changement du user agent de Googlebot (vers mars 2006 !) qui est passé de "Googlebot/2.1 (+http://www.googlebot.com/bot.html)" à "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)". Et apparemment cela perturbe ASP.NET (la version 2.0 seulement ?) dans sa façon de détecter les caractéristiques du navigateur appelant, si bien qu'il finit par faire comme s'il s'agissait un très vieux Mozilla/1 et par faire un peu n'importe quoi...

Deux billets pour avoir des explications plus complètes :

J'ai pu vérifier ce problème par moi-même en utilisant Safari et en le configurant pour qu'il déclare le même user agent que Googlebot. Grâce à quoi, quand j'accédais aux pages du blog, je tombais moi aussi sur une magnifique erreur "Cannot use a leading .. to exit above the top directory" :

[HttpException (0x80004005): Cannot use a leading .. to exit above the top directory.]
   System.Web.Util.UrlPath.ReduceVirtualPath(String path) +3626102
   System.Web.Util.UrlPath.Reduce(String path) +84
   System.Web.Util.UrlPath.Combine(String appPath, String basepath, String relative) +326
   System.Web.HttpResponse.ApplyAppPathModifier(String virtualPath) +209
   System.Web.UI.HtmlControls.HtmlForm.GetActionAttribute() +2036998
   System.Web.UI.HtmlControls.HtmlForm.RenderAttributes(HtmlTextWriter writer) +840
   System.Web.UI.HtmlControls.HtmlControl.RenderBeginTag(HtmlTextWriter writer) +39
   System.Web.UI.HtmlControls.HtmlForm.Render(HtmlTextWriter output) +56
   System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +25
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +121
   System.Web.UI.HtmlControls.HtmlForm.RenderControl(HtmlTextWriter writer) +37
   System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +199
   System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +20
   System.Web.UI.Page.Render(HtmlTextWriter writer) +26
   ap.Engine._default.Render(HtmlTextWriter writer) +89
   System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +25
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +121
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +22
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +2558

J'ai donc suivi la solution proposée par Brendan qui consiste à ajouter un fichier genericmozilla5.browser dans le répertoire App_Browsers pour détecter correctement les navigateurs compatibles Mozilla/5 génériques. Et après ça, tout est rentré dans l'ordre (au moins pour Safari) et les pages du blog devraient donc être générée à nouveau correctement pour Googlebot.

Ce qui est quand même bizarre, c'est que :

  1. je n'ai eu ce problème de "Network unreachable" qu'avec les pages du blog alors que je fais de l'url rewriting dans tout le site,
  2. je suis affecté alors que même si le site tourne en .NET 2, il est (encore) compilée en .NET 1.1 (d'un autre côté, du moment que le répertoire spécial App_Browsers fonctionne avec une application en .NET 1.1).

Reste plus qu'à espérer qu'avec tout ça je n'ai pas trop épuisé la patience de Goooglebot et qu'il aura la bonté de prendre en compte les pages du blog très très vite ! Mise à jour : ça à marché :)

lundi 21 septembre 2009

Mise à jour des descriptions

J'ai terminé l'ajout des balises descriptions aux pages des tags de l'annuaire dans les délais que j'avais envisagés. Et j'ai même trouvé le temps compléter les descriptions de quelques pages isolées : séances cinéma de l'été, formulaire de contact, plan du site...

Cela m'a d'ailleurs donné l'occasion d'améliorer Altrr-Press pour supprimer les guillemets lors de la saisie des champs description et mots clés dans le formulaire de mise à jour d'un écran. Cela évite de générer ensuite des balises description ou keywords avec des guillemets inopportun.

Pour les pages de l'annuaire contenant les liens, j'ai d'abord regardé comment fonctionnent les autres annuaires et j'ai fait à peu près pareil. Dans ce cas, le consensus semble être de reproduire le début du texte décrivant le site internet lié dans la balise description. L'avantage de cette méthode, c'est que c'est rien que de la programmation et que c'est donc assez rapide à mettre en oeuvre.

Pour suivant sur ma lancée, j'ai aussi commencé à insérer des descriptions pour les pages consacrées aux communes de l'Ardèche. Pour l'instant, je n'en ai fait qu'une bonne soixantaine. Mais en attendant d'avoir traité l'ensemble des 339 communes, j'ai codé un truc pour générer automatiquement une balise description contenant un texte du type "Informations, plan et liens sur la commune de Xxxxxxx en Ardèche".

Après ça, il ne me restera plus qu'à régler le cas du répertoire des offices de tourisme ardéchois pour qu'en théorie chaque page du site ait une balise description différente. Mais dans ce cas, il faudra que je profite de ces modifications pour revoir le contenu de ces pages, et notamment les coordonnées des offices de tourisme.

vendredi 11 septembre 2009

Amélioration du référencement

Je persiste et poursuis mes efforts entamés ce week-end en continuant de travailler à améliorer le référencement de mon site sur l'Ardèche. Par rapport à avant hier, j'ai finalement trouvé une description très convenable pour les pages de cinéma :

  • Pour la page principale j'ai complété ce qui existait pour finir avec : "Programme des cinémas en Ardèche, annuaire des salles de cinéma ardéchoises, les films à l'affiche et les horaires des séances"
  • Et pour les pages spécifiques pour chaque cinéma, cela donne : "Présentation du programme du cinéma NomCinéma à NomCommune : les films à l'affiche et les horaires des séances".

C'est pas très extraordinaire, mais c'est plutôt mieux que ce qu'il y avait jusqu'à présent...

Ensuite, comme je l'avais fait pour certains titre des pages de cinéma, j'ai résolu le même genre de problème de doublon pour les titres des pages concernant les offices de tourisme de l'Ardèche.

Jusqu'à présent, j'utilisais le nom de l'office de tourisme comme titre de la page. Mais il y a un certain nombre d'OT qui ont plusieurs antennes et par conséquent je me retrouvais avec 3 cas où un même titre était attribué à plusieurs pages. J'ai donc du là aussi ajouter le nom de la commune où se situe l'antenne dans le titre de la page pour corriger le problème.

Reste plus à espérer que Google repasse très vite sur ces pages pour que mon problème de "Pages with duplicate title tags" soit de l'histoire ancienne !

Et pour finir, le plus gros de mon travail a consisté à ajouter des descriptions différentes pour chaque tag géré dans l'Annuaire de l'Ardèche. C'est pas une mince affaire et ça m'a pris pas mal de temps. Pour l'instant, j'ai réussi à rédiger une description spécifique pour un bon tiers des 56 tags existants.

Pour bien faire, il faudrait encore que j'en trouve 5 ou 6 d'ici la fin de la semaine et que j'essaie de terminer le tout avant la fin du mois....

mercredi 9 septembre 2009

Des nouvelles de l'Ardèche

Maintenant que Google a tant bien que mal pris en compte mon changement de nom de domaine et qu'il a plus ou moins daigné me renvoyer des visiteurs, j'ai estimé qu'il était temps de mon côté de faire quelques efforts pour améliorer le référencement de 07 - Ardèche (c'est ma bonne résolution de la rentrée).

Pour commencer, je teste deux modifications assez importantes sur la page consacrée à la Météo de l'Ardèche :

  • j'ai essayé de rendre la description de la page un peu plus concrète et attractive : "Consultez la météo de l'Ardèche : le temps du jour et les prévisions météo à 5 jours des principales communes du département".
  • j'ai ajouté un petit texte d'une dizaine de lignes pour expliquer la complexité et la diversité du climat ardéchois.

En ce qui concerne les pages de météo des communes, la description est désormais initialisé de la façon suivante : "Prévisions météo de Xxxxxxxxx (en Ardèche) et de sa région : le temps qu'il fait aujourd'hui et les prévisions météo à 5 jours". Cela permet d'avoir une description bien différente pour chaque page de météo, comme le recommande Google Webmaster Tools.

Pour les Cinémas de l'Ardèche, j'ai déjà fait en sorte que le titres des pages soit bien uniques en ajoutant le nom de la commune à la fin du titre de la page. Grâce à quoi il n'y a plus de doublon dans le titre des cinémas suivants :

Et sinon, toujours dans le cas des pages de cinéma, il faut encore que je mette au point une description qui soit à la fois un peu plus parlante et que je puisse décliner correctement pour chaque salle de cinéma.

Plus généralement, il faudrait que je fasse quelques essais avec le site sur Saint-Privat pour voir ce qu'il est préférable de faire :

  • utiliser la même description pour plusieurs pages
  • laisser la description vide pour éviter d'avoir des doublons

Après ces tâches plutôt triviales, j'ai aussi commencé à faire évoluer l'Agenda de l'Ardèche. Par rapport à ce qui existait jusqu'à présent, je voudrais aller vers quelque chose d'un peu plus complet :

  • donner plus d'informations
  • proposer le programme des théâtres
  • présenter les différents festivals
  • ajouter les foires
  • automatiser la répétition des évènements
  • etc...

Pour l'instant, je suis parti sur l'ajout d'une page de blog très simplifiée et je reprend les billets dans la page de l'agenda. Par rapport à la liste actuelle des évènements, cela offre plusieurs avantages :

  • il y a plus de contenu qu'avant. C'est bon pour le référencement et cela me permet de faire apparaitre plus d'informations : une description beaucoup plus complète, des tarifs, des contacts...
  • quand je reçois des demandes pour faire figurer une manifestation dans l'agenda, la plupart des gens fournissent une description assez détaillée (beaucoup plus d'ailleurs que ceux qui veulent faire référencer leur site !). Par conséquent, cela devrait être facile de transformer ces demandes en billets.
  • Les évènements disparaissent de l'agenda une fois que la date est passée. Avec un fonctionnement de type blog, les billets seront publiés pour l'éternité ce qui m'évitera de "perdre" du contenu (ce qui n'est pas plus mal pour les visites).
  • Cela devrait me permettre de "regrouper" les évènements. Par exemple, je pourrais tenter de rédiger des billets pour présenter la saison 2009 / 2010 des théâtres de l'Ardèche.
  • Si je réussi à publier un billet par semaine en moyenne, j'aurai accumulé une cinquantaine de pages supplémentaires d'ici un an (et donc un certain nombre de visites en plus !). Et comme les différentes manifestations se répètent généralement d'année en année, cela me fera une bonne base pour la suite...

Il faudrait aussi que je réfléchisse sur comment utiliser le blog pour d'autres contenus que l'agenda. Par exemple, j'aimerais m'en servir pour annoncer les séances de cinéma en plein air proposées en été. J'ai encore un peu de temps pour ça d'ici l'année prochaine, mais ça serait pas mal si j'arrivais d'ores et déjà à transformer les films des années passées en billet.

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.

mardi 21 avril 2009

System.Net.Mail et smtp.gmail.com

Poursuivant mes essais de migration d'Altrr-Press vers ASP.NET 2.0, j'ai enfin étudié comment remplacer System.Web.Mail par System.Net.Mail. Jusqu'à présent, j'utilisais la méthode décrite dans mon plus célèbre billet System.Web.Mail and smtp.gmail.com.

Par rapport à cette méthode, les modifications à apporter n'ont finalement pas été si compliquées que ça :

public string sendMail (string from, string to, string cc, string bcc, string subject, string body) {

  // Mail initialization
  MailMessage mail = new MailMessage();
  mail.From = new MailAddress(from);
  mail.To.Add(to);
  if (cc != "") {
    mail.Cc.Add(new MailAddress(cc));
  }
  if (bcc != "") {
    mail.Bcc.Add(new MailAddress(bcc));
  }
  mail.Subject = subject;
  mail.IsBodyHtml = false;
  mail.BodyEncoding = System.Text.Encoding.UTF8;
  mail.Body = body;

  // Smtp configuration
  SmtpClient smtp = new SmtpClient();
  smtp.Host = "smtp.gmail.com";
  // - smtp.gmail.com use smtp authentication
  smtp.Credentials = new NetworkCredential("myemail@gmail.com", "mypassword");
  // - smtp.gmail.com with System.Net.Mail accepts port 25 or 587
  smtp.Port = 25;
  // - smtp.gmail.com use STARTTLS (some clients call this SSL)
  smtp.EnableSsl = true;

  // Mail sending
  try {
    smtp.Send(mail);
    return "";
  } catch (Exception ex) {
    return ex.Message;
  }
}

Une première petite difficulté est venu des propriétés From, To, Cc et Bcc de l'objet MailMessage qui ne sont plus de type chaine comme en .NET 1 mais du type MailAddress ou collection de MailAddress.

Mais le plus gros problème, c'est qu'avec System.Net.Mail, je n'ai jamais réussi à utiliser le port 465 alors qu'il fonctionne très bien avec System.Web.Mail (et même sur un site en .NET 2 qui utilise encore System.Web.Mail !!!). Et comme en local le port 587 n'est pas ouvert, j'ai un peu trainé pour faire mes tests puisque je devais à chaque fois faire une mise en production afin de vérifier si cela fonctionnait.

Mais au final, j'ai quand même découvert qu'avec System.Net.Mail :

  • cela fonctionne aussi bien avec le port 587 qu'avec le port 25 (!)
  • il n'est même pas nécessaire de définir la propriété smtp.Port pour que le mail parte (re !)

jeudi 26 mars 2009

Créer une boite encore plus facilement

Et beaucoup plus vite ! Il y a quelque temps, j'avais déjà un peu simplifié la façon de créer une nouvelle boite dans Altrr-Press, en passant de 7 à 5 étapes :

  1. cliquer sur le bouton Ajouter une boite pour créer une nouvelle boite
  2. laisser les propriétés proposées par défaut
  3. valider pour créer la boite (et passer en saisie de son contenu)
  4. saisir le contenu de la boite
  5. valider la saisie pour enregistrer le contenu

Cette fois-ci, j'ai fait encore mieux ! J'ai carrément court-circuité les 3 premières étapes qui servent à créer la nouvelle boite.

Pour cela, j'ai ajouté un bouton "Ajouter une boite Htmltext" qui crée une boite Htmltext temporaire et passe directement en saisie de son contenu.

Une fois le contenu saisi et validé, la boite Htmltext est d'abord réellement créée puis son contenu est enregistré dans la foulée.

Ce qui fait que maintenant, il n'y a plus que 3 étapes pour ajouter du contenu texte (soit le plus fréquent) dans une page :

  1. cliquer sur le bouton Ajouter une boite Htmltext pour créer une nouvelle boite de type Htmltext
  2. saisir le contenu de la boite
  3. valider la saisie pour créer la boite et enregistrer son contenu

mardi 17 mars 2009

Web Application Project et Web Site Project

Quelques pistes de réflexions pour décider si pour migrer Altrr-Press en 2.0 je reste prudemment sur un projet de type ASP.NET 1.x (Web Application Project) ou si j'y vais franchement et que je saute le pas pour faire un vrai projet de type ASP.NET 2.x (Web Site Project) :

mardi 10 mars 2009

Premier essai pour organiser le plan d'un site

Avant Altrr-Press il y avait QC et avant QC il y avait inPortal. Et à l'époque, il existait un module pour mettre à jour le plan du site.

Ca permettait de déplacer un écran avant ou après un autre écran en cliquant sur les flèches haut et bas et aussi de le changer de niveau en cliquant sur les flèches gauche et droite. Et même si ça devenait un peu compliqué à manipuler dès que le site commençait à avoir un peu trop de pages mais ça rendait bien service.

Pour l'occasion, j'ai ressorti les vieux zip et j'ai reconstitué une page de démonstration, mais sans garantie (ça marchotte sous Firefox parce que c'est tellement vieux qu'en ce temps là j'utilisais encore K-Meleon).

Quand j'ai commencé à travailler sur QC, je me disais toujours qu'il fallait que je refasse une boite pour modifier le plan du site mais soit je n'avais pas envie de faire la même chose qu'avant, soit il y avait d'autres trucs à faire, soit c'était les vacances... Tant et si bien que jusqu'à présent il n'existe toujours rien qui permet de changer l'ordre des pages ou de les ré-organiser dans Altrr-Press.

Actuellement, si l'écran a est avant l'écran b et que l'on veut faire passer l'écran b en première position, il faut :

  • créer un écran c avant l'écran a
  • déplacer dans l'écran c toutes les boites de l'écran b
  • supprimer l'écran b
  • renommer l'écran c en ecran b

L'avantage avec cette méthode c'est que cela oblige à réfléchir 5 minutes au plan du site avant de se mettre à créer les pages.

Mais là, avec tout les trucs qui existent sous jQuery, je me dit qu'il serait quand même temps d'essayer d'offrir une méthode un peu plus pratique pour réorganiser la structure des sites créés avec Altrr-Press.

J'ai donc fait un premier essai avec le composant Sortable de jQuery UI. Pouvoir organiser le plan du site à coup de drag & drop, ça devrait le faire. L'avantage, c'est qu'une fois qu'on a trouvé un exemple qui marche, c'est assez simple à utiliser.

Il faut commencer par insérer tous les fichiers javascript nécessaires :

<script type="text/javascript" src="jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="ui.core.js"></script>
<script type="text/javascript" src="ui.sortable.js"></script>

Puis on appelle la méthode sortable() pour le 1° niveau de la liste que l'on veut pouvoir trier :

<script type="text/javascript">
$(document).ready(function() {
  var sortOpts = {
    items: "li", 
    cursor: "crosshair"
  }
  $("ul.ap-sitemap").sortable(sortOpts);
});
</script>

C'est déjà pas mal, mais :

  • ça tremblote un peu quand on est en mode drag
  • on a du mal à prévoir où on va dropper

Si je rajoute un peu de CSS et que je met en évidence la zone où le drop va avoir lieu :

C'est déjà plus clair, mais je reste un peu sur ma faim :

  • c'est mieux que rien mais sans plus... je m'attendais à un effet plus spectaculaire
  • je n'arrive pas à créer un sous-niveau : pendant un moment j'ai pensé que la propriété dropOnEmpty me le permettrait mais apparemment ça n'est pas ça.
  • et en plus, il faudra que je cherche comment limiter le nombre de sous-niveau à 3

A creuser donc...

Note : la liste que j'utilise en exemple est tirée de l'article Complex Dynamic Lists: Your Order Please de Christian Heilmann et publié sur A List Apart.

mercredi 4 mars 2009

Limiter le nombre de lignes dans un fil Atom

La boite XmlFile d'Altrr-Press permet depuis quelque temps d'afficher le contenu d'un flux RSS, RDF ou Atom en indiquant l'url du fil de syndication et la feuille de style XSLT à lui appliquer :

Par défaut, ces feuilles de styles XSLT affichent le titre du billet suivi de son contenu. Mais dans Altrr-Press, il existe aussi une version qui affiche une simple liste <ul> / <li> en présentant uniquement le titre des billets.

Lorsque le fil d'origine contient beaucoup de billets, le résultat n'est pas toujours heureux car cela génère un assez long pavé . C'est par exemple le cas avec un Gandiblog (et aussi Dotclear ?) pour lequel le flux de syndication contient 20 billets par défaut.

Il fallait donc trouver une méthode pour ne faire apparaitre que les 5 premiers billets du fil (ce qui correspond donc aux 5 derniers billets publiés) :

  • contacter l'auteur du blogue et lui demander s'il vous plait s'il peut limiter son fil Atom à 5 billets, merci.
  • se débrouiller et trouver comment gérer ça de mon côté

Avant de faire le barbare avec un style "overflow: hidden;" pour cacher les billets que je ne saurais voir, j'ai fait chat et demandé à un spécialiste XSLT s'il existerait un truc pour faire ça. Et ben oui, et pour une fois c'est même carrément simple.

Au lieu d'appliquer bêtement le template aux différents items du fil Atom :

<ul>
        <xsl:apply-templates select="atom:entry"/>
</ul>

Il faut indiquer que le template ne doit s'appliquer que pour les 5 premières entrées :

<ul>
        <xsl:apply-templates select="atom:entry[position() &lt;= 5]"/>
</ul>

L'astuce, c'est que comme le fichier XSLT est lui-même un document XML, il faut penser à échapper le signe inférieur et écrire &lt; au lieu du caractère <.

jeudi 26 février 2009

Requêtes SQL pour la mise à jour d'Altrr-Press

En attendant d'adapter pour Altrr-Press ma documentation pour développer des boites qui date de QC, je vais commencer par faire un petit récapitulatif afin de passer en revue les scripts SQL utiles pour mettre à jour le contenu d'une base de données Altrr-Press.

Cela concerne généralement les données de configuration qu'il n'est pas possible de mettre à jour depuis l'interface. Mais cela peut aussi être utile d'avoir des scripts pour créer des écrans ou des boites de façon à faciliter la mise à jour de sites en production.

Supposons que l'on vienne de finir de développer un nouveau type de boite TypeToto . Si on essaie de créer une nouvelle boite, le choix TypeToto n'apparait pas encore. C'est dû au fait que le type de boite TypeToto n'existe pas dans la table qc_BoxTypes.

Il faut donc ajouter le nouveau type TypeToto dans la table qc_BoxTypes à l'aide de la requête suivante :

/* ---------- Ajoute le type de boite TypeToto
*/

INSERT INTO qc_BoxTypes (
    idBoxType, 
    title, 
    description, 
    ctrlSource, 
    cnfgSource, 
    editSource, 
    defaultCache, 
    toolbox)
VALUES (
    'typetoto', 
    'TypeToto', 
    'La boite TypeToto', 
    '~/Classic/TypeToto/viewTypeToto.ascx', 
    '', 
    '~/Classic/TypeToto/editTypeToto.ascx', 
    '600', 
    'Classic');
GO;

Description des colonnes de la table qc_BoxTypes :

  • idBoxType = typetoto : l'identifiant du type de boite
  • title = TypeToto : le nom du type de boite
  • description = La boite TypeToto : description du type de boite
  • ctrlSource = ~/Classic/TypeToto/viewTypeToto.ascx : le contrôle pour afficher la boite
  • cnfgSource = (vide) : le contrôle pour configurer la boite
  • editSource = ~/Classic/TypeToto/editTypeToto.ascx : le contrôle pour modifier le contenu de la boite
  • defaultCache = 600 : la durée du cache pour ce type de boite (mettre -1 pour interdire la mise en cache)
  • toolbox = Classic : la boite à outils où regrouper ce type de boite

Attention, les identifiants comme idBoxType doivent toujours être :

  • en minuscules,
  • sans accents,
  • uniquement des lettres de "a" à "z", des chiffres de "0" à "9"
  • le tiret "-" est acceptable, mais pas en 1° ou dernière position

Après avoit créé un nouveau type de boite, il est fréquent d'insérer une boite de ce type dans un écran. Généralement, il est même nécessaire de commencer par créer un nouvel écran destiné à contenir la nouvelle boite.

Pour créer un écran, il faut ajouter un enregistrement dans la table qc_Screens à l'aide de la requête suivante :

/* ---------- Ajoute un écran PageToto
*/

INSERT INTO qc_Screens (
    idScreen, 
    idSite, 
    title, 
    shortTitle, 
    template, 
    description, 
    keywords, 
    order1, 
    order2, 
    order3, 
    isVisible, 
    lastUpdate)
VALUES (
    'page-toto', 
    'default', 
    'Ecran PageToto', 
    'PageToto', 
    '', 
    '', 
    '', 
    1, 
    2, 
    3, 
    #TRUE, 
    #DAYDATE);
GO;

Description des colonnes de la table qc_Screens :

  • idScreen = page-toto : l'identifiant de la page
  • idSite = default : le nom du site où apparait la page (toujours default)
  • title = Ecran PageToto : le nom de la page
  • shortTitle = PageToto : le nom abrégé de la page
  • template = (vide) : le template pour la page (toujours vide)
  • description = (vide) : le contenu pour la balise meta description
  • keywords = (vide) : le contenu pour la balise meta keywords
  • order1 = 1 : la position de la page dans le niveau 1
  • order2 = 2 : la position de la page dans le niveau 2
  • order3 = 3 : la position de la page dans le niveau 3
  • isVisible = #TRUE : est-ce que l'écran est visible dans le menu (ou #FALSE)
  • lastUpdate = #DAYDATE : la date de mise à jour de l'écran (toujours #DAYDATE)

La requête précédente permet d'ajouter l'écran à la table qc_Screens en indiquant à quel emplacement l'insérer dans l'arborescence des pages (à l'aide des colonnes order1, order2 et order3). Par contre, cela ne suffit pas pour que l'arrangement des pages reste correct.

En effet, s'il existait déjà un écran en position 1.2.3, il y aurait maintenant 2 écrans au même emplacement, ce qui complique la création des menus ou du plan du site. Par conséquent, il est impératif de penser à faire de la place avant d'insérer un nouvel écran.

Par exemple, pour insérer un écran en position 4.0.0, il faut pousser d'un cran tous les écrans en position 4.x.x avant d'insérer le nouvel écran :

UPDATE qc_Screens
SET    order1 = order1 + 1
WHERE  (order1 >= 4);
GO;

Autre exemple, pour insérer un écran en position 4.3.0, il faut pousser d'un cran tous les écrans en position 4.3.x avant d'insérer le nouvel écran :

UPDATE qc_Screens
SET    order2 = order2 + 1
WHERE  (order1  = 4)
AND    (order2 >= 3);
GO;

Et pour insérer un écran en position 4.3.2, il faut pousser d'un cran tous les écrans à partir de la position 4.3.2 avant d'insérer le nouvel écran :

UPDATE qc_Screens
SET    order3 = order3 + 1
WHERE  (order1  = 4)
AND    (order2  = 3)
AND    (order3 >= 2);
GO;

Donc, pour résumer :

  • on fait de la place pour le nouvel écran dans la table qc_Screens
  • on insère le nouvel écran dans la table qc_Screens

Là on est presque bon. Il est cependant préférable de définir les autorisations pour accéder à l'écran que l'on vient de créer :

  • le profil 'admins' peut visualiser (view) l'écran et l'administrer (admin)
  • le profil 'all-users' peut seulement visualiser l'écran (view)

Ce que l'on réalise avec les requêtes suivantes :

/* ---------- Défini les droits d'accès à l'écran PageToto
*/

INSERT INTO qc_ScreenRoles (idScreen, aktion, idRole) VALUES ('page-toto', 'admin', 'admins');
GO;

INSERT INTO qc_ScreenRoles (idScreen, aktion, idRole) VALUES ('page-toto', 'view', 'admins');
GO;

INSERT INTO qc_ScreenRoles (idScreen, aktion, idRole) VALUES ('page-toto', 'view', 'all-users');
GO;

On progresse ! Maintenant, il reste juste à créer dans l'écran PageToto une nouvelle boite du type TypeToto. Pour cela, on crée un nouvel enregistrement dans la table qc_Boxes :

/* ---------- Ajoute une boite TestToto de type TypeToto dans l'écran PageToto
*/

INSERT INTO qc_Boxes (
    idBox, 
    idScreen, 
    idBoxType, 
    title, 
    paneName, 
    paneOrder, 
    cacheTime, 
    visibility)
VALUES (
    'test-toto', 
    'page-toto', 
    'typetoto', 
    'Boite TestToto', 
    'paneMain', 
    10, 
    600, 
    0);
GO;

Description des colonnes de la table qc_Boxes :

  • idBox = test-toto : l'identifiant de la boite
  • idScreen = page-toto : l'identifiant de la page où créer la boite
  • idBoxType = typetoto : l'identifiant du type de boite à créer
  • title = Boite TestToto : le nom de la nouvelle boite
  • paneName = paneMain : la colonne où créer la boite (paneSide, paneMain ou paneMore)
  • paneOrder = 10 : la position de la boite dans sa colonne (libre de 10 en 10)
  • cacheTime = 600 : la durée du cache pour cette boite
  • visibility = 0 : niveau de visibilité de la boite (0 = dans l'écran, 1 = dans l'écran et ses sous-écrans, 2 = dans tous les écrans)

Et pour finir, il faut définir les autorisations role par role pour la boite que l'on vient de créer :

  • le profil 'admins' peut visualiser (view) la boite, l'administrer (admin) et la modifier (edit)
  • le profil 'all-users' peut seulement visualiser la boite (view)

Note: Etant donné que le type de boite TypeToto n'a pas de contrôle prévu pour la configuration (cnfgSource laissé vide), il ne sert à rien de paramétrer que profil peut configurer (cnfg) la boite.

Ce qui nous donne les requêtes suivantes :

/* ---------- Défini les droits d'accès à la boite TestToto
*/

INSERT INTO qc_BoxRoles (idBox, aktion, idRole) VALUES ('test-toto', 'admin', 'admins');
GO;

INSERT INTO qc_BoxRoles (idBox, aktion, idRole) VALUES ('test-toto', 'view', 'admins');
GO;

INSERT INTO qc_BoxRoles (idBox, aktion, idRole) VALUES ('test-toto', 'edit', 'admins');
GO;

INSERT INTO qc_BoxRoles (idBox, aktion, idRole) VALUES ('test-toto', 'view', 'all-users');
GO;

C'est vrai que c'est un peu plus compliqué qu'une simple mise à jour depuis l'interface, mais c'est devenu quasiment indispensable maintenant qu'il commence à exister plusieurs sites gérés avec Altrr-Press.

mercredi 17 décembre 2008

Group By d'un champ Mémo sous Access

Hier soir j'ai enfin corrigé un bug sournois qui raccourcissait les descriptions des films à 255 caractères. Jusqu'à présent, je n'avais pas réussi à reproduire ce bug en local et donc à trouver ce qui pouvait le provoquer. Mais à force d'insister et d'éliminer tout ce qui ne pouvait pas poser problème, j'ai fini par avoir de gros doutes sur la requête qui sert à mettre à jour la date de dernière programmation des films.

Cette requête retrouve tous les films dont la date de dernière programmation actuellement enregistrée (dir_Movies.lastDate) est inférieure à la date de la dernière séance correspondant à ce film (dir_Seances.progDate).

SELECT T1.idMovie, 
       T1.displayName, 
       T1.description, 
       T1.length, 
       T1.picture, 
       T1.lastDate, 
       MAX(T2.progDate) AS progDate 
FROM   dir_Movies  T1, 
       dir_Seances T2 
WHERE  (T2.keyFilm = T1.idMovie) 
GROUP BY 
       T1.idMovie, 
       T1.displayName, 
       T1.description, 
       T1.length, 
       T1.picture, 
       T1.lastDate 
HAVING (T1.lastDate < MAX(T2.progDate))

Le souci, c'est qu'en local, cette requête ne renvoyait aucune ligne. Ce qui est "normal" dans la mesure où ma base de données locale est une simple copie de la base de données de production, et que lorsque je récupère cette copie, la mise à jour de la date de dernière programmation a déjà été faite.

Par conséquent, il a d'abord fallu bidouiller la table des films pour que je puisse tester en local cette requête. Pour cela, j'ai mis toutes les dates de dernière programmation au 1° décembre.

UPDATE dir_Movies SET dir_Movies.lastDate = #12/1/2008#;

Et là, la requête qui me semblait douteuse m'a renvoyé 52 lignes. Après avoir agrandi la colonne "description", j'ai pu constater qu'en effet celle-ci était bel et bien coupée et à première vue à quelque chose comme 255 caractères.

Etant donné que je n'avais pas vraiment besoin de faire un GROUP BY sur la description, j'ai donc modifié ma requête pour faire sortir la colonne "description" de la clause GROUP BY :

SELECT T1.idMovie, 
       T1.displayName, 
       T1.length, 
       T1.picture, 
       T1.lastDate, 
       MAX(T1.description) AS description, 
       MAX(T2.progDate) AS progDate 
FROM   dir_Movies  T1, 
       dir_Seances T2 
WHERE  (T2.keyFilm = T1.idMovie) 
GROUP BY 
       T1.idMovie, 
       T1.displayName, 
       T1.length, 
       T1.picture, 
       T1.lastDate 
HAVING (T1.lastDate < MAX(T2.progDate))

Malheureusement, Access n'a pas du tout apprécié et m'a gratifié du message d'erreur suivant : Impossible d'obtenir des champs Memo, OLE ou Hyperlink Object dans l'argument d'agrégat (T1.description).

Heureusement, Google a été plus conciliant et après quelques recherches j'ai fini par trouver qu'il suffisait d'utiliser la fonction FIRST() ou LAST() pour contourner le problème.

SELECT T1.idMovie, 
       T1.displayName, 
       T1.length, 
       T1.picture, 
       T1.lastDate, 
       LAST(T1.description) AS description, 
       MAX(T2.progDate) AS progDate 
FROM   dir_Movies  T1, 
       dir_Seances T2 
WHERE  (T2.keyFilm = T1.idMovie) 
GROUP BY 
       T1.idMovie, 
       T1.displayName, 
       T1.length, 
       T1.picture, 
       T1.lastDate 
HAVING (T1.lastDate < MAX(T2.progDate))

Et ça marche : la requête passe et la colonne description n'est pas tronquée à 255 caractères.

jeudi 11 décembre 2008

Renommer les namespaces de QC en AP

Comme j'ai un peu tendance à stagner dans ma migration vers ASP.NET 2.0, j'ai décidé de consacrer un peu de temps pour renommer les différents éléments du projet de qc.* en ap.*.

Dans un premier temps (étapes 1 à 3), je vais remplacer qc.Xxxxxx par ap.Xxxxxx dans tous les fichiers du projet, ce qui aura pour effet de modifier tous les namespaces et les assemblys de qc.Xxxxxx en ap.Xxxxxx. Les noms des projets en eux-même seront renommés à l'étape 3 via TortoiseSVN.

1° étape

  • S'assurer que la solution est totalement versionnée
  • Faire une sauvegarde complète du en lançant un Export All sous Subversion

2° étape

Sous Visual Studio, sans ouvrir la solution, faire Edition / Rechercher et remplacer / Remplacer dans les fichiers

  • Rechercher : qc.Engine
  • Remplacer par : ap.Engine
  • Regarder dans : D:\Portals\pi
  • Type de fichiers : *.*

renommer-en-ap.png

  • Cliquer sur le bouton [ Remplacer tout ]
  • Confirmer que l'on veut effectuer le remplacement global

Répéter la même chose pour qc.Classic, qc.Department, qc.Devel et qc.Framework puis une dernière fois pour renommer BAS.Data en Altrr.Data.

3° étape

  • Supprimer le contenu du répertoire bin
  • Supprimer le fichier pi.suo
  • Utiliser TortoiseSVN pour renommer les fichiers qc.Xxxxxxx.csproj en ap.Xxxxxxx.csproj => proposera de renommer également qc.Xxxxxxx.csproj.webinfo en ap.Xxxxxxx.csproj.webinfo
  • Ouvrir la solution sous Visual Studio et la regenérer totalement => "Régénération globale : 6 a réussi, 0 a échoué, 0 a été ignoré"
  • Vérifier que le répertoire bin ne contient plus de DLL qc.Xxxxxxx.dll
  • Contrôler que le site fonctionne encore :)
  • Commiter le projet en indiquant "Renommé qc.Xxxxxx en ap.Xxxxxx"

4° étape

Une fois le gros oeuvre fait, je fignole pour qu'il ne reste presque plus de trace de QC.

  • changements des namespaces folkloriques ("qc" et"qc.res.ae") en "ap.Engine"
  • remplacement des TagPrefix="qc" par TagPrefix="ap" => il faut aussi remplacer :
    • <qc:Literal par <ap:Literal
    • <qc:AutoComplete par <ap:AutoComplete
    • <qc:SelectFile par <ap:SelectFile
    • <qc:SelectLink par <ap:SelectLink
    • <qc:RoleGrid par <ap:RoleGrid
    • <qc:Wysiwyg par <ap:Wysiwyg
  • Ouvrir la solution sous Visual Studio et la regenérer totalement => "Régénération globale : 6 a réussi, 0 a échoué, 0 a été ignoré"
  • Contrôler que le site fonctionne encore :)
  • Commiter le projet en indiquant "Suite renommage 'qc' en 'ap' (namespace, tagprefix, user controls ...)"

Après ça, il ne reste plus que les tables de la base de données qui sont encore préfixées par "qc_" et pas "ap_".

5° étape

Le souci, c'est qu'il faut tout re-déployer puisque les namespaces ont changés et que désormais les fichiers .ascx héritent de ap.Xxxxxx.Yyyyyy et plus de qc.Xxxxxx.Yyyyyy. Il faut donc impérativement remplacer les ascx actuels par les nouveaux sur tous les sites en production. Heureusement qu'il n'y en a pas tant que ça :)

Et en plus, suite à la disparitions du namespace qc au profit de ap.Engine, il faut impérativement penser à mettre à jour les fichiers web.config pour corriger les 2 lignes suivantes :

<add verb="POST,GET" path="*MailFormHandler.ashx" type="ap.Engine.MailFormHandler, ap.Engine" />
<add verb="POST,GET" path="*InfoStatHandler.ashx" type="ap.Engine.InfoStatHandler, ap.Engine" />

Si tout va bien, je vais encore tester en local quelques jours et j'attaquerai les mise à jour la semaine prochaine.

jeudi 2 octobre 2008

Migration de Altrr-Press de .NET 1 vers .NET 2

Carnet de route

Les pré-requis

  • Renommer les projets, les assemblys et les namespaces de qc.* en ap.*
  • Supprimer les trucs inutiles :
    • les boites obsolètes
    • SqlRepeat et BangGrid.cs
    • widgEditor
    • treeview (et DestroyDrop Tree et FileExplorer)
  • Refactoriser / fusionner le code pour
    • parcourir les écrans
    • parcourir les fichiers
  • Nettoyer / simplifier le code
  • Vérifier que tout est subversionné et se compile sans erreur

Premier bon point : à part le renommage, je suis maintenant quasiment bon sur tout et je peux donc considérer que j'ai une version 1.0 "finie" sous ASP.NET 1.

Comment faire

Dans un premier temps, juste pour tester :

  • Faire une sauvegarde complète
  • Copier la solution de D:\Portals\pi sur D:\Portals\az (il suffit de lancer ApCopy az)
  • Ouvrir la solution az.sln sous Visual Studio 2005 => lance l'assistant de migration
  • Il faut ensuite re-référencer à la main les projets ap.Engine, ap.Classic, ap.Department et ap.Devel dans le projet ap.Framework (pas sûr)
  • Une fois terminé, faire clic-droit, Convertir en application Web sur les projets ap.Engine, ap.Classic, ap.Department et ap.Devel
  • A la fin, on se retrouve avec des fichiers *.ascx.designer.cs en plus (suite à Convertir en application Web)

Deuxième bon point : depuis la mise au point de ApCopy.bat, c'est devenu très facile de copier la solution de pi vers az et donc de faire des tests aussi souvent que je veux.

Une fois que ces premiers tests seront OK, cela devrait être suffisant pour passer en production certains des petits sites sous ASP.NET 2. Puis si tout se passe bien, il sera alors possible de tout migrer en ASP.NET 2.

Dans un second temps, et pour aller plus loin qu'une simple compilation sous .NET 2, il faudra aussi envisager de mettre en place les modifications suivantes dans le cadres de (ou ça pourrait faire l'objet d'une version 2.1 ?)

  • Remplacer les DLL compilées en .NET 1 par leur équivalent en .NET 2
    • Ajax.dll
    • Altrr.Services.dll
    • GoogleSearchEngine.dll
    • MySql.Data.dll (et donc ICSharpCode.SharpZipLib.dll)
    • Npgsql.dll (et donc Mono.Security.dll)
  • Utiliser les nouveautés du .NET 2 :
    • les providers de ADO.NET 2 => revoir le fonctionnementdu BDHelper
    • les classes génériques
    • les méthodes d'extensions
    • etc...

Les premières tentatives

Après les tous premiers essais de migration qui furent un peu difficile, la situation est maintenant pas mal rodée pour finaliser la mise à jour du code avant d'attaquer les tests.

Troisième bon point : désormais la compilation des sources de Altrr-Press sous Visual Studio 2005 ne génère plus que 47 avertissements. Et il me suffit de quelques modifications très simples pour ramener ce nombre à 13.

Page_Load() ne s'éxécute plus (pas signalé !!!)

C'est un problème critique parce que sans ça Altrr-Press ne fonctionne pas du tout puisque c'est dans le Page_Load() de default.aspx.cs que sont chargées toutes les boites de contenu. Et ce qui est très ennuyeur, c'est que ce n'est pas signalé à la conversion et que ça ne génère pas d'erreur de compilation ou d'exécution.

Par conséquent, la première chose à faire une fois que la conversion est terminée, c'est de penser à aller modifier la méthode InitializeComponent() de /default.aspx.cs pour y ajouter manuellement la ligne suivante :

this.Load += new System.EventHandler(this.Page_Load);

Je suppose que ce problème doit être dû au fait qu'il existe des fichiers default.aspx au niveau des chartes graphiques et que ceux-ci héritent de /default.aspx.cs, ce qui doit pertuber le convertisseur (c'est pas Microsoft qui n'est pas doué, c'est moi qui suis trop tordu pour eux).

ConfigurationSettings n'existe plus (29 avertissements)

  • il suffit de faire un remplacer dans tous les fichiers de ConfigurationSettings.AppSettings par ConfigurationManager.AppSettings
  • Mais pour que ça marche, il faut aussi ajouter System.configuration aux références des projets ap.Engine, ap.Department et ap.Devel
  • Dans ap.Department, cela ne semble servir que pour DIR_levelOne (30 par défaut) et DIR_levelTwo (75 par défaut) qui en fait sont rarement paramétrés autrement => modifié ap.Department pour renvoyer des constantes et ne plus avoir besoin de ConfigurationSettings

Instancier une Hashtable (1 avertissement)

C# 2 n'aime pas que j'utilise :

Hashtable h = new Hashtable(CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default);

Pour faciliter la migration (et éviter l'éparpillement), j'ai ajouté une méthode Common.NewHashtable() qui se contente de renvoyer un nouvel objet Hashtable. Sous Visual Studio 2005, il suffit de modifier cette méthode pour renvoyer un truc comme

new Hashtable(StringComparer.InvariantCultureIgnoreCase);

Peut-être même qu'un simple new Hashtable() serait suffisant => il faudra tester.

De toute façon, dans un second temps l'utilisation des Hashtables devrait être avantageusement remplacée par des classes génériques.

Blocs de scripts clients (4 avertissements)

En gros, il n'existe plus Page.RegisterClientScriptBlock(...) et autres et il faut maintenant utiliser Page.ClientScript.RegisterClientScriptBlock(Page.GetType(), ...)

Donc là aussi, ajout des deux méthodes Common.RegisterClientScript() et Common.RegisterStartupScript() pour éviter l'éparpillement et faciliter la migration vers .NET 2 de façon à ne faire le remplacement qu'à un endroit.

Envois de méls (10 avertissements)

Comme System.Web.Mail n'est plus en odeur de sainteté, il faut maintenant utiliser System.Net.Mail à la place puis faire toutes les modifications qui en découlent. Normalement, cela devrait simplement consister à réécrire la méthode sendMail de la classe Email (ça c'est pas compliqué) et re-tester que ça marche encore dans tous les cas (ouille!).

Pour l'instant, pour faciliter les essais entre .NET 1 et .NET 2, je reste avec System.Web.Mail => il y toujours 10 avertissements.

Problèmes XML (3 avertissements)

'System.Web.UI.WebControls.Xml.Document' est obsolète : 'The recommended alternative is the XPathNavigator property. Create a System.Xml.XPath.XPathDocument and call CreateNavigator() to create an XPathNavigator. http://go.microsoft.com/fwlink/?linkid=14202

'System.Xml.Xsl.XslTransform' est obsolète : 'This class has been deprecated. Please use System.Xml.Xsl.XslCompiledTransform instead. http://go.microsoft.com/fwlink/?linkid=14202

Maintenant que je me remet à faire du XML, il faudra bien que je trouve une solution. Mais en attendant, il faut encore supporter les 3 avertissements que cela génère.

Ce que ça apporte

Pour l'instant, pas grand chose à part du boulot en plus et un ralentissement perceptible dû au fait de travailler sous VS 2005 au lieu de VS 2003.

Autre problème : quand je teste les temps de génération des fichiers statiques de PI, c'est quasiment 10% plus lent sous .Net 2 que sous .NET 1 :(

Sur mon portable, les chronométrages donnent les résultats suivants :

1° test : après compilation et avec Explorer, DOS, IE, FF, VS2003, VS2005... en mémoire

  • ASP.NET 2 (sous IE) : 15:01:09 -> 15:12:34 => 11 minutes 25 secondes
  • ASP.NET 1 (sous FF) : 15:18:03 -> 15:30:44 => 12 minutes 41 secondes

2° test : avec seulement IE en mémoire

  • ASP.NET 2 (sous IE) : 15:35:56 -> 15:47:08 => 11 minutes 12 secondes
  • ASP.NET 1 (sous IE) : 15:50:07 -> 16:00:31 => 10 minutes 24 secondes

3° test : au repos, avec seulement IE en mémoire

  • ASP.NET 2 (sous IE) : 16:06:58 -> 16:18:01 => 11 minutes 03 secondes
  • ASP.NET 1 (sous IE) : 17:35:53 -> 17:46:00 => 10 minutes 07 secondes

Pour l'instant, je vais mettre tout ça sur le compte du portable et considérer qu'il n'est pas correctement dimensionné pour travailler avec le .NET 2.

Et si je trouve le temps, j'essaierai de faire un nouveau chronométrage sur un PC un peu plus récent.

Est-ce que ça marche ?

Ca dépend.

En ce qui concerne les fichiers statiques générés par PI, une comparaison avec WinMerge de tous les fichiers produits par la version ASP.NET 1 et ceux issus de la version ASP.NET 2 montre qu'il n'y a pas de différence. Donc, la migration ne va rien casser de ce côté là.

Pour être vraiment complet et vérifier que tout est bon, j'ai aussi fait un petit programme pour lire toutes les pages du site sous ASP.NET 1 et les enregistrer sur le disque dur. Puis pareil avec le site sous ASP.NET 2 avant de lancer une comparaison entre les deux résultats obtenus. Et c'est là que ça coince un peu. J'obtiens presque toujours des contenus identiques pour les deux versions, sauf quand il y a des formulaires dans la page.

Il y a un premier problème avec ma technique pour éviter le double-clic en validation de saisie. Normalement, je remplace l'évènement onclick du bouton de validation :

onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();"

par:

onclick="this.style.display='none';if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();"

Apparement, sous ASP.NET 2 il n'y a plus :

onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();"

mais :

onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;_ctl1:btnSend&quot;, &quot;&quot;, true, &quot;&quot;, &quot;&quot;, false, false))"

Et j'ai un second problème avec les améliorations que j'apporte au html généré par ASP.NET pour en faire du code XHTML plus présentable :

  • Insertion de la balise input hidden générée pour le ViewState dans une balise div (validation XHTML)
  • Déplacement du viewState en fin de page (pour amélioration SEO)

Donc, pour l'instant, après déjà pas mal de boulot, si je souhaite atteindre le minimum pour mettre en production une version sous ASP.NET 2, je dois encore :

  • remplacer ma procédure Email.sendMail() par une version à base de System.Net.Mail
  • ne plus utiliser System.Web.UI.WebControls.Xml.Document et System.Xml.Xsl.XslTransform
  • espérer que sur un vrai serveur ASP.NET 2 sera au moins aussi rapide que ASP.NET 1
  • résoudre mes problèmes de formulaires (priorité 1)
  • puis tester que tout ça marche correctement.

C'est pas gagné, mais y'a de l'espoir...

mercredi 24 septembre 2008

Je me souviens...

Il y a près de quatre ans, je démarrais un nouveau projet sans trop savoir ce que j'allais en faire et petit à petit c'est devenu Quick-Content puis Altrr-Press aujourd'hui.

Pour me motiver et garder une trace des différents essais que je faisais, j'avais démarré un blog en anglais sur WDevs, un hébergeur de blogs dédiés au .NET. Malheureusement, WDevs avait fermé ses portes et disparu courant 2007, ce qui faisait que j'avais perdu toutes mes notes.

Et aujourd'hui, après des tonnes de fois à me dire qu'il fallait que j'aille faire un tour du côté des archives de l'internet , je me suis enfin décidé (merci le bruit) et miracle, on dirait que tout est encore là : http://blogs.wdevs.com/qc, en attendant le backup complet ici même : http://blog.pagesd.info/tag/wdevs.

Ca parle pas mal de QC (enfin quand je m'astreignais à bloguer), mais pas que (y'a notamment mon "célèbre" billet sur l'utilisation du SMTP de GMail avec System.Web.Mail. Les liens sont un peu cassés, parce que 4 ans pour internet c'est vieux, mais c'est quand même agréable de retrouver et relire tout ça.

Edit: ça pourrait être l'occasion de terminer la mise à jour du site à partir des différents essais sous Wordpress et Blogger.

- page 1 de 2