All entries

Hier, j'ai voulu mettre une jolie barre de scroll sur un menu déroulant (dropdown) dans la barre des menus (navbar) de Bootstrap. Je suis donc parti à la recherche de plugins jQuery et je suis tombé sur LionBars dont le style (qui imite celui de OS X Lion) m'a pas mal plu. Il permet de rendre facilement un élément jQuery scrollable en appelant la fonction lionbars() dessus.

1er problème

Le premier problème est que si on l'appelle sur une dropdown de Bootstrap, LionBars ne fonctionnera pas car le menu n'est pas visible. Il faut donc, pour régler ce problème, appeler la méthode lionbars() lors d'un clic sur l'élément qui déclenche l'apparition du menu et uniquement lorsque la div englobant la dropdown (qui a la classe "btn-group") possède la classe "open" (afin qu'elle ne soit pas appelée lorsqu'on referme le menu déroulant). C'est ce que fait ce script (your-menu est une classe rajoutée à la div englobant la dropdown afin d'identifier les dropdowns sur lesquelles LionBars est utilisé) :

  1. $(document).ready(function() {
  2. $('.your-menu a.dropdown-toggle').on('click', function() {
  3. var menu = $(this).parent();
  4. if(menu.hasClass('open')) {
  5. var menuContent = menu.find('ul.dropdown-menu');
  6. menuContent.lionbars();
  7. }
  8. });
  9. });

2nd problème

Maintenant, LionBars fonctionne mais il y a un second problème : LionBars redéfinit le style CSS des listes non-ordonnées (ul), ce qui donne un résultat assez laid :

Cela arrive car LionBars applique une position relative aux listes non-ordonnées tandis que Bootstrap leur applique un positionnement absolu. On peut régler simplement ce problème en rajoutant la ligne suivante après l'appel à la fonction lionbars() dans le script précédent :

  1. menuContent.css('position', 'absolute');

On se retrouve maintenant avec un menu qui ressemble à ça, ce qui n'est déjà pas si mal :

3ème problème

La barre de défilement s'affiche donc bien mais on remarque que la petite flèche qui est censée apparaître en haut du menu n'apparait plus. La raison de cette disparition est expliquée sur cette page ainsi que le moyen de le régler : on va simplement recréer les styles appliqués aux éléments before et after des listes non-ordonnées :

  1. .your-menu ul.dropdown-menu {
  2. z-index: 0;
  3. height: 200px; /* You can change this value */
  4. }
  5.  
  6. .your-menu.open > a.dropdown-toggle::before,
  7. .your-menu.open > a.dropdown-toggle::after {
  8. content: '';
  9. display: inline-block;
  10. position: absolute;
  11. bottom: -9px; /* You can change this value */
  12. }
  13.  
  14. .your-menu.open > a.dropdown-toggle::before {
  15. border-left: 7px solid transparent;
  16. border-right: 7px solid transparent;
  17. border-bottom: 7px solid #CCC;
  18. border-bottom-color: rgba(0, 0, 0, 0.2);
  19. right: 7px;
  20. }
  21. .your-menu.open > a.dropdown-toggle::after {
  22. border-left: 6px solid transparent;
  23. border-right: 6px solid transparent;
  24. border-bottom: 6px solid white;
  25. right: 8px;
  26. z-index: 1001;
  27. }
Ces styles sont à peu de choses près les mêmes que ceux donnés dans la page mentionnée un peu plus haut. J'ai rassemblé les styles communs à before et after et j'ai fait en sorte que leur portée (scope) soit limitée aux dropdowns sur lesquelles est appliqué LionBars afin que les autres listes du site ne soient pas affectées. Il se peut que vous deviez modifier la propriété bottom qui définit la position verticale de la flêche pour l'adapter à la hauteur de votre barre de menus. La propriété height définit la hauteur maximale du menu déroulant. Pour éviter que les sous-menus mis en surbrillance lorsqu'ils sont survolés avec la souris débordent sur la barre de défilement, il suffit de rajouter une propriété padding-right (de 15px, par exemple) à la règle .lb-content dans lionbars.css

Le résultat obtenu est le suivant :

Code

J'ai créé un petit code de démonstration téléchargeable ici.
Last edited by afnarel on 15/09/12 at 13:38.

Ces derniers jours, j'ai développé, entre autres, un système de notifications tel que celui présent sur Facebook. J'ai décidé d'utiliser la fonction $.ajax() de jQuery (et ses dérivés : $.get(), $.getJSON()...) pour faire du long polling (voir aussi Comet). Cette fonction utilise l'objet XMLHttpRequest (ou plus précisément jqXHR depuis jQuery 1.5) pour maintenir une connexion HTTP au serveur active pendant un long moment.

Bref aperçu du long polling

Avec le protocole HTTP, la communication entre un serveur et ses clients est déséquilibrée : le serveur ne peut envoyer des données aux clients qu'en réponse à une demande explicite de leur part. Le long polling est une méthode qui permet de remédier à ce "problème", au prix d'une consommation CPU plus importante sur le serveur, puisque celui-ci doit réaliser une attente active (en PHP en tout cas).

Je ne vais pas détailler ici le fonctionnement du long polling puisque d'autres l'ont déjà fait de nombreuses fois. Si vous recherchez un bon exemple de code en PHP, je vous conseille cette vidéo qui explique en 5 minutes un exemple de base. Je me contenterai de préciser qu'avec ce code, si le serveur n'envoie aucune donnée pendant la durée définie par max_execution_time dans le fichier php.ini (30 secondes par défaut), le code s'arrêtera tout simplement en renvoyant une erreur "Fatal error: Maximum execution time of 30 seconds exceeded" (visible dans une console d'erreur Javascript telle que celle de Firebug). Pour que la fonction callback d'erreur définie dans $.ajax() soit appelée et relance le script, il faut définir un timeout ainsi:

  1. $.ajax({
  2. ...
  3. timeout: 40000, // -- Abort after [40] seconds.
  4. ...
  5. });
