Le sélecteur de dates en javascript; proche du DateTimePicker comdlg

calendrier-2011-gratuit.jpg

Edit, 13/11/2011 La version 0.3 du sélecteur de date est disponible sur ce billet. Je garde celui-ci publié juste parcequ'un blogueur n'aime pas mettre offline son travail, mais je vous invite à préférer la version 0.3 du sélecteur de dates.

Mise à jour du billet 2/11/2011: possibilité de déplier ou non le datePicker.

A la 15ème fois que j'ai du insérer un sélecteur de date, bah ça m'a gonflé de réécrire les boucles etc... pour donner ça:

Je me suis donc imposé d'écrire une function pour ne plus jamais avoir à le refaire, et tant qu'à coder, autant faire plus sexy. le voici en full javascript:

En mode plié (il suffit de passer la souris dessus pour pouvoir le dérouler).

En mode déplié en permanence

Il peut s'intégrer dans un formulaire web en méthode POST ou GET. Il contient un <input type="hidden" ... qui a pour valeur la date sélectionnée au format mySql: AAAA-MM-JJ.

Intégration / Code source:

Créez ou ajoutez ceci à votre CSS:

  1. .dateTimePicker {width: 170px; font-size: 10px;}
  2. .dateTimePicker table tr td{
  3. text-align: center;
  4. border: none;
  5. }
  6. .dateTimePicker table tr td.dayUnSelected{
  7. background-color: #A3AED3;
  8. cursor: pointer;
  9. }
  10. .dateTimePicker table tr td.daySelected{
  11. color : #666;
  12. background-color: #DDDDDD;
  13. cursor: pointer;
  14. }
  15. .dateTimePicker table tr th{
  16. color : #fff;
  17. background-color: #59627A;
  18. cursor: pointer;
  19. }

