Une question de gros bon sens...
S'il y a une chose qui me choque dans le monde des produits électroniques et informatiques, c'est qu'à chaque fois qu'on se procure un nouveau gadget, on veut protéger notre investissement et on se sent obligé d'acheter un étui ou un protecteur quelconque. Et c'est justement là le problème car les entreprises savent qu'on dépense une petite fortune pour ces appareils alors c'est naturel pour le consommateur de vouloir en prendre soin pour en bénéficier le plus longtemps possible. Sans compter que de nos jours, les produits sont munis d'une garantie de base d'un an, sauf si on achète les plans de protections prolongées (une autre arnaque).
Il y a quelques années, j'étais propriétaire d'un Palm Tungsten. Comme c'était un appareil fragile, j'avais acheté un étui conçu spécialement pour ce modèle. Et il fallait inmanquablement se munir de pellicules protectrices pour protéger l'écran des éraflures dûes à l'usure. Quand j'ai eu mon premier iPod, je me souviens avoir acheté un iSkin en silicone pour environ 25 ou 30$. Quand je l'ai changé pour un iPod Touch, je me suis procuré un étui en cuir (40$). Si ma mémoire est bonne, une seule pelicule protectrice pour l'écran se détaillait 10$ (et dire que l'écran est supposé être insensible aux égratignures). Bref, c'est sans compter un autre étui pour la caméra numérique et plus récemment, lorsque j'ai acheté mon ordinateur portatif, il fallait me magasiner un étui convenable.
Je n'avais jamais vraiment été informé sur la valeur de tels étuis mais je ne fûs pas très surpris d'apprendre qu'ils se détaillaient entre 50 et 100$ chez les Future Shop et Best Buy de ce monde. Le seul étui que je trouvais correct et qui soutenait le portable de façon stable à l'intérieur coûtait 70$ (contrairement aux autres qui retenaient l'ordinateur par une bande velcro dans un compartiment trop grand). C'est pourquoi je me pose la question : jusqu'à quel point est-ce qu'on peut dépenser pour des accessoires de protection ?
Ne voulant pas encourager les gros détaillants à nous arnaquer davantage, j'ai cherché pour une solution alternative. Après en avoir glissé un mot à ma copine, ça n'a pas pris de temps qu'elle me parla d'une boutique qu'elle connaissait, un centre de liquidation situé sur la rue Chabanel dans le nord de Montréal.
Dans cette petite boutique familiale qui s'appelle Moura Cuir, où on y trouve des valises, sacs à dos, portefeuilles, sacs à main et accessoires, j'y ai déniché un étui pour ordinateur portatif de bonne qualité pour à peine 25$ (taxes incluses ou sans taxe ?). Pour une fois que le prix n'est pas exagéré, je crois que ça vaut la peine d'être dit et encouragé. Voici l'adresse :
Centre de liquidation Moura Cuir
220 Chabanel Ouest
Montréal, Québec
514-384-3906
Pour effectuer une requête SQL paginée, PostgreSQL possède les clauses LIMIT et OFFSET tandis que MySQL utilise la syntaxe LIMIT x,y. Étonnamment, Microsoft SQL Server n'avait pas d'équivalent, jusqu'à l'arrivée de la version MSSQL 2005 et plus récemment la 2008.
Ces dernières versions ont introduit une nouvelle approche pour construire la requête, qui ne ressemble en rien à ce que PgSQL ou MySQL font. La magie s'opère grâce à la fonction row_number(), qui permet de numéroter séquentiellement les résultats d'une requête. À titre d'exemple, imaginez qu'on définisse préalablement une sous-requête qui liste des villes. On utilisera le mot clé OVER pour associer un numéro de 1 à n, en suivant l'ordre spécifié par le champ noté entre parenthèses (dans mon exemple, alphabétique selon le nom de la ville). Une fois la sous-requête définie, il ne restera qu'à l'utiliser dans un autre SELECT et utiliser la référence à l'indice idx attribué par row_number() pour trancher les résultats selon le nombre d'enregistrements par page voulu.
WITH subquery ASPersonnellement, je suis d'accord que c'est pratique et que ça constitue un gros pas en avant pour SQL Server, mais c'est beaucoup moins élégant que ce que propose ses concurrents. OK, je sais que ça fera plaisir à plusieurs de l'entendre : Microsoft a copié sa solution sur Oracle :-)
(
SELECT *, row_number() OVER (ORDER BY city_name) as idx
FROM dbo.table_cities
)
SELECT *
FROM subquery
WHERE idx BETWEEN 50 AND 100;
Précédemment, que devait-on faire lorsqu'on utilisait MSDE (Desktop Engine) ou SQL Server 2000 ? Il fallait simplement user d'imagination. Mais on peut gagner beaucoup à apprendre quel genre de mécanisme permettait d'arriver à paginer des résultats.
On pouvait créer une requête avec des sous-requêtes imbriquées mais ma solution favorite était d'utiliser du Transact-SQL (T-SQL) à l'intérieur d'une procédure stockée. À l'aide d'une table temporaire ou une variable table qui contient une colonne integer avec IDENTITY(1,1) - qui débute par 1 (seed) et qui s'incrémente de 1 à chaque appel, on insère les résultats de la requête de base et un numéro incrémentiel unique est attribué à chaque enregistrement, ce qui nous permet ensuite d'extraire la page.
CREATE PROCEDUREVous devrez par la suite vous assurer que le numéro de page n'est pas négatif, que la page demandée n'excède pas le nombre de résultats, que votre table de base ne possède pas trop d'enregistrements, ce qui entraînerait une perte de performance. De quoi se faire aller les méninges un peu. À bien y penser, row_number() n'est pas si mal.
@page INT,
@resultsPerPage INT
AS
DECLARE
@tempResults TABLE (
idx INT IDENTITY(1,1),
field1 INT,
field2 VARCHAR(100),
field3 BIT,
...
)
DECLARE
@idxFirst INT,
@idxLast INT
-- assigner un numéro de ligne automatiquement
INSERT INTO @tempResults (field1, field2, field3)
SELECT field1, field2, field3 FROM dbo.my_table
WHERE condition1 = x
AND condition2 = y
ORDER BY field2
-- pagination
/*
Selon ce calcul, si on compte 50 résultats par page
Page 1 : 1 à 50
Page 2 : 51 à 100
*/
SET @idxFirst = (@page - 1) * @resultsPerPage + 1
SET @idxLast = @idxFirst + @resultsPerPage - 1
-- retourner la page
SELECT * FROM @tempResults
WHERE idx BETWEEN @idxFirst AND @idxLast
GO
Je viens de m'amuser pour la première fois avec jQuery User Interface. C'est vraiment du beau boulot! En allant de l'animation, aux effets, en plus d'ajouter les widgets et les thèmes, il y a de quoi s'amuser et créer des interfaces attrayantes en un tour de main.
Parmi les widgets disponibles, il y a :
- l'accordéon
- un sélectionneur de date (calendrier)
- une boîte de dialogue (modale)
- une barre de progression
- un curseur de défilement
- les onglets
Pour ma part, j'ai fait mes premières expériences avec le calendrier qui permet à l'utilisateur de sélectionner une date facilement, tout simplement parce que c'est le type de contrôle que j'utilise régulièrement dans mes formulaires. J'ai été surpris de voir à quel point celui de jQuery UI est complet. D'abord, il s'initialise facilement, il suit les règles esthétiques d'un thème qui peut se marier agréablement avec les autres contrôles et il est très flexible. Je publie ici le résultat commenté de mon premier test.
Après avoir sélectionné les options, le thème et téléchargé le tout, j'ai pris tous les fichiers et je les ai placés dans mon projet à cet emplacement : /js/jquery-ui.
Dans ma page web, j'ai chargé les librairies :
- d'abord jQuery
- ensuite le noyau UI et le datepicker
- l'internationalisation, pour afficher le contrôle dans la langue de notre choix
<script type="text/javascript" src="/js/jquery-ui/js/jquery-1.3.2.min.js"></script>Ensuite le thème de base en CSS :
<script type="text/javascript" src="/js/jquery-ui/development-bundle/ui/ui.core.js"></script>
<script type="text/javascript" src="/js/jquery-ui/development-bundle/ui/ui.datepicker.js"></script>
<link type="text/css" href="/js/jquery-ui/development-bundle/themes/base/ui.all.css" rel="Stylesheet" />Dans le HTML, j'ai crée un formulaire et j'y ai ajouté un champ texte, par exemple pour inscrire une date de naissance :
<input type="text" id="birthdate" />
Pour contrôler la grosseur du calendrier, j'ai dû ajuster le CSS pour réduire ses dimensions. Comme le reste du texte de ma page en était affecté, j'ai forcé la taille du texte dans différentes balises.
<style type="text/css">Pour initialiser le datepicker, j'ai créé un objet JS qui définit le format de date international (ISO 8601) du type "2008-03-29". Comme vous le verrez plus loin, d'autres options pourront être ajoutées.
body {
font-size: 62.5%;
}
// voir plus loin
#inlineCalendar {
font-size: 62.5%;
}
div, p {
font-size: 12px;
font-family: arial;
}
</style>
var dp_config = { dateFormat: 'yy-mm-dd' };J'initialise le contrôle pour afficher le datepicker lorsque le champ qui possède l'ID "birthdate" recevra le focus.
function initialize() {Configurer le calendrier en français
$('#birthdate').datepicker(dp_config);
}
$(document).ready( initialize );
Par défaut, le calendrier s'affiche en anglais. Quelques petites modifications sont nécessaires pour le configurer en français (ou tout autre langue désirée, le répertoire d'internationalisation, i18n, semble contenir environ 40 langues prédéfinies).
// charger le fichier d'internationalisation globalDans l'objet dp_config, on pourra aussi redéfinir les entêtes de colonnes pour les noms de jours pour passer d'une abréviation de deux lettres à une seule :
<script type="text/javascript" src="/js/jquery-ui/development-bundle/ui/i18n/jquery-ui-i18n.js"></script>
// ou mieux, charger seulement la langue française
<script type="text/javascript" src="/js/jquery-ui/development-bundle/ui/i18n/ui.datepicker-fr.js"></script>
var dp_config =Dans la fonction d'initialisation, j'ai appelé la fonction setDefaults() pour paramétrer deux éléments de langue :
{
dateFormat: 'yy-mm-dd',
dayNamesMin: ['D', 'L', 'M', 'M', 'J', 'V', 'S']
};
- showMonthAfterYear : false, pour afficher "mars 2009" au lieu de "2009 mars"
- $.datepicker.regional['fr'] pour indiquer à i18n de charger le package de langue français
function initialize() {Avec ceci, mon calendrier / datepicker était 100% fonctionnel en français.
$.datepicker.setDefaults($.extend({showMonthAfterYear: false}, $.datepicker.regional['fr']));
$('#birthdate').datepicker(dp_config);
}
Un peu plus d'options
En lisant la documentation, d'autres ajouts intéressants sont possibles, que j'ai placé dans dp_config (cet objet ne fait pas parti de la suite jQuery, c'est une façon que j'ai de structurer mon code pour qu'il soit plus lisible). Comme pour dateFormat et dayNamesMin, il faut ajoute une ligne à la fin pour chaque option désirée (clé/valeur, séparée par une virgule, sauf le dernier) :
- Permettre de changer de mois et/ou d'année. Comme c'est un champ pour saisir la date de naissance, on spécifiera yearRange pour permettre au calendrier de reculer 100 ans en arrière et 0 année en avant (année courante) :
changeMonth: true,
changeYear: true,
yearRange: '-100:+0' - Afficher deux mois consécutifs (ou plusieurs), comme le font les sites de voyage :
numberOfMonths: 2 - Affichage des boutons pour revenir au mois courant et pour forcer la fermeture du datepicker :
showButtonPanel: true - Ajouter un effet graphique d'animation à l'affichage du calendrier :
showAnim: 'fadeIn' - Afficher le calendrier inline dans le HTML. Il suffit de créer une balise DIV dans le HTML et attacher le calendrier au DIV plutôt qu'au champ du formulaire dans la fonction initialize().
$('#divInlineCalendar').datepicker(dp_config);
Pour moi, en dépit de la popularité du service de micro-blogging Twitter, c'est encore resté au stade de curiosité. Peut-être parce que j'ai le verbe facile et que j'ai de la difficulté à être contraint à 140 caractères! Non, sans blague, on est habitué à ce genre de fonctionnalité, que ce soit dans le statut MSN (Share a quick message), dans celui de Facebook ou dans les SMS (160 caractères), donc je comprends l'engouement des gens à vouloir résumer leur pensée en une courte phrase.
Alors que la demande est grandissante, les utilisateurs veulent pouvoir intégrer le contenu à partir d'une interface unique. Qu'en est-il si on veut faire en sorte de poster un message vers Twitter à partir de notre propre application ?
En utilisant cUrl (à supposer qu'il est installé), on sait qu'on peut appeler la commande par le shell pour s'authentifier et poster le statut:
curl http://twitter.com/statuses/update.xml -u username:password -d status="message"
Sachant cela, rien ne nous empêcherait de lancer la commande PHP shell_exec(), mais pour des raisons de sécurité, les administrateurs réseaux ne sont jamais très friand à débloquer ce type d'appel. Souvent, le mieux qu'ils puissent faire est d'autoriser l'exécution de certains programmes, en les spécifiants comme sudoers.
$result = shell_exec('curl http://twitter.com/statuses/update.xml -u username:password -d status="message"');
Donc il est préférable d'utiliser la librairie cUrl et d'instancier l'appel à l'API REST directement par PHP:
// url qui pointe vers l'action update, en xml ou jsonUne fois l'envoi terminé, on récupère l'information retournée dans une variable et on peut regarder le statut par le code HTTP reçu:
$api_url = 'http://twitter.com/statuses/update.xml';
// l'information de votre compte
$username = 'usr';
$password = 'pwd';
// le message
$args = array( 'status' => 'Mon premier statut par curl');
// initialiser une session cUrl
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $api_url);
curl_setopt($ch, CURLOPT_HEADER, True);
curl_setopt($ch, CURLOPT_NOBODY, False);
curl_setopt($ch, CURLOPT_POST, True);
curl_setopt($ch, CURLOPT_POSTFIELDS, $args);
// information de login
curl_setopt($ch, CURLOPT_USERPWD, $username.':'.$password );
// en secondes
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// pour éviter l'erreur HTTP 417 - Expectation Failed
$headers = array('Expect:', 'X-Twitter-Client: ', 'X-Twitter-Client-Version: ', 'X-Twitter-Client-URL: ');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// envoyer le message
$response = curl_exec($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if( intval( $info['http_code'] ) == 200 )Notez que si vous faites des tests et que vous avez du mal à vous authentifier, votre compte sera bloqué temporairement et provoquera une erreur 401. Vous devrez patienter un certain temps pour qu'il soit dévérrouillé.
echo 'Message envoyé';
else
echo 'Erreur HTTP ' . $info['http_code'];
Vous vous souvenez du film Jurassic Park, lancé sur nos écrans en 1993 ? Ça faisait un bout de temps que je voulais en parler car le film présente quelques petites curiosités informatiques que je n'avais pas réellement noté à l'époque, mais qui sont appropriées pour cette chronique quotidienne.
Dans ce film, ce n'était pas tant les dinosaures qui m'avaient impressionné. Non, la magie se retrouvait d'abord dans les Jeeps qui possédaient des systèmes de CD-Rom interactifs! Rien de moins. Et que dire du personnage de Lex Murphy, la fillette qui connaissait Unix et qui a su restaurer le programme pour assurer le bon fonctionnement du parc d'attraction. Un vrai jeu d'enfant...
Sinon j'aimerais attirer votre attention sur un détail qui ne défile que pendant quelques secondes : quand Dennis Nedry, l'informaticien crasseux, lance le programme de sabotage. J'ai dû effectuer une saisie d'écran au ralenti pour montrer ce qui est affiché à l'écran. Cliquez sur l'image ci-dessous pour la voir en plus grand format.
Qu'est-ce que vous remarquez dans le code source ?
- Des commentaires (#)
- Le décompilateur de ressources Macintosh (Derez)
- Une expression régulière à évaluer
- Une commande checkout pour du contrôle de source
En fouillant un peu, j'ai trouvé ceci dans le trivia d'IMDB (que je traduis approximativement) :
Le logiciel du parc est écrit en Pascal; un programme est clairement visible en gros plan dans l'un des moniteurs du système UNIX. L'interface graphique reconnue comme étant UNIX était en réalité un navigateur de fichiers fictif en 3D de Silicon Graphics. En réponse au film, une véritable application a été écrite (NDLR: elle se nommait FSN). Le numéro de version du système d'exploitation Unix de SG était 4.0.5 que l'on peut voir dans un des shell.
J'ai réussi à mettre la main sur une copie inspirée du Silicon Graphics 3D File System Navigator, qui se nomme FSV pour Linux / Unix. Requiert au minimum OpenGL 1.1 ou les librairies Mesa3D. Ma curiosité a été assouvie pour aujourd'hui.
Aujourd'hui, j'ai eu à récupérer un flux RSS externe et à l'intégrer sur le site web d'un de nos clients. Évidemment, c'était en supposant que le RSS soit formatté correctement...
La difficulté que j'ai rencontré était que des caractères illégaux s'étaient glissés dans le texte, en particulier le symbole "&", qui n'est pas considéré valide, tout comme les balises HTML. N'ayant pas le contrôle sur le flux en question, je cherchais un moyen de contourner le problème, à la plus grande satisfaction de mon client.
Pour y remédier, je me suis simplement arrangé pour entourer le texte de sections CDATA, qui font en sorte que les caractères spéciaux et le code HTML provenant d'un éditeur de texte riche (gras, italique, etc.) ne soient pas analysés et validés en tant que structure du document XML.
D'abord, j'ai lancé une commande cUrl pour capturer les résultats situés sur le site distant. Ensuite, j'ai effectué une série de remplacement pour insérer les sections CDATA (Character Data) aux bons endroits. Le texte compris dans une section CDATA doit toujours être placé entre les marqueurs <![CDATA[ et se ]]-->.
Ce que j'ai fait pouvait ressembler à ceci :
// url du RSSUne fois les sections insérées, le RSS a pu s'afficher correctement, tant dans Internet Explorer que dans Firefox. Contrairement à ce dernier, IE 7 reconnaissait l'erreur mais n'effectuait pas la correction, se contentant de lancer un message de syntaxe invalide :
$url = 'http://www.site.com/rss-avec-syntaxe-invalide.php';
// initialiser une session cUrl
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
//
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// obtenir le contenu
$xml = curl_exec($ch);
// effectuer les remplacements
// j'aurais aussi bien pu le faire par RegExp
$xml = str_replace("<title>", "<title><![CDATA[", $xml);
$xml = str_replace("</title>", "]]></title>", $xml);
$xml = str_replace("<description>", "<description><![CDATA[", $xml);
$xml = str_replace("</description>", "]]></description>", $xml);
curl_close($ch);
// afficher le flux dans ma page PHP
echo $xml;
Internet Explorer ne peut pas afficher ce flux
Ce flux contient des erreurs de code.
Aucun espace blanc n'est autorisé à cet emplacement.
Vous devez certainement connaître ThinkGeek, la boutique de prédilection pour trouver des curiosités en tout genre qui plaira aux geeks ?
Et bien j'en ai découvert une autre récemment, un peu moins originale mais où les prix sont ridiculement bas. Je dois cette trouvaille à mon père, bien qu'il ne soit pas très techno, dès qu'il l'a vu, il a immédiatement su que ça me plairait. Naturellement, plutôt que de me transférer le courriel, il a jugé bon de l'imprimer et de me le remettre en personne. Comme quoi il est bien ancré dans ses habitudes!
Ce fournisseur de gadgets se nomme DealExtreme et il a pignon sur rue quelque part à Hong Kong. Leur site compte des milliers d'articles, en passant par l'informatique, l'électronique, des accessoires rares et curiosités diverses. Chaque fiche possède une description, des photos et vidéos des utilisateurs, des critiques en plusieurs points (pour et contre), ce qui me laisse croire que la qualité des produits est somme toute respectable.
Il n'y a qu'à cet endroit où on peut trouver :
- un porte-clés en sabre laser lumineux à 1,83$
- des étuis pour Nintendo DS Lite ou pour PSP à moins de 1,50$
- un câble USB pour iPod à 2,48$
- une souris optique à doigt
- des housses de protection pour ordinateurs portables entre 5$ et 10$
- des accessoires de tours de magie
- des farces et attrapes
- des adaptateurs de manettes de jeux vidéos
Avez-vous déjà rencontré l'erreur PHP suivante ?
Fatal error: Cannot redeclare class test in path.php on line 2
C'est le genre de message qui apparaît lorsqu'on tente d'inclure deux fois le même fichier avec include() ou require().
require('myClass.php');Heureusement, on peut facilement corriger le tout en utilisant require_once() pour éviter la redéfinition.
require('myClass.php');
require_once('myClass.php');Dans bon nombre de projet, ce n'est pas rare qu'une librairie contienne des dépendances ou qu'elles en incluent d'autres. C'est exactement ce qu'on fait dans nos projets : on inclut des librairies externes et nos propres librairies. Si on déclare une nouvelle classe dans un projet, c'est possible qu'un autre fichier en contienne une du même nom. Et nous n'avons pas le contrôle sur la façon que choisissent les autres développeurs pour les nommer. Le message d'erreur est assez explicite pour indiquer la redéfinition d'une classe, mais comment savoir où se trouve la première déclaration ?
require_once('myClass.php');
Un truc très simple pour déboguer : il suffit de déclarer une classe vide du même nom et placer cette déclaration avant tous les includes de notre projet. En faisant cela, notre classe deviendra la principale (la première déclarée) et celle cachée dans les profondeurs d'une librairie sera considérée comme celle redéfinie. Le message d'erreur permettra de connaître l'emplacement exact de la classe externe et nous pourrons ensuite décider des meilleures actions à prendre.
// forcer une déclaration videFatal error: Cannot redeclare class test in path\external\library\test.php on line 2
class test {}
require_once('external.library.with.test.class.php');
require_once('my.test.class.php');
Dans d'autres circonstances, ça peut parfois être pratique d'avoir un aperçu de toutes les classes déclarées dans un projet en les listant comme ceci :
echo '<pre>';De la même façon, on pourra obtenir la liste de tous les fichiers inclus dans notre projet, incluant leur chemin complet :
print_r(get_declared_classes());
echo '</pre>';
echo '<pre>';
print_r(get_included_files());
echo '</pre>';
Saviez-vous que le premier exemple de programme "Hello World" avait été écrit vers 1974 par le canadien Brian Kernighan, un guru Unix et du langage C ? Alors qu'il travaillait pour Bell Labs, il rédigea un tutoriel où il y faisait référence. Une tradition était née.
Un programme Hello World est habituellement constitué d'un nombre minimal de lignes de code pour faire afficher le message et il sert généralement d'introduction lorsqu'on apprend un langage. En un coup d'oeil, le programmeur peut reconnaître la structure et la syntaxe à utiliser.
Parmi ses réalisations, Brian Kernighan a co-écrit le langage AWK, il a été co-auteur de la célèbre bible The C Programming Language et c'est à lui qu'on doit le nom du système d'exploitation Unix.
Pour aiguiser votre curiosité, jetez un oeil à une collection de programmes Hello World écrits dans plus de 400 langages!
Hier, j'ai démontré comment on pouvait programmer un diaporama de base pour afficher des photos. Bien que le code était fonctionnel, sa structure laissait à désirer. Avant de lire cette deuxième partie, je vous suggère de lire l'introduction du sujet, vous pourrez mieux comparer les améliorations apportées pour transformer le code en orientée objet. J'introduirai aussi le concept des closures JavaScript (fermetures), qui sont nécessaires à la réalisation de cet exercice.
La première amélioration apportée sera d'ajouter deux boutons pour pouvoir démarrer et arrêter le défilement des photos :
<input type="button" id="btnStart" value="Démarrer" />Contrairement à l'exemple précédent, comme le diaporama ne débutera pas automatiquement, on voudra cacher la photo et sa vignette jusqu'à ce que le bouton soit cliqué. Nous allons placer les balises <img> et <span> dans un conteneur que nous allons cacher au chargement de la page.
<input type="button" id="btnEnd" value="Terminer" />
<div id="slideshow">Avec Prototype (mais on aurait aussi pu prendre jQuery), on appelle un fonction d'initialisation qui attache les événements aux deux boutons.
<p><img id="pictureHolder" src=""></p>
<span id="counter"></span>
</div>
document.observe('dom:loaded', initialize );On définit les méthodes et on inclut les appels à un objet "s" encore inexistant, qui sera l'objet slideshow, instancié globalement.
function initialize() {
initSlideshow();
Event.observe('btnStart', 'click', showSlideshow );
Event.observe('btnEnd', 'click', hideSlideshow );
}
function initSlideshow() {Jusqu'ici, nous n'avons pas encore couvert la refonte orientée objet de notre slideshow. Par contre, on sait qu'on voudra obtenir un code semblable à ceci, où le constructeur (new Slideshow) prendra comme attributs :
hideSlideshow();
}
function showSlideshow() {
$('slideshow').show();
s.start();
}
function hideSlideshow() {
$('slideshow').hide();
s.end();
}
- l'ID du conteneur d'image <img>
- l'ID du compteur, la vignette <span>
- le chemin du répertoire où sont placées les images
- un array qui contient la liste des images
var path = 'images/vacances/';Pour commencer, il faut définir l'objet :
var pics = new Array(
'001.jpg',
'002.jpg',
'003.jpg',
'004.jpg',
'005.jpg',
'006.jpg',
'007.jpg',
'008.jpg',
'009.jpg',
'010.jpg'
);
var s = new Slideshow('pictureHolder', 'counter', path, pics);
s.showLengthInSeconds(1);
// et plus tard, contrôler le défilement
s.start();
s.end();
function Slideshow(pImageId, pCounterId, pPath, pPicsArray) {Ici, on utilise le mot clé "this" pour attacher des propriétés à l'objet Slideshow. Les variables sont initialisées aux valeurs par défaut et selon les paramètres reçus.
var _self = this; // closure :-)
this.holderId = pImageId;
this.counterId = pCounterId;
this.path = pPath;
this.pics = pPicsArray;
this.appearLength;
this.currentPicture = 0;
this.timerDisplay;
this.timerChange;
// pour le moment, j'assume qu'il y a au moins une photo
this.totalPictures = this.pics.length;
}
La première ligne sert à créer une fermeture (closure), c'est-à-dire une référence sur l'objet actuel Slideshow. Comme notre objet contiendra des fonctions, si nous essayons d'utiliser "this" à l'intérieur, ce sera le "this" de la portée (scope) de la fonction, et non pas le "this" de notre objet. En créant une référence dans une variable dès le départ, nous serons en mesure de faire la distinction entre la portée locale de la fonction et globale de l'objet.
À l'intérieur de Slideshow(), on attachera des fonctions anonymes (leur définition ne contient pas de nom, on les appellera par le nom associé à l'objet)
this.showLengthInSeconds = function(sec) {En conclusion, il y a trois notions importantes à retenir pour réussir à faire fonctionner le tout sans problème. Elles se retrouvent dans les fonctions displayFor() et waitFadeOutEnd() :
_self.appearLength = sec;
}
this.start = function() {
_self.loadPicture();
}
this.loadPicture = function() {
// remarquez l'utilisation de _self
$(_self.holderId).src = _self.path + _self.pics[_self.currentPicture];
_self.showCounter();
Effect.Appear(_self.holderId, { duration:1, from:0.0, to:1.0 });
_self.displayFor(_self.appearLength);
}
this.showCounter = function() {
$(_self.counterId).update( (_self.currentPicture + 1) + ' / ' + _self.totalPictures + ' - ' + _self.pics[_self.currentPicture]);
}
this.displayFor = function(sec) {
// utilisation du closure pour faire le callback
_self.timerDisplay =
setTimeout(
function() { _self.changePicture(); },
sec * 1000
);
}
this.changePicture = function() {
_self.currentPicture++;
if (_self.currentPicture == _self.totalPictures )
_self.currentPicture = 0;
Effect.Fade(_self.holderId, { duration:1, from:1.0, to:0.0 });
_self.waitFadeOutEnds(1); // 1 sec, according to duration
}
this.waitFadeOutEnds = function(sec) {
// setTimeout s'attend à recevoir des millisecondes, donc paramètre secondes * 1000
_self.timerChange =
setTimeout(
function() { _self.loadPicture(); },
sec * 1000
);
}
this.end = function() {
clearTimeout( _self.timerDisplay );
clearTimeout( _self.timerChange );
}
- l'utilisation de setTimeout pour faire un appel à une fonction à un intervalle régulier
- conserver une référence sur le timerID (_self.timerDisplay et _self.timerChange). Ceci nous permettra de stopper le callback, donc d'arrêter le diaporama
- l'utilisation du closure _self pour appeler la fonction à l'intérieur d'une fonction anonyme, plutôt que d'utiliser setTimeout avec le code en texte
Ce matin, pendant que ma blonde dormait, j'ai fait un petit exercice pour créer un diaporama de photos simple en JavaScript, où les images défilent automatiquement. En moins d'une heure, j'ai réalisé deux versions, une utilisant des méthodes standards procédurales, et l'autre, que je présenterai demain, qui est plus orientée objet et qui introduit les fermetures. J'ai écrit ce code pour le plaisir, alors je ne prétends pas qu'il soit parfait et je ne serais certainement pas prêt à le mettre tel quel en production sans l'avoir préalablement paufiné. Comme tout ce que je présente ici, l'objectif est surtout de présenter le concept et de voir les applications possibles.
Pour atteindre mon but, j'ai utilisé la librairie Prototype pour simplifier les appels document.getElementById par $() et Script.aculo.us pour ajouter un effet de transition dans le chargement des images. Le reste est du JS pur. Je peux aussi dire que je l'ai testé uniquement sous Firefox 3 et Internet Explorer 7.
Pour commencer, j'ai défini un conteneur HTML pour la photo et un autre pour le compteur d'images qui apparaîtra au dessous. L'objectif est simplement de créer une boucle qui chargera alternativement les images d'une liste selon un intervalle régulier. La balise <img> servira à recevoir l'image en changeant dynamiquement son attribut source. Le compteur indiquera le numéro de l'image courante et le nombre d'images au total.
<p><img id="picture" src=""></p>On définit en JavaScript le chemin où se trouvent les images et une variable array liste tous les fichiers.
<span id="detail"></span>
var path = 'images/vacances/';On initialise des variables pour indiquer l'image courante (l'indice dans la liste), le nombre total de photos et le temps d'affichage de chacune d'elles.
var pics = new Array(
'001.jpg',
'002.jpg',
'003.jpg',
'004.jpg',
'005.jpg',
'006.jpg',
'007.jpg',
'008.jpg',
'009.jpg',
'010.jpg'
);
var currentPicture = 0;Au chargement de la page, je démarre le slideshow en utilisant la notation de Prototype. Ensuite, je charge la première photo en appelant la méthode LoadPicture.
var totalPictures = pics.length;
var showImageSeconds = 4;
document.observe('dom:loaded', startSlideShow );Dans le conteneur d'image <img>, on remplace la source par le chemin de la première image dans l'array. On met à jour le compteur avec les valeurs courantes en utilisant update() de Prototype, mais on aurait pu le faire aussi bien avec InnerHTML. On fait apparaître l'image avec l'effet Appear de Scriptaculous (qui est l'inverse de Fade) qui dure une seconde. On affiche ensuite l'image quelques secondes selon la variable définie plus haut.
function startSlideShow() {
LoadPicture();
}
function LoadPicture() {Pour afficher l'image un certain temps, j'appelle setTimeout qui me permet d'effectuer une pause avant de montrer la suivante. Lorsque le temps se sera écoulé, la fonction ChangePicture sera appelée.
$('picture').src = path + pics[currentPicture];
// compteur
$('detail').update( (currentPicture + 1) + ' / ' + totalPictures);
Effect.Appear('picture', { duration:1, from:0.0, to:1.0 });
Display(showImageSeconds);
}
function Display(sec) {Lors du changement de photo, on vérifie si l'indice est le dernier de la liste. Si c'est le cas, on revient au début pour afficher la première photo. Un petit effet la fait disparaître et comme on veut voir la transition correctement, on doit attendre une seconde. Le temps de l'effet est spécifié dans la propriété duration alors on fait coïncider l'appel de Wait() pour attendre que l'effet soit terminé avant de changer d'image.
setTimeout("ChangePicture()", sec * 1000);
}
function ChangePicture() {Lorsque l'effet est terminé, on charge la photo suivante.
currentPicture++;
// si c'est la dernière
if (currentPicture == totalPictures )
currentPicture = 0;
Effect.Fade('picture', { duration:1, from:1.0, to:0.0 });
Wait(1);
}
function Wait(sec) {En exécutant le code, on devrait voir les images défiler en boucle une à une. Le problème avec ce genre de code procédural, c'est que ça ne forme pas un tout, il n'y a pas d'unité qui relie les variables et fonctions entre elles. Demain, je présenterai une refonte du code en version orientée objet et j'expliquerai le concept des fermetures JavaScript (closures) pour pouvoir créer un objet Slideshow mieux structuré, plus facile à comprendre et à maintenir.
setTimeout("LoadPicture()", sec * 1000);
}
Eh oui, Microsoft a enfin lancé officiellement la dernière mouture de son fureteur populaire, Internet Explorer 8. On y propose maintenant une sécurité accrue (protection contre les logiciels malveillants), une confidentialité améliorée, la navigation privée InPrivate, les web slices (abonnement à des portions de site), etc.
L'innovation la plus intéressante est certainement les accélérateurs qui permettent de souligner un bout de texte avec la souris et de lancer une action sur ce terme en utilisant un service en ligne.
Oh, mais attendez! Firefox possédait déjà un plugin du même genre : Select-n-Go by Cleeki.
Malgré tout, je dois avouer être plutôt enthousiaste car je vois dans ce lancement une bonne nouvelle : on s'éloigne de plus en plus d'IE 6 et des maux de têtes qu'il occasionne. Espérons que les utilisateurs effectueront la mise à jour.
En conclusion
En ce 20 mars 2008, je déclare que IE8 est certainement le meilleur fureteur depuis Netscape 5... Et vous, quels sont vos pronostics ?
Avec les années, j'ai pris l'habitude de développer mes projets web en utilisant l'encodage de caractères UTF-8. Non seulement les pages web sont enregistrées au format Unicode, mais les fichiers JavaScript et la base de données le sont aussi.
Avec le temps, j'ai ajouté des scripts à ma librairie principale de code et je ne me suis pas posé de question puisqu'ils étaient tous au standard Unicode. Jusqu'à ce que j'aie à effectuer une refonte et un refactoring d'un vieux projet encodé en ISO-8859-1 que je voulais migrer en UTF-8... Ouf! Je comptais bien améliorer le code en utilisant Prototype ou jQuery, un peu de Ajax et ce en espérant ne pas rencontrer de conflits d'encodage. Mon problème principal a été au niveau des textes statiques, où les caractères accentués étaient tous intégrés en utilisant les entités HTML (é pour é). Alors comment pouvoir convertir automatiquement tous ces caractères spéciaux dans les pages de mon projet pour qu'ils respectent la norme Unicode ?
Que ce soit pour convertir du format ISO à UTF-8 ou le contraire, Adobe Dreamweaver offre la possibilité de le faire d'un seul coup. Contrairement à d'autres éditeurs qui ne permettent d'enregistrer que sous un autre type d'encodage, Dreamweaver va un peu plus loin et convertit les entités d'un format à un autre.
Pour convertir une page existante :
- Menu Modify
- Page Properties
- Title / Encoding
- Choisir le nouveau format (par exemple Europe Occidentale ou UTF-8)
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
On pourra aussi spécifier l'encodage à utiliser lors de la création des nouveaux documents :
- Menu Edit
- Preferences
- New Document
- Default encoding
- Choisir dans la liste déroulante "Europe Occidentale" ou "UTF-8"
La base de données PostgreSQL offre plusieurs opérateurs utiles pour effectuer des comparaisons.
Opérateur =
Pour comparer de façon exacte deux chaînes de caractères :
SELECT * FROM table WHERE field = 'code18'
LIKE
Le mot réservé LIKE est standard dans le monde des bases de données. Il permet de vérifier si le texte comparé respecte partiellement un pattern :
commence par : WHERE title LIKE 'code%'
se termine par : WHERE title LIKE '%18'
contient : WHERE title LIKE '%code%'
ILIKE
Semblable à LIKE, ILIKE permet de faire la même comparaison mais en ne tenant pas compte de la casse (case insensitive) :
WHERE title LIKE '%CoDe%' -- ne trouvera pas "code18"
WHERE title ILIKE '%CoDe%' -- le trouvera
SIMILAR TO
Permet d'utiliser un pattern qui combine, comme pour LIKE, les wildcards _ (n'importe quel caractère unique) et % (n'importe quoi) :
WHERE encoding SIMILAR TO '[a-zA-Z0-9]%'
WHERE encoding SIMILAR TO '%utf-(8|16)%'
Ici, '%utf-*(8|16)%' est équivalent à '%utf-%(8|16)%'.
Expression régulière POSIX
Ou encore utiliser des expressions régulières. Un opérateur équivalent existe au pour case insensitive (~*) :
WHERE song_title ~ '^[0-9]+'
Résultats :
99 Luftballoons - Nena
3ème avertissement - Miriodor
100,000 People - Philip Glass
Accent Insensitive ?
Malheureusement, malgré la version 8.3 de PostgreSQL, il n'y a toujours aucune trace d'une recherche insensible aux accents. On peut certainement partenir à quelque chose en convertissant le texte avec to_ascii() mais avec certains encodages comme UTF-8, ce n'est pas supporté correctement. Un workaroung possible serait de créer une fonction qui remplace les accents pour pouvoir effectuer la comparaison. Aujourd'hui, Thom Brown a proposé sur la liste de diffusion de Postgre une fonction appelée unaccent_string :
CREATE OR REPLACE FUNCTION unaccent_string(text)
RETURNS text AS $$
my ($input_string) = @_;
$input_string =~ s/[âãäåaaa]/a;
$input_string =~ s/[ÁÂÃÄÅAAA]/A;
$input_string =~ s/[èééêëeeeee]/e;
$input_string =~ s/[EEEEE]/E;
$input_string =~ s/[ìíîïìiii]/i;
$input_string =~ s/[ÌÍÎÏÌIII]/I;
$input_string =~ s/[óôõöooo]/o;
$input_string =~ s/[ÒÓÔÕÖOOO]/O;
$input_string =~ s/[ùúûüuuuu]/u;
$input_string =~ s/[ÙÚÛÜUUUU]/U;
return $input_string;
$$ LANGUAGE plperl;
Appel :
WHERE unaccent_string(address) ILIKE unaccent_string('Montréal')
Finalement, une piste de solution viendra peut-être éventuellement dans une version ultérieure avec Text Search Configuration.
Dans les beaux jours de PHP 4, si on voulait parcourir et lister le contenu d'un répertoire, on devait s'y prendre comme ceci :
$directory = '/path/to/files';On peut encore utiliser cette bonne vieille méthode, mais PHP 5 a introduit dans son SPL la classe DirectoryIterator qui est beaucoup plus facile à comprendre. Voici le même bout de code traduit à la façon PHP 5.
if ($handle = opendir( $directory )) {
while ($file = readdir($handle)) {
if ($file != '.' && $file != '..') {
echo $file;
}
}
closedir($handle);
}
$iter = new DirectoryIterator( $directory );L'objet $file possède plusieurs méthodes pour nous simplifier la vie, comme par exemple getPath, getPathName, getSize, isDir, isFile, isDot, isExecutable, isReadable et isWritable.
foreach($iter as $file ) {
if ( !$file->isDot() ) {
echo $file->getFilename();
}
}
En revanche, si le répertoire contient d'autres dossiers à parcourir, il suffit d'utiliser la classe RecursiveDirectoryIterator.
$iter = new RecursiveDirectoryIterator( $directory );Dans les deux derniers cas, on peut parcourir la liste à l'aide d'un foreach puisque les deux classes implémentent l'interface Iterator, un pattern populaire.
foreach (new RecursiveIteratorIterator($iter) as $file) {
echo $file->getPath() . ' [' . $file->getFilename() . ']';
}
Ce midi, j'ai commandé un livre en ligne et je suis tombé par hasard sur la page 404 d'Amazon.ca et l'image Kailey Kitty.
Par définition, une page 404 correspond à la réponse HTTP pour indiquer que la page demandée est introuvable. C'est généralement le genre de page qui ne mérite aucune attention de la part des visiteurs car elle n'affiche qu'un simple "404 Not Found".
Cependant, le web étant un endroit où tout est possible, certains se sont amusés à rendre la chose plus amusante.
Voici deux galeries d'images de pages 404 très originales publiées par Smashing Magazine :
Avez-vous déjà rencontré l'erreur T_PAAMAYIM_NEKUDOTAYIM ? Drôle de nom n'est-ce pas ? Cette constante PHP est utilisée pour définir l'opérateur de résolution de portée "::" (scope resolution operator), comme par exemple dans le cas suivant :
Classe::element
"Parse error: parse error, expecting 'T_PAAMAYIM_NEKUDOTAYIM' in ..."
Évidemment, quand on rencontre ce type d'erreur, le nom n'est pas très explicite et il faut fouiller pour comprendre le message. Il faut savoir qu'en hébreux, Paamayim veut dire "double / twice" et Nekudotaim est "colon", donc on fait référence à l'opérateur ::. En remontant plus loin dans l'histoire du développement, on découvre que Andi Gutmans and Zeev Suraski, les créateurs du Zend Engine de PHP et Zend Technologies, sont israéliens et ont étudié au Technion Israel Institute of Technology. D'ailleurs, le nom Zend est vient de la fusion de leurs prénoms : ZEev et aNDi.
Ça explique certaines choses. Mais pourquoi ne pas avoir standardisé les noms en anglais et avoir conservé celle-ci ? En fait, la question est aussi de savoir s'il existe d'autres noms curieux utilisés dans les entrailles du code source C. Alors cherchons les!
Sur la page de téléchargement de PHP, j'ai téléchargé le code source complet, php-5.2.9.tar.gz, soit l'archive stable la plus récente. Après l'avoir extraite, je me suis débrouillé pour utiliser une combinaison des commandes grep et sed pour effectuer les recherches.
D'abord, j'ai voulu obtenir la liste des fichiers et leurs lignes où on trouvait des constantes dont le nom débutait par T_. J'ai utilisé grep avec les options de recherches récursives (-r) dans les sous-répertoires et j'ai spécifié la recherche de mots complets (-w). Ensuite, j'ai défini une simple expression régulière et j'ai envoyé le résultat dans un fichier :
grep -r -w ^.*\bT_.*\b php-5.2.9 > all_lines_with_t_.txt
À partir du résultat, j'ai lancé la commande sed pour extraire les constantes trouvées. J'ai utilisé une regexp (-e), la substitution (s/old/new/) et la capture de pattern (\1).
sed 's/^.*\(\bT_\w*\b\).*$/\1/' all_lines_with_t_.txt > all_t.txt
Finalement j'ai effectué un tri avec filtre unique (sort -u) sur le fichier pour ne conserver qu'une seule occurence de chaque constante. Si vous êtes sous Windows, faites attention car il y a une autre fonction sort native. Assurez-vous d'utiliser celle Unix (Cygwin ou UnxUtils).
sort -u all_t.txt > sorted.txt
En jetant un coup d'oeil au fichier final, on peut noter 137 constantes différentes mais la seule qui porte un nom bizarre est T_PAAMAYIM_NEKUDOTAYIM. Ok, j'avoue que je ne sais pas encore ce que T_MX et T_FMT_AMPM veulent dire mais ce sont certainement des abréviations.
Qu'est-ce que ça représenterait de changer toutes ces constantes par un nom plus explicite ? J'imaginais pouvoir y arriver en le remplaçant par T_DOUBLE_COLON mais cette constante existe déjà dans un autre contexte. Alors pourquoi ne pas utiliser T_SRO ou T_SCOPE_RESOLUTION_OP ?
Pour trouver toutes les occurences de la constante dans l'ensemble du code source :
grep -r -w T_PAAMAYIM_NEKUDOTAYIM php-5.2.9 > paamayim_occurences.txt
Seulement 66 occurences trouvées. Ça devrait pouvoir se remplacer facilement.
Les mots de passe sont comme les sous-vêtements : on devrait les changer souvent, ne pas les exposer en public et ne jamais les donner à des étrangers.
L'autre jour, j'ai démontré comment manipuler les archives tarball à partir d'une ligne de commande mais le sujet était loin d'être complet si je n'abordais pas la question de la compression, l'idée étant de réduire au maximum la taille de l'archive en utilisant un algorithme de compression comme gzip ou bzip2.
Par exemple, en téléchargeant le code source php-5.2.9.tar.gz (12 Mo), on remarque que le fichier porte l'extension .tar .gz, ce qui est une convention pour indiquer que que le tarball est compressé en utilisant gzip.
Avant de pouvoir extraire l'archive, la première étape consistera à la décompresser :
gunzip -c php-5.2.9.tar.gz > unzipped.tar
-c permet de contrôler la sortie pour pouvoir ensuite l'envoyer dans un nouveau fichier que je nomme unzipped.tar. Autrement, si -c et > ne sont pas spécifiés, l'archive sera décompressée et écrasera le fichier original.
Ensuite, on pourra l'extraire l'archive unzipped.tar (70 Mo) :
tar -xf unzipped.tar
Patientez un moment car l'archive contient plus de 10000 fichiers.
On peut aussi combiner les deux commandes une à la suite de l'autre :
gunzip < php-5.2.9.tar.gz | tar xf -
Remarquez le symbole - à la fin.
Dimanche dernier, j'ai fait l'achat d'un ordinateur portatif Toshiba Satellite chez Future Shop. Les caractéristiques semblaient intéressantes :
- écran de 15,4 po
- disque dur de 250 Go
- 3 Go de mémoire vive
- processeur Intel à double coeur cadencé à 2,16 GHz
- caméra numérique
- graveur DVD double couche
- lecteur de cartes
- système d'exploitation bilingue
- une garantie d'un an du fabricant
- le tout pour 700$ (avant taxes)
En y réfléchissant un peu, ce n'était pas un gros risque, d'autant plus que la garantie couvrait les produits citrons. Je lui ai donc demandé l'autorisation de vérifier son fonctionnement avant de prendre une décision. À première vue, l'esthétique de l'ordinateur était impeccable et il y avait même certains autocollants de protection originaux. Il démarrait sans problème et tout semblait dans l'ordre. Jusqu'à ce que je remarque dans le haut de l'écran une marque qui était dissimulée sous des traces de doigts. J'ai essayé de le montrer au vendeur mais même en tentant de l'essuyer, il semblait incapable de voir la même chose que moi.
J'ai pris une chance et j'ai décidé d'acheter le portable quand même. De toute façon, le vendeur disait que si je remarquais quelque chose après l'achat et que je n'étais pas satisfait, je pouvais le retourner dans les 14 jours. En cas de problème, l'appareil serait réparé et en cas de trois retours, il serait échangé contre un produit neuf équivalent.
Arrivé chez moi, j'ai eu le réflexe de mettre un fond d'écran uni pour détecter plus facilement les pixels brûlés. Sans surprise, ce que j'avais cru voir était bel et bien un pixel éteint. Une autre déception a été que l'ordinateur était supposé avoir été réinitialisé comme s'il sortait de l'usine. Or, le système d'exploitation était en français et il n'y avait aucun moyen de revenir en arrière et d'installer le OS en anglais. C'est un détail qui m'agace car comme mes applications sont anglaises, si je les utilise sur un OS en français, certains menus et options sont affichés dans les deux langues en même temps. L'utiliser en anglais a aussi l'avantage de trouver plus facilement de documentation et d'aide sans avoir besoin de traduire les termes.
Pour Windows, l'explication que j'ai trouvé est qu'en usine, une partition a été réservée pour contenir les deux versions du système d'exploitation. Lors de la première ouverture de l'ordinateur, on doit choisir la langue à installer et la partition se supprime automatiquement par la suite. Et comme Windows est pré-installé, le portable ne vient pas avec le CD d'installation.
En y réfléchissant, j'ai eu le raisonnement suivant :
- Dans le cas des boîtes ouvertes, combien de fois un produit peut-il être retourné au magasin et revendu à la clientèle ?
- Combien de clients ont eu entre les mains ce même ordinateur ?
- Il paraît qu'un pixel brûlé se répare. Mais si l'appareil est neuf et qu'il a déjà des problèmes, est-ce un défaut de fabrication ? Quelles autres surprises m'attendent ? Devrais-je l'apporter à chaque fois pour le faire réparer et attendre parfois plusieurs semaines pour le recevoir ?
- Selon les termes de la garantie, suite à un échange, la garantie se termine automatiquement. Est-ce qu'un pixel peut justifier la défectuosité que couvre la garantie ? Après tout, ce n'est que de l'affichage...
- Si je profite de la garantie après avoir été quelques jours en possession du portable, j'aurais payé 30$ supplémentaires pour rien pour ajouter une troisième année de garantie. À ce prix-là, j'aurais été mieux d'en acheter un neuf.
- Comme on m'a donné la garantie, sa valeur est soustraite de la valeur de l'ordinateur. Donc le coût du laptop diminue à 440$ (surprise!). En cas d'échange, c'est la valeur que j'aurais eu pour en choisir un nouveau. Et à ce prix, les portables se font rares, sont peu puissants et de moindre qualité.
- Je me suis souvenu que mes achats portés à ma carte Visa m'offre gratuitement de doubler la garantie de réparation du fabricant jusqu'à concurrence d'une année supplémentaire.
- Enfin, le meilleur choix est certainement le retour et le remboursement de la marchandise. Surtout pour avoir l'esprit tranquille.
Au final, je n'ai eu aucun problème à me faire rembourser mais malheureusement, quelqu'un d'autre sera pris avec ce défaut...
Dans certaines distributions Linux comme Ubuntu et Kubuntu, il y a une commande nommée "apt-get" et une autre qui porte le nom "aptitude" qui permettent d'installer les packetages et ses dépendances.
On obtiendra le résultat suivant si on lance cette commande :
~$ apt-get moo
(__)Alternativement, si on lance aptitude avec l'argument -v (pour afficher des informations supplémentaires) :
(oo)
/------\/
/ | ||
* /\---/\
~~ ~~
...."Have you mooed today?"...
~$ aptitude -v moo
Il nous répondra :
There really are no Easter Eggs in this program.
~$ aptitude -vv moo
Didn't I already tell you that there are no Easter Eggs in this program?
Un oeuf de pâque pour dire qu'il n'y en a pas ?
L'entreprise CoSoSys a créé une bande dessinée en 6 langues (dont français et anglais) pour sensibiliser le public à la sécurité informatique. Excellente initiative!
Endpoint Security Adventures - Comic Series
Dans l'univers de la programmation web, une des actions les plus fréquentes est de remplir un formulaire pour tester l'envoi des données et les enregistrer dans une base de données. Pour nous faciliter la vie, voici deux extensions Firefox qui permettent de remplir un formulaire automatiquement.
Web Developer Toolbar
Dans Web Developer Toolbar, on peut utiliser une option pour remplir automatiquement les champs d'un formulaire. WDT extraira le nom de chaque champ et utilisera le texte pour populer la valeur.
Ici, le texte saisi sera "firstname" :
<input type="text" id="firstname" name="firstname" value="" />Si l'input porte un nom qui contient le terme "email", il remplira ce champ avec une adresse courriel (par défaut example@example.com). Vous pourrez personnaliser le courriel avec le vôtre en accédant au menu Options / Options / Miscellaneous / Encadré Forms.
Selenium IDE
L'autre possibilité, beaucoup plus flexible, est d'utiliser l'extension Selenium, de loin une de mes préférées. Selenium possède une nette avance sur WDT : on peut enregistrer nos actions sur un formulaire, l'information exacte saisie, les événements déclenchés, etc, et créer un test case / test suite. Par exemple, on pourra facilement remplir les différents champs du formulaire avec les valeurs désirées, effectuer des choix dans des listes déroulantes, lancer des appels Ajax, cocher des cases, cliquer sur des contrôles et soumettre le formulaire, tout ça d'un seul coup.
Avouez que c'est génial et que ça peut nous faire sauver beaucoup de temps. Donc, moins de temps à remplir des formulaires et à tester et plus de temps à passer sur ce que l'on aime vraiment : programmer.
Pour bien démarrer avec Selenium, installez l'extension et démarrez la à partir du menu Tools / Selenium IDE. Une fois lancée, repérez le bouton rouge dans le coin supérieur droit : c'est le bouton qui vous permettra de démarrer l'enregistrement des actions.
D'abord, rendez-vous sur le formulaire de votre choix.
- Entrez l'URL du formulaire dans la case Base URL
- Créez un nouveau Test Case et donnez lui un nom (clic droit de la souris / Properties)
- Démarrer l'enregistrement en cliquant sur le bouton rouge
- Dans le formulaire, saisissez l'information normalement
- Soumettez le formulaire
- Cliquez à nouveau sur le bouton rouge pour terminer l'enregistrement des actions
- Ouvrez un nouvel onglet dans Firefox et accédez à nouveau au formulaire vierge
- Exécutez l'ensemble des actions en cliquant sur le bouton Play Current Test Case
- Admirez l'automatisation
N'oubliez pas d'enregistrer vos tests pour une utilisation future. Mieux encore, vous pouvez exporter automatiquement vos tests pour PHPUnit. Menu File / Export Test Case As / PHP Selenium RC (Java, C#, Perl, Python et Ruby aussi disponibles).
En PHP, l'interface interne la plus simple à implémenter est sans aucun doute countable, qui est fournie de façon native avec la SPL (Standard PHP Library). Comme son nom l'indique, une classe qui implémente l'interface countable doit pouvoir être en mesure de compter ses items qui la compose.
Cette interface est simple car elle ne nécessite que l'implémentation de la méthode count(). Autrement dit, la classe que vous créez doit simplement posséder une fonction count() qui retourne le nombre d'éléments.
À la base, on sait qu'on peut compter les éléments d'une liste (array) :
echo count($arr);En utilisant un exemple tiré du site php.net, on voit que la représentation interne des données pourrait simplement être un entier statique :
class myCounter implements Countable {En définissant une autre classe pour gérer une liste d'épicerie, on pourrait conserver la liste de n'importe quelle façon. Ici, j'ai utilisé un array pour la représentation interne mais ça aurait pu être n'importe quoi.
public function count() {
static $count = 0;
return ++$count;
}
}
class GroceryList Implements Countable {Si je n'avais pas mis "Implements Countable" dans la définition de ma classe, count() n'aurait pas pu déterminer le nombre d'items dans la liste interne et aurait retourné 1 (1 étant l'objet lui-même. Sa méthode count() ne serait pas appelée). Dans ce cas, j'aurais pu utiliser $gl->count(); pour obtenir le bon nombre. Question de standardiser les objets qui se comptent, on est mieux d'implémenter countable.
private $someData;
public function __construct() {
$this->someData = array();
}
public function add($pItem, $pQty) {
$this->someData[] = array('name' => $pItem, 'qty' => $pQty);
}
public function count() {
$count = 0;
foreach($this->someData as $item) {
$count += $item['qty'];
}
return $count;
}
}
// type 1Dans l'exemple ci-dessus, tous les objets peuvent être comptés, ce qui permet de boucler sur tous les types d'objets qui se comptent (array, myCounter, GroceryList) puisque la méthode count() et commune et uniforme.
$list = array(1,2,3,4,5);
// type 2
$counter = new myCounter;
count($counter); // 1
count($counter); // 2
count($counter); // 3
// type 3
$gl = new GroceryList();
$gl->add('Hamburger buns', 12);
$gl->add('Ground beef', 2);
$gl->add('Relish', 1);
$gl->add('Ketchup', 1);
$gl->add('Mustard', 1);
// créer une liste qui contient différents objets qui peuvent se compter :
$countables = array();
$countables[] = $gl;
$countables[] = $list;
$countables[] = $counter;
foreach ($countables as $cnt) {
echo count($cnt);
}
C'est reconnu qu'un programmeur qui effectue de l'intégration fera généralement un travail douteux. On doit se l'avouer, pour la plupart, nous n'avons pas l'oeil. C'est pourquoi j'apprécie les librairies graphiques qui me simplifient la vie comme script.aculo.us et Open Rico. Dans le cas de Rico, l'avantage que j'en tire est de pouvoir générer des conteneurs à coins ronds (rounded corners) en quelques lignes de code, et par programmation, pour mon plus grand bonheur.
On doit d'abord charger sa librairie principale (rico.js) et la dépendance à prototype.js. Ensuite, on chargera dynamiquement l'extension "Corner" et on appliquera la transformation sur les conteneurs désirés en utilisant les ID.
<script src="js/prototype/1.6.0.3/prototype-1.6.0.3-min.js"></script>La fonction round() peut aussi recevoir des paramètres optionnels qui indiquent le type de coins à utiliser, la couleur de la bordure (s'il y a lieu) et quels coins on doit arrondir.
<script src="js/rico2/src/rico.js" type="text/javascript"></script>
<script type='text/javascript'>
Rico.loadModule('Corner');
Rico.onLoad( function() {
Rico.Corner.round('conteneur1');
});
</script>
On y ajoute un peu de CSS pour rendre le tout joli. En plus de définir une classe "card" qui représente le conteneur principal, j'en crée une deuxième qui me permettra d'avoir un meilleur contrôle sur le détail que contiendra le conteneur.
body {Un exemple de conteneur qui sera transformé :
font-family: verdana;
font-size:12px;
}
div.card {
background-color: #cecece;
float:left;
margin:0;
padding:0;
width:400px;
min-height:150px;
height:150px;
}
div.card-detail {
height:100%;
}
<div id="conteneur1" class="card">Quelques recommandations :
<div class="card-detail">
</div>
</div>
- attention de ne pas appeler round() deux fois sur le même item...
- faites gaffe avec les bordures et l'application des classes CSS avec Internet Explorer. Ça peut être désastreux.
- sous IE, assurez-vous d'appliquer la bordure à l'aide des options Rico et pas avec le CSS. Autrement, le CSS risque d'être appliqué au rectangle et la bordure pourrait sembler brisée dans les coins
- encore avec IE 7, le seul truc que j'ai trouvé pour pouvoir spécifier la hauteur du conteneur comme il faut a été de mettre un conteneur à l'intérieur du conteneur principal avec une hauteur de 100% (comme l'exemple ci-dessous) et un min height au div principal.
Dernière journée de la conférence PHP. Au programme, plusieurs sessions intéressants :
- Building PHP Powered Android Applications (téléphones mobiles).
- Growing a Development Team While Building a Huge App at 500 miles/hour (avec Owen Byrne, co-fondateur de Digg, maintenant avec TravelPod Labs, qui appartient à TripAdvisor). J'adore sa biographie sur Twitter : The person who "built digg for $1000 @ $10/hour".
- Git et le renouveau du contrôle de versions. Une alternative intéressante à Subversion.
- K.I.S.S. (Keep It Simple, Stupid). Trop souvent négligé.
- Laboratoire sécurité : audit de code PHP en deux parties.
- La clinique de Regex d'Andrei. Un tour complet de la puissance des expressions régulières. J'espère que sa présentation sera disponible en téléchargement sur le site de PHPQuébec.
- PHP Worst Practices (il y avait aussi une autre session nommée Bonnes pratiques en un clin d'oeil).
- PHP 5, Ajax avec jQuery et JSON.
La journée s'est terminée par une session commune dans la salle principale où Chris Shiflett a fait une présentation sur le design axé sur la sécurité. C'était léger et peu technique (tant mieux) mais ses exemples étaient introduits par des jeux de photos et de vidéo qui illustraient ses propos. Si vous n'avez pas entendu parler de l'exploit de Twitter "Don't Click", je vous réfère vers son blogue pour en savoir plus.
Pour clôturer la conférence, il y a eu un petit tirage d'une dizaine de prix de présence : livres, peluches elePHPant, laisser-passer pour la conférence PHP Québec 2010, etc. Les intéressés pouvaient ensuite aller faire la fête aux 3 Brasseurs et en profiter pour regarder sur écran géant le match (détaite) du Canadiens. Et ce matin, ceux qui avaient encore de l'énergie pouvaient participer à la sortie annuelle à la cabane à sucre. C'est exactement pour cette raison que je suis présentement chez moi :-)
La semaine dernière, en téléchargeant un tarball (.tar) de Smarty, j'ai eu l'idée d'aborder le sujet des archives. Trop souvent, les utilisateurs se tournent vers les interfaces graphiques pour réaliser le travail à leur place alors que pourtant, ça se résume en une seule ligne à exécuter par l'invite. Je ne peux pas en vouloir à monsieur et madame tout le monde, mais les développeurs et les gens qui travaillent dans les technologies de l'information (TI) devraient être en mesure de pouvoir faire cette opération alternative.
Pour commencer, un tarball représente un fichier d'archive qui contient plusieurs fichiers réunis ensemble (comme un zip) et qui peut être décompressé pour récupérer les fichiers originaux. Son nom provient de Tape ARchive puisque qu'il a toujours été utilisé pour des fins de distribution et d'archivage.
Extraire le contenu d'une archive TAR
En prenant pour exemple l'archive Smarty-2.6.22.tar, voici la ligne de commande la plus simpliste à exécuter pour extraire les fichiers :
tar -xf Smarty-2.6.22.tar
où l'option -x indique qu'on veut extraire l'archive et -f (combiné -xf) spécifie le nom de l'archive à traiter. On voit parfois l'utilisation de -v (-xvf) pour lister les fichiers au moment de l'extraction.
Créer une archive TAR
En prenant pour acquis que l'archive Smarty ait été extraite dans un répertoire du même nom que le tarball original, on pourrait réutiliser les fichiers du répertoire pour créer un nouveau tarball.
tar -cf Smarty-new.tar Smarty-2.6.22/
où l'option -c indique qu'on veut créer une archive et -f est encore présent pour spécifier le nom du fichier. On indique ensuite le nom du répertoire source.
Plutôt que d'utiliser un répertoire, on peut aussi spécifier les fichiers individuellement :
tar -cf archive.tar fichier1 fichier2 fichier3
Lister les fichiers à l'intérieur d'un TAR
En tout temps, on pourrait vouloir consulter le contenu d'un tarball. Avant de l'extraire, on peut utiliser une autre commande qui listera simplement le contenu :
tar -tf archive.tar
On utilisera l'option -t pour indiquer qu'on souhaite connaître la liste des fichiers de l'archive et encore une fois -f pour spécifier le nom du fichier à observer.
Pour cette deuxième journée à la conférence PHP Québec, la journée a débuté un peu plus tard que la veille, soit à 8h30. 30 minutes de sommeil bien appréciées, d'autant plus qu'avec le salon emploi qui se tenait en fin de journée, ça s'annonçait une grosse journée.
Depuis le début, je comptais bien assister à la session "PHP 5.3" par Johannes Schlüter, un développeur de Sun qui travaille sur le projet MySQL mais qui est aussi parmi les responsables de la version 5.3 de PHP. J'avais déjà lu un peu sur les nouvelles fonctionnalités intégrées (support unicode, namespaces, closures, etc.) et c'était intéressant d'écouter ce qu'avait à dire un acteur de premier plan impliqué dans son développement.
En guise d'entrée avant le dîner, John Coggeshall a fait une présentation (Beyond the Browser) pour expliquer que le web fait du sur place depuis plusieurs années en raison du fureteur. Son discours suggérait que le web se dirigera vers l'ajout de vues différentes parmi lesquelles le fureteur ferait (entre autre) parti. Il a aussi noté les lacunes des applications web qui s'exécutent à travers un fureteur et qui ne peuvent intégrer de support matériel, comme par exemple la possibilité d'utiliser des lecteurs de codes barres, imprimantes, etc. Ce sera vraisemblablement le prochain gros défi.
Au menu midi, le repas était encore une fois excellent mais je n'ai pas eu l'embarras du choix pour le dessert... Un peu oublié la veille, le mot s'est passé et tout le monde en a profité avant qu'il n'en reste plus.
Pour occuper mon après-midi, j'ai choisi de participer à l'atelier Design Patterns. Plusieurs provenaient de ceux répertoriés par la Gang of Four (Gof) : 5 modèles de conception de création (Factory, Abstract Factory, Builder, Prototype et Singleton), 7 de structure (Adapter, Bridge, etc.) tandis que le conférencier en a aussi présenté quelque uns d'architecture d'entreprise de Martin Fowler (Active Record, DAO, Lazy Load, MVC, Front Controller, 2 step view). D'ailleurs, ses livres ainsi que Design Patterns sont certainement des lectures fortement recommandées. Mais tenez-vous en à la version originale, nous dit-on; la version française est horrible!
Pour terminer la journée, j'ai fait un peu de réseautage social durant le 5 à 7, en sirotant un verre, sans vraiment porter attention aux chasseurs de têtes et aux kiosques des employeurs... quoi que la présence de membres des Forces Armées Canadiennes m'a intrigué un bout de temps. Mais rassurez-vous, j'ai su résister à la tentation d'aller les voir. Pour les autres entreprises, de jolies demoiselles étaient présentes pour rencontrer les candidats à l'emploi. Chose certaine, c'est un bon truc pour attirer un public majoritairement masculin.
Je me suis levé de bonne heure ce matin car je devais me préparer pour aller à la place Bonaventure (Montréal) pour assister à la conférence PHP Québec 2009. Comme je n'avais pas pu y être la dernière fois, j'attendais ce moment avec hâte.
À l'arrivée, on devait procéder à l'inscription où on nous remettait notre badge ainsi qu'une serviette de cuirette qui contenait un bloc-note, un stylo, un DVD vidéo de 6 heures sur les sessions de la conférence de 2003, un surligneur miniature à l'effigie d'un commanditaire, une copie de l'application SQL Anywhere 11 Web Edition for Windows et une publicité en format signet / carte d'affaire pour une entreprise qui embauche dans le domaine du divertissement pour adulte sur le web (franchement original! d'autant plus que cette compagnie est un commanditaire de l'événement).
En ouverture, on a eu droit à une introduction par le co-auteur de PHP, Zeev Suraski, qui a expliqué pour quelles raisons PHP était gagnant (Why PHP Wins). Malheureusement, on a appris que Sara Golemon (Yahoo!) n'avait pu être présente pour des raisons de santé et que ses interventions allaient être remplacées. Ensuite, au programme de la journée, il y avait plusieurs sessions qui se déroulaient simultanément, alors chaque participant devait faire un choix selon ce qui l'intéressait. Pour ma part, j'avais un intérêt dans la plupart des sessions, sauf celles où étaient abordées les CMS (Joomla, Typo3, ImpressCMS). Le choix n'a pas été facile.
Évidemment, ça serait difficile de résumer les présentations mais pour cette première journée, j'ai pu m'initier à 5 sujets très différents les uns des autres tous reliés au PHP. Le niveau de difficulté variait selon le sujet abordé mais j'en suis sorti satisfait car j'y ai appris quelque chose à chaque fois. Aussi, à chaque conférence, un livre était tiré dans l'assistance. Dans le domaine du curieux, paraît-il qu'un employé de Google donnait une présentation et qu'elle était incompréhensible tellement le niveau de complexité était élevé. On est geek ou on ne l'est pas!
Un autre truc comique que j'ai noté, à chaque fois qu'un conférencier posait la question à savoir qui travaillait sur Windows, personne n'osait lever sa main... Mais en même temps, la proportion de gens qui avait apporté leur ordinateur portatif roulant sur Ubuntu était assez grande.
Avec ce que j'ai vu jusqu'à maintenant, je suis convaincu que les 500$ (prévente) demandés pour assister à cette conférence de 3 jours sont bien investis (et ce n'est pas cher). Les conférenciers proviennent d'un peu partout à travers le monde et apportent vraiment un plus dans leurs messages. C'est vraiment stimulant de savoir qu'on peut amener les choses à un autre niveau et qu'il y a tant à découvrir.
Demain, il y aura un salon emploi d'organisé. Même si je ne suis pas à la recherche de travail, je compte bien y faire un tour puisqu'on nous a remis deux billets pour obtenir une consommation gratuite. J'y ferai honneur :-)
Lorsque vous testez vos pages web dynamiques, est-ce que ça vous modifiez directement les paramètres de la query string dans la barre d'adresse du fureteur ? Quand vous postez un formulaire, devez-vous rafraîchir la page et la soumettre à nouveau ? Il est temps de changer vos habitudes. Grâce à une petite extension Firefox qui se nomme UrlParams, vous pourrez automatiquement lire, séparer et modifier les paramètres GET et POST.
À tout moment, ouvrez le panneau en utilisant la combinaison des touches CTRL+ALT+U (sinon il se trouve dans View / Sidebar). Vous verrez alors apparaître deux sections qui récupèrent les paramètres GET (page.php?q=test) et POST. Chaque paramètre sera séparé en deux champs, soit la clé (q) et la valeur (test), les deux étant modifiables pour regénérer une requête HTTP. Très simplement, il est possible d'ajouter une combinaison en utilisant l'icône "+" alors qu'en décochant une des clés, vous la désactiverez. Il ne restera qu'à cliquer sur le bouton "Submit" pour relancer la requête.
Vous pourrez ainsi tester la réception des champs d'un formulaire sans même en utiliser un. Cet outil est un atout quand on veut tester une requête Ajax puisque la page dynamique doit recevoir certains paramètres pour en retourner un objet Json ou XML. On peut même faire en sorte de transformer l'ensemble des paramètres reçus pour convertir une requête GET en POST (et vice versa). Simple et pratique.
Sur une note différente, demain est le début de la conférence PHP Québec à laquelle j'assisterai. Je vous donnerai un petit compte rendu de mes découvertes.
Récemment, j'ai commencé à évaluer les options possibles pour protéger la propriété intellectuelle dans les librairies PHP distribuées, plus particulièrement dans les applications web. Évidemment, on n'a pas à avoir de souci quand on héberge les projets sur nos propres serveurs mais il vient un temps où certains clients souhaitent déménager leur projet chez un autre hébergeur et c'est là qu'on sent la nécessité de protéger certaines portions critiques contre les gens mal intentionnés (vol de code, espionnage industriel, ingénierie inversée / reverse engineering). On met des efforts pendant des mois, parfois des années à développer et paufiner des mécanismes et des processus intelligents, il est normal de vouloir protéger notre savoir. Comme PHP est un langage interprété (non compilé), et que le code peut être lu et décortiqué facilement par des programmeurs, on est aussi bien de leur compliquer la vie un peu!
Lors de mes recherches, j'ai retenu deux possibilités : Zend Guard (basé sur la notoriété de Zend, c'est celui que j'ai testé) et IonCube (efficace et un peu moins cher).
Zend Guard, successeur de Zend Encoder, permet de prendre un projet, le sélectionner dans son ensemble ou en partie et de convertir les instructions PHP en un code intermédiaire illisible par un être humain normalement constitué. Ce code, obfusqué et encodé, pourra ensuite être lu, compris et exécuté par Zend Optimizer, un gratuiciel qui doit être installé sur le serveur qui héberge le projet.
Pour appliquer simplement la protection à un projet, suivez les instructions suivantes :
- Menu File / New / Zend Guard Project
- Entrez le nom du projet et le chemin où les fichiers de configuration seront conservés
- Indiquez le nom du produit (comme s'il s'agissait d'une application web) et la version
- Spécifiez l'emplacement où vous voulez déposer les fichiers une fois encodés
- À l'étape suivante, choisissez le répertoire où sont placés les fichiers sources PHP (racine)
- Pour des fins de tests, terminez en utilisant les options par défaut
Dans la fenêtre Guard Explorer (normalement à gauche, elle ressemble à un explorateur de fichiers) :
- Sélectionner les fichiers et répertoires à exclure, ouvrez le menu contextuel avec le bouton droit de la souris et choisissez l'option "Exclude resource"
- En ouvrant le menu contextuel sur le nom du projet (racine de l'explorateur de fichiers), cliquez sur "Encode Project"
Dans le répertoire que vous avez spécifié plus tôt, vous retrouverez les fichiers encodés. Vous pourrez vérifier que les fichiers choisis ont bien été transformés en les ouvrant avec votre éditeur préféré. Vous ne devriez plus reconnaître les portions PHP. Ceux exclus demeureront intacts.
Pour terminer, il ne restera qu'à transférer les fichiers sur le serveur en utilisant un client FTP. À l'exécution du code, si vous voyez apparaître une erreur du type "Fatal error: Unable to read xxx bytes in chemin-du-fichier on line 0", c'est que vous n'avez pas transféré les fichiers encodés dans le bon mode. Forcez votre client FTP à utiliser le mode binaire (par exemple dans Filezilla : Menu Transfert / Mode de transfert / Binaire).
Bonne nouvelle pour tous les développeurs PHP qui ont besoin d'un espace d'hébergement comme laboratoire pour expérimenter différents modules et techniques, le service 000webhost.com peut vous fournir tout cela gratuitement (d'où leur nom 0.00$).
Voici leur offre:
- 1500 Mo d'espace disque
- 100 Go de bande passante
- Possibilité d'y attacher votre nom de domaine
- PHP 5.2.x (mail, cUrl, GD2, fopen, sockets, PEAR, et plus)
- MySQL
- cPanel
- Accès FTP
- Support pour Zend Optimizer et IonCube
- Pop3
- Cron jobs
- Activation instantanée
- Aucune publicité de leur part (vous pouvez ajouter les vôtres)
- 100% gratuit