Il faut que ce timeout soit inférieur à la valeur de max_execution_time, sinon il n'aura aucun effet. Si vous voulez changer la durée maximale d'exécution d'un script PHP spécifique, vous pouvez utiliser la fonction set_time_limit($nb_seconds) dans ce script.

Bref, je n'ai pas décidé d'écrire cet article pour expliquer le long polling mais pour garder une trace d'un problème que j'ai rencontré en l'implémentant et de sa solution...

Le problème

Imaginons qu'on a un code tel que celui-ci sur le serveur :

  1. <?php
  2. set_time_limit(50); // Script will stop after running for [50] seconds
  3.  
  4. session_start(); // <===== Cette ligne est importante
  5.  
  6.  
  7. $lastupdate = isset($_GET['timestamp']) ? $_GET['timestamp'] : 0;
  8.  
  9. $lastmodif = lastModification();
  10.  
  11. // =====> Cette boucle aussi est importante <=====
  12. while($lastmodif <= $lastupdate) {
  13. usleep(10000);
  14. $lastmodif = lastModification();
  15. }
  16.  
  17. // The event occured => send the response
  18. $response = array();
  19. $response['timestamp'] = $lastmodif;
  20.  
  21. update($response, $lastupdate);
  22.  
  23. echo json_encode($response);
  24.  
  25. function lastModification() {
  26. return myQuery('SELECT COUNT(*) FROM `notifications` WHERE `read`=0 AND `user_id`=' . $_SESSION['user_id']);
  27. }
  28.  
  29. function update(&$response, $lastupdate) {
  30. ...
  31. }

Dans ce code, on veut avoir accès à la variable $_SESSION. On utilise donc un session_start(). Cependant les sessions PHP sont bloquantes ! Puisque l'appel à $.ajax() est réalisé dans un $(document).ready(), il n'y a pas de problème tant que l'utilisateur n'utilise le site que sur une page ou onglet à la fois :

  • Une session est ouverte par le script principal
  • Le script principal de termine
  • L'appel à notre script via Ajax est réalisé => le script Ajax ouvre une session
  • Les données sont reçues ou le timeout expire => la session ouverte est fermée
Par contre, si l'utilisateur décide d'ouvrir une deuxième page, il y a un interblocage (deadlock) :
  • Une session est ouverte par le script principal
  • Le script principal de termine
  • L'appel à notre script via Ajax est réalisé => le script Ajax ouvre une session
  • L'utilisateur ouvre une nouvelle page (alors que le script est en attente active de données dans la boucle while)
  • Le script PHP qui va se charger de l'affichage de la nouvelle page fait un appel à session_start()
  • Le chargement de la page est mis en attente jusqu'à ce que le script appelé via Ajax se termine et libère la session. L'utilisateur ne voit qu'une page blanche...

La solution