Créez ou ajoutez ceci au script javascript de votre page:

  1. /*
  2.  * Codé par Gnieark http://blog-du-grouik.tinad.fr/ Novembre 2011 version 0.2
  3.  * Ditribué sans aucune garantie.
  4.  * Vous pouvez faire ce que vous voullez de ce code, l'utiliser, le revendre,
  5.  * le modifier etc... à condition de laisser intact le présent avertissement.
  6.  */
  7. function createElem(type,attributes)
  8. {
  9. var elem=document.createElement(type);
  10. for (var i in attributes)
  11. {elem.setAttribute(i,attributes[i]);}
  12. return elem;
  13. }
  14. function dtPickerDeroule(tableId)
  15. {
  16. racine=document.getElementById(tableId);
  17. //on parcours la table pour passer style="" à tous les tr
  18. var tr=racine.firstChild;
  19. while(tr=tr.nextSibling)
  20. { tr.setAttribute("style","");}
  21.  
  22. }
  23. function dtPickerMasque(tableId)
  24. {
  25. racine=document.getElementById(tableId);
  26. //on parcours la table pour passer style="display:none" à tous les tr
  27. var tr=racine.firstChild;
  28. while(tr=tr.nextSibling)
  29. { tr.setAttribute("style","display:none");}
  30. }
  31.  
  32. function refreshTableJours(container,name,selectedDate,reduire)
  33. {
  34. //purge
  35. container.innerHTML="";
  36.  
  37. var leMois=selectedDate.getMonth();
  38. var lAnnee=selectedDate.getFullYear();
  39. var leJour=selectedDate.getDate();
  40. //locales sucks
  41. var lesJours= new Array('dim','lun','mar','mer','jeu','ven','sam');
  42. var lesJoursFull= new Array('dimanche','lundi','mardi','mercredi','jeudi','vendredi','samedi');
  43. var lesMois= new Array( 'janvier', 'fevrier','mars','avril','mai','juin','juillet','aout','septembre','octobre','novembre','decembre');
  44.  
  45. /*Première ligne de titre: 3 cases: * un lien mois précédent * la date sélectionnée * un lien mois suivant */
  46. var trTitre=createElem("tr",{});
  47. container.appendChild(trTitre);
  48. var thPreviousMonth=createElem("th",{"onClick":"refreshTableJours(document.getElementById('" + container.getAttribute("id") + "'),'"+ name + "', new Date(" + lAnnee + "," + (leMois -1) + "," + leJour + "));"});
  49. thPreviousMonth.innerHTML="&lt;";
  50. trTitre.appendChild(thPreviousMonth);
  51. var thAfficheDate=createElem("th",{"colspan":5});
  52. thAfficheDate.innerHTML=lesJoursFull[selectedDate.getDay()] + " " + leJour + " " + lesMois[leMois] + " " + lAnnee;
  53. //Au passage on créé un input type=hidden qui prend la date au format mySql. Cela permettra d'intégrer
  54. //le datetimePicker à des formulaires type POST ou GET
  55. var inputHidden=createElem("input",{"type":"hidden", "name":name,"value": lAnnee + "-" + (leMois + 1) + "-" + leJour});
  56. thAfficheDate.appendChild(inputHidden);
  57. trTitre.appendChild(thAfficheDate);
  58. var thNextMonth=createElem("th",{"onClick":"refreshTableJours(document.getElementById('" + container.getAttribute("id") + "'),'"+ name + "', new Date(" + lAnnee + "," + (leMois + 1) + "," + leJour + "));"});
  59. thNextMonth.innerHTML="&gt;";
  60. trTitre.appendChild(thNextMonth);
  61.  
  62. var unDuMois=new Date(lAnnee,leMois,1);
  63. //la date du dernier dimanche (valeur 0 pour les jours de la semaine) du mois précédent sert de départ au calendrier:
  64. var startDate=new Date(unDuMois.getFullYear(), unDuMois.getMonth(), unDuMois.getDate() - unDuMois.getDay());
  65.  
  66. //si le dtPicker est paramétrer pour etre réduit par défaut, les lignes suivantes seront display:false;
  67. if(reduire){
  68. var style ="display:none;"
  69. }else{
  70. var style="";
  71. }
  72. //Ligne de titre 2: le nom des jours
  73. var trDays=createElem("tr",{"style":style});
  74. container.appendChild(trDays);
  75. for (var i=0; i<7;i++)
  76. {
  77. var tdLeJour=createElem("td",{});
  78. tdLeJour.innerHTML=lesJours[i];
  79. trDays.appendChild(tdLeJour);
  80. }
  81. var increment=0;
  82. //6 lignes
  83. for(var week=0; week<6; week++)
  84. {
  85. var trDates=createElem("tr",{"style":style});
  86. container.appendChild(trDates);
  87.  
  88. for (var dayNumber=0; dayNumber<7;dayNumber ++)
  89. {
  90. var currentDate=new Date(startDate.getFullYear(),startDate.getMonth(), startDate.getDate() + increment);
  91. if ((currentDate.getDate() == leJour)&&(currentDate.getMonth() == leMois)){
  92. var tdclass="daySelected";
  93. }else{
  94. var tdclass="dayUnSelected";
  95. }
  96. var tdJour=createElem("td",
  97. {"onClick":"refreshTableJours(document.getElementById('" + container.getAttribute("id") + "'),'"+ name + "', new Date(" + currentDate.getFullYear() + "," + currentDate.getMonth() + "," + currentDate.getDate() + "));",
  98. "class": tdclass ,"title":lesJours[currentDate.getDay()] + " " + currentDate.getDate() + "-" + (currentDate.getMonth() + 1) + "-" + currentDate.getFullYear()
  99.  
  100. });
  101. tdJour.innerHTML=currentDate.getDate();
  102. trDates.appendChild(tdJour);
  103. increment++;
  104. }
  105. }
  106.  
  107. }
  108. function dateTimePicker(name,defaultDate, reduire)
  109. {
  110. //se créer un container
  111. document.write('<div id="datetimePicker' + name + '" class="dateTimePicker"></div>');
  112. var racine=document.getElementById('datetimePicker' + name);
  113. if(reduire){
  114. var tableJours=createElem("table",{
  115. "id": "datetimePickerTable" + name ,
  116. "onMouseOver": "dtPickerDeroule('datetimePickerTable" + name +"');",
  117. "onMouseOut": "dtPickerMasque('datetimePickerTable" + name + "');"
  118. });
  119. }else{
  120. var tableJours=createElem("table",{"id": "datetimePickerTable" + name });
  121. }
  122.  
  123. racine.appendChild(tableJours);
  124. refreshTableJours(document.getElementById("datetimePickerTable" + name),name,defaultDate,reduire);
  125. }