C'est un commentaire sur cette page qui m'a fourni la solution : If the ajax function doesn't do session_write_close(), then your outer page will appear to hang, and opening other pages in new tabs will also stall.

Pour résoudre ce problème, il suffit donc de faire un appel à session_write_close() juste après l'appel à session_start(). Ainsi, la variable $_SESSION sera initialisée puis la session sera immédiatement fermée pour libérer les autres pages.

Notes

  • Il existe d'autres moyens de permettre au serveur d'envoyer des données à un client sans que celui-ci les demande. Les WebSockets (avec notamment socket.io) sont certainement l'avenir...
  • PHP n'est certainement pas le langage le plus adapté pour réaliser une communication du serveur vers le client de manière performante.


Last edited by afnarel on 13/09/12 at 23:25.

Lors d'une mise à jour d'Archlinux il y a quelques jours, je me suis retrouvé avec un kernel panic au démarrage. Sur le coup, je me suis dit "aucun problème, un petit coup de chroot à partir d'un liveCD devrait faire l'affaire". Le problème, c'est que quand j'ai essayé de chrooter, j'ai obtenu l'erreur suivante :

chroot: cannot execute /bin/bash: no such file or directory

ou, en français :

chroot: impossible dexécuter la commande /bin/bash: Aucun fichier ou dossier de ce type

Heureusement, ce post de tuxce (que je remercie au passage pour toutes les excellentes solutions qu'il donne, car ce n'est pas la première fois qu'un de ses posts m'aide à résoudre un problème) explique que /lib/ est maintenant un simple lien symbolique vers /usr/lib/.

Pour que le chroot fonctionne, il faut supprimer (ou renommer) le dossier lib de la partition sur laquelle se trouve la racine du système défectueux (jail) et créer le lien symbolique de usr/lib vers lib :

  • mv lib lib.old
  • ln -sf usr/lib/ lib

Note 1 : si par contre l'erreur que vous rencontrez est chroot: cannot run command '/bin/bash': Exec format error, alors c'est que vous essayez de chrooter dans un système 64bits depuis une distribution 32bits, comme expliqué sur le wiki d'ArchLinux...

Note 2 : apparemment, certaines personnes ont dû faire un mkinitcpio -p linux (voir ici) après cette mise à jour. Dans mon cas, d'après ce que j'ai vu dans les logs, pacman l'a fait tout seul.


Last edited by afnarel on 04/08/12 at 20:28.

Et la chanson du jour est... Sweet Child O' Mine par Guns N' Roses.

Si vous êtes guitariste, que vous aimez les solos bien rock et que vous n'avez pas encore appris le 2ème solo de cette chanson, je vous le conseille ;). Je parle de celui qui commence à 03min35.

Je ne parle que de Sweet Child O' Mine mais les chansons les plus célèbres des GnR sont toutes excellentes : Knocking on Heaven's Door (un classique, of course), November Rain, Don't cry... Et pour ceux qui aiment connaître l'histoire des groupes qu'ils écoutent, celle d'Axl Rose, Tracii Guns, Slash, Buckethead et DJ Ashba est plutôt intéressante :).

Sweet Child O Mine by Guns N' Roses on Grooveshark

Premier post qui parle de musique sur ce blog. Etrangement, pour parler d'un style de musique qui est loin de compter parmi ceux que je préfère en général.

J'avais pas mal écouté Eminem il y a plusieurs années et je l'avais catalogué dans les artistes qui font toujours le même genre de chansons. Cependant, j'ai réécouté ses albums ces derniers jours et j'ai été très positivement surpris.

Les paroles sont en fait très riches et racontent des histoires souvent émouvantes avec des rebondissements et des chutes inattendues. Du côté de la musique, ce n'est pas qu'une beatbox : on retrouve souvent des mélodies assez tristes sur les refrains. Ce qui m'a le plus marqué dans ses chansons est la prouesse vocale. Le rap est souvent rapide et saccadé et il excelle dans ce domaine. Eminem est capable d'enchaîner des phrases très longues avec un rythme soutenu et des accélérations/décélérations en plein milieu sans reprendre son souffle et en prononçant chaque syllabe distinctement.

La chanson Mockingbird est l'une de celles que je préfère (avec Stan, Echo, Superman et Lose Yourself).


I'ma give you the world
I'ma buy a diamond ring for you, I'ma sing for you
I'll do anything for you to see you smile

Lyrics

Mockingbird by Eminem on Grooveshark
Last edited by afnarel on 21/07/12 at 15:10.