Insérer le datetime picker dans votre page

Là où vous le voulez, appellez le comme ceci:

<script type="text/javascript">
<!--
 dateTimePicker("MADATE",new Date(),true);
-->
</script>
  • "MADATE" sera le nom ("name") de l'input qui contient la date. Modifiez le selon votre stratégie de nommage des variables.[1] Si vous mettez plusieurs datepicker sur la même page, ce parametre doit être unique.
  • /!\ Il y a un piège sur les mois. Ils sont comptés de 0 à 11 en javascript. Genre si vous l'appellez en PHP, il faut soustraire 1 au numéro du mois. Exemple pour que par défaut ce soit la date du jour:
  • Le parametre tru ou false indique s'il doit être déplié en permanence (false) ou se dérouler lorsque la souris est passée dessus.
  1. echo '<div><script type="text/javascript">
  2. <!--
  3. dateTimePicker("plop",new Date('.date("Y").', '.(date("n") - 1).', '.date("j").'),true);
  4. -->
  5. </script></div>';

Enfin ceci dit le plus simple pour la date du jour c'est d'utiliser la function javascript Date() sans paramètres; comme ceci:

[html]
<script type="text/javascript">
<!--
  dateTimePicker("plop",new Date(),true);
-->
</script>

Conclusion

Je suis sûr qu'on en trouve plein de codes dans ce genre. J'adore réinventer l'eau chaude. Mais ceci dit, je ne vais pas galérer à trouver comment l'intégrer #CestMoiQuiLaFait .

Paradoxalement la date sous forme de liste déroulantes (exemple au début de ce billet), est plus pratique à utiliser sur les petits écrans tactiles (smartphones). Le "datetimepicker" interdit d'avoir de gros doigts.

Non, ce billet n'est pas périmé avant même sa publication. la balise HTML5 <input type="datetime-local"/> a du mal à pointer son nez sur les navigateurs.

Notes

[1] A ce sujet, je vous présente mes excuses pour les variables dans le script, en anglais, en français et parfois en franglais. C'est plus fort que moi. Mais j'ai respecté le camelCase.

Commentaires

1. Le mardi, novembre 1 2011, 18:51 par Franck

Joli sauf que je ne peux pas naviguer au clavier dessus, c'est normal ?

2. Le mardi, novembre 1 2011, 18:55 par gnieark

Merci pour ce retour Franck!

heu. (tu voudrais utiliser la touche tabulation?) Ce n'est pas volontaire, je me disais bien me disais bien qu'il y avait une limite à l'utilisation d'une <table> et d'avoir mis les "onClick" dans les balises td

3. Le dimanche, novembre 6 2011, 13:12 par Zigazou

Yo !

Le casse-couille est de retour ! ;-)

Non c'est un plaisir de te voir sévir sur mon blog ;) Salut Zigazou!

En testant et parcourant rapidement le code et donc à chaud :

→ le calendrier replié ne peut pas être utilisé depuis un tactile (tablette ou téléphone) puisqu'il utilise l'événement de survol,

Oui et ça c'est un gros fail de ma part, car ce calendrier a pour destination de remplacer tous les sélecteurs de dates de l'extranet du travail et qu'à coté de ça j'étudie la possibilité de mettre en place des écrans tactiles dans les services de soins.

→ le calendrier change de taille en fonction du jour du calendrier, ce n'est pas heureux ni pratique quand on clique plusieurs fois de suite sur jour suivant ou précédent,

Je corrigerai ça dans le css pour la version 0.3 ;)

→ il n'y a pas possibilité de changer d'année autrement qu'en cliquant 12 fois sur suivant ou précédent,

Pas gênant dans l'immédiat vu mon besoin (Hormis le dossier de soins, pour lequel j'ai déja demandé à ma direction de recruter 6 développeurs pour le refaire entièrement en 6 mois; la conception étant déja faite). J'ai mis en place la dématérialisation de la plupart des échanges. C'est à dire que pour les bons de travaux et la GED on ne peut pas remonter avant 2006 car c'était du papier. 2007 pour les évènements indésirables et 2011 pour le système de restauration. Cependant oui, vu que le prochain projet est une GPEC, il faudra pouvoir naviguer sur plus d'années. Pour la version 0.4 de ce calendrier ;)

→ pour dtPickerMasque : connais-tu les balises theader et tbody ? Je pense qu’elles t’éviteraient de devoir parcourir chaque ligne afin de les cacher,

C'est vrai. Pour la version 0.3 du calendrier

→ il est plus efficace/performant de travailler avec des classes CSS que d’affecter directement des styles à une balise en JavaScript,

Ouep

→ lesJours, lesJoursFull et lesJoursMois peuvent être placés en dehors de la fonction en tant que variables locales, c’est moins coûteux que de les créer et supprimer à chaque appel de fonction,

pour la version 0.3 ;)

→ dans refreshTableJours, pourquoi supprimer et recréer tout le tableau ? Ne peux-tu pas simplement modifier le contenu des cases ? (pour les éventuels lignes surnuméraires, tu pourrais tout bonnement les cacher),

C'était de la fénantise de ma part. Plus simple à coder comme ça.

→ toujours dans refreshTableJours, il est plus efficace de créer un conteneur, d’y ajouter tous les éléments désirés et, en dernier, de le raccrocher à un élément parent visible du document,

Je déplacerai le prototype appendChild à la fin dans la version 0.3

→ il peut être déroutant pour l’utilisateur que la case qu'il vient de cocher remonte tout d’un coup, c’est le cas quand tu cliques en bas du calendrier sur un jour du mois suivant, il change automatiquement de mois,

Heu....

→ une bonne intégration du calendrier devrait pouvoir le faire fonctionner sans JavaScript. L’idée étant d’afficher en standard le système à 3 listes déroulantes classiques et que celui-ci soit masqué par ton script. Charge à ton calendrier ensuite de mettre à jour en fond ces listes déroulantes. Gros avantage : quel que soit la solution technique retenue sur le poste client, la gestion du formulaire sur le serveur reste la même.

Oui, mais non, c'est chiant ça. (d'autant que les trois listes déroulantes me font récupérer le mois, le jour et l'année séparément contrairement au calendrier) à la rigueur je laisserai un input texte pour la date s'ils n'ont pas de JS activé. Tant pis pour ceux qui me mettront des 31 fevrier. Faut que je trouve la statistique de personnes qui ont javascript désactivé. Dans mon Lan, c'est 0%; et 1% d'IE6 (ouep il doit bien rester un windows 2000 quelque part, genre à la médecine du travail)

Pfiou ! Bon, j’espère que je t’aurais bien pris la tête ;-)

4. Le dimanche, novembre 6 2011, 21:29 par Zigazou

Hello !

Je viens encore de m’apercevoir d’un truc ;-p

Pour moi, ce n’est pas au script de créer une entrée de formulaire de type hidden ! C’est le serveur qui génère le formulaire dans son entièreté et c’est au client de le remplir et de le renvoyer conformément à ce qu’a défini le serveur. Que le client utilise un DateTimePicker ou un champ classique le regarde, mais cela ne doit pas interférer avec le reste du système.

Ce qui nous ramène à ma dernière remarque que tu rejettes alors qu’elle apportait déjà cette séparation présentation/code. Le principe étant de considérer que le formulaire est amélioré par le JavaScript et non que le formulaire est généré par le JavaScript.

5. Le lundi, novembre 7 2011, 09:48 par gnieark

Ouep;

C'est ce que je pensais faire en créant un <input type="text" maxlength="9" value="2011-09-01"/> qui lui sera masqué et remplacé par le script (affichage seulement.

Je reprends ce code en fin de matinée. j'en ai besoin pour le taf ;)

Page top