Horaires de ma femme
Ma femme est aide-soignante dans un EMS. Ses heures de travail changent d'un jour à l'autre. Elle apprend au courant du mois ses horaires pour le mois suivant. Jusqu'ici elle arrivait à se procurer une photocopie des horaires. Malheureusement, un règlement absurde interdit de les photocopier et la direction a récemment décidé d'appliquer ce règlement. Nous ferions mieux d'appeler le syndicat, mais comme je suis informaticien, j'écris plutôt une application:
<source lang="javascript> Ext.setup({
onReady: function() {
Ext.regModel('Day', {fields: ['date','horaire']} ); var list = new Ext.List({ itemTpl : '{date}: {horaire}',
singleSelect: true, //pour raccourcir le code, je crée le store ici.
store: new Ext.data.JsonStore({ model : 'Day',
// voir la définition de loadData plus bas
data: loadData()}) });
// Les configurations qui vont être utilisées pour les boutons.
// Les attributs qui sont pareils pour tous les boutons (xtype, height, width, listeners) // vont être ajoutés par défaut par la fonction makeButtonPannel
var buttons1 = [{text: '1'},{text: '2'},{text: '3'},{text: '3s'},{text: '4'},{ text: '5'}];
var buttons2 = [{text: 'ce'},{text: 'f'},{text: 'r'},{text: 'v'},{text: 'fo'}, {
// Les listeners sont des fonctions qui sont appelées lorqu'un événement a lieu. // Ici on a un listener qui réagit à l'événement 'tap', qui est envoyé // quand on touche le bouton. Pour un exemple d'utilisation d'autres événements // voir sencha-touch-1.1.0/examples/kitchensink/src/demos/touch.js // Comme ce bouton a déjà un attribut listeners, il ne va pas recevoir // les listeners par défaut donnés par makeButtonPanel listeners: { 'tap': function () {selectPreviousNode(list);} }, // Ce dernier bouton contient une icone à la place du texte. // Ca ne fonctionne pas très bien avec l'agrandissement des boutons. // Pour faire cela correctement, voir // http://www.sencha.com/forum/showthread.php?150028-Scaling-button-icons
iconCls: 'arrow_up', iconMask: true // necessary if the icon is encoded as base64 }];
var size = screen.width/Math.max(buttons1.length,buttons2.length); var buttonPanel1 = makeButtonPannel(buttons1,size,list); var buttonPanel2 = makeButtonPannel(buttons2,size,list);
var titleToolbar = new Ext.Toolbar({dock: 'top',title: 'horaires' }); var bottomToolbar = new Ext.Toolbar({
dock: 'bottom', defaults: {iconMask: true},
layout: {pack: 'center'}, items: [{iconCls: 'delete', text: 'reset', listeners: {'tap': function () {resetData(list);}} },
{iconCls: 'action', text: 'save',
listeners: {'tap': function () {saveData(list);}}
},
{iconCls: 'compose',
text: 'post',
listeners: {'tap': function () {postData(list);}}
}]
});
new Ext.Panel({ fullscreen: true,
// Le layout 'fit' remplit tout avec le seul item qu'il contient // Sans cette ligne la liste dépasserait sous les boutons // voir la doc http://docs.sencha.com/touch/1-1/#!/api/Ext.layout.FitLayout
layout: 'fit', items: [list], dockedItems: [titleToolbar,bottomToolbar,buttonPanel2,buttonPanel1] });
list.select(0);
}});
// Crée un panel de boutons à partir de: // buttons: un array de configurations de boutons // size: le coté d'un bouton (ils sont carrés) // list: la liste sur laquelle agissent les boutons function makeButtonPannel(buttons, size, list){
return new Ext.Panel({ // quand on utilise ce panel comme dockedItem d'un autre panel // il apparait en bas dock: 'bottom', layout: { // layout qui pose ses items côte à côte horizontalement // voir http://docs.sencha.com/touch/1-1/#!/api/Ext.layout.HBoxLayout type : 'hbox', pack : 'center' }, // hauteur du panel: size passé en argument de la fonction height: size, // attributs qui vont s'ajouter aux attributs des items. defaults: { // Les items auront le type Button // pour la correspondance entre xtypes et classes // voir http://docs.sencha.com/touch/1-1/#!/api/Ext.Component xtype: 'button', height: size, width: size, listeners: { 'tap': function(button, event){ setHoraire(button.text,list);} } }, // la liste de boutons passés en argument items: buttons });
}
// Positionne l'horaire à la date sélectionnée dans la liste // et sélectionne l'élément suivant. function setHoraire(horaire,list){
// Il doit y en avoir un unique élément sélectionné var selectedRecord = list.getSelectedRecords()[0]; var index = list.indexOf(selectedRecord); // C'est un modèle. Pour le modifier il faut utiliser la fonction set // http://docs.sencha.com/touch/1-1/#!/api/Ext.data.Model selectedRecord.set('horaire',horaire); // Il faut rafraichir le node pour que le changement soit visible list.refreshNode(index); // sélectionner la ligne suivante if(index < list.getStore().getCount() - 1){ list.select(index + 1); }
}
// Recule la sélection d'une position dans la list function selectPreviousNode(list){
var selectedNode = list.getSelectedNodes()[0]; //il doit y en avoir un var selectedIndex = list.indexOf(selectedNode); var newIndex = selectedIndex === 0 ? 0 : selectedIndex - 1; list.select(newIndex);
}
// Enregistre les horaires sous la forme d'un string, // par exemple "3s|4|r|..."
function saveData(list){
// Array de tous les modèles Day var records = list.getStore().data.getRange(); // string "3s|4|r|..."
var horaires = DAY.recordsToHoraires(records);
// localStorage est un objet fourni par javascript (html 5).
// Les données qu'on y met sont enregistrées dans le browser // et sont disponibles à chaque fois qu'on va sur la page.
localStorage["horaires"] = horaires;
}
// Lit les données enregistrées et les retourne sous forme d'un array // de configuraions du modèle Day // par exemple [{date: "Thu 1.12.2011", horaire: "4"}, {...}, ... ] function loadData(){
// Ce programme n'est pas encore capable de gérer plusieurs mois. // On décide arbitrairement qu'on est en décembre 2011 var year = 2011; // les mois sont numérotés de 0 à 11, 11 est décembre var month = 11; // les données enregistrées. // localStorage est un objet fourni par javascript (html 5) // Les données qu'on y met sont enregistrées dans le browser // et sont disponibles à chaque fois qu'on va sur la page. var horaires = localStorage["horaires"]; if(horaires){ return DAY.horairesToRecordConfigs(year,month,horaires) }else{ return DAY.makeEmptyRecordConfigs(year,month); }
}
// Remet tous les horaires de la liste à "-" function resetData(list){
var records = list.getStore().data; for(var i=0; i< records.length; i++){ // records est une MixedCollection, sur laquelle il faut appeler la méthode get. // record est un modèle. Pour le modifier il faut utiliser la méthode set // http://docs.sencha.com/touch/1-1/#!/api/Ext.data.Model records.get(i).set("horaire","-"); }
}
// Envoie les horaires sur un serveur. function postData(list){
// Array de tous les modèles Day var records = list.getStore().data.getRange(); // string "3s|4|r|..." var horaires = DAY.recordsToHoraires(records);
// On envoie les données sur le serveur par ajax. // On peut faire des requetes ajax directement en javascript // mais c'est plus confortable de passer par une librairie. // Ici nous utilisons celle qui est fournie par sencha.
Ext.Ajax.request({
// l'adresse du serveur // J'y ai mis un programme php qui enregistre les données pour un nom.
url: "http://infolipo.net/cours/data/data.php",
// C'est gentil de remplacer "andreas" par votre nom pour ne pas écraser mes données.
// On peut voir ce qui est enregistré à la page // http://infolipo.net/cours/data/data.php?name=andreas
params: {name:"andreas", data:horaires},
// Cette fonction est appelée au retour de la requête, si tout a bien marché
success: function(response, opts) { alert("Réponse du serveur: " + response.responseText); },
// Cette fonction est appelée en cas d'échec.
failure: function(response, opts) { alert('échec: ' + response.status); } });
}
// Les fonctions pour manipuler le modèle Day ont assez en commun // pour que je les regroupe dans un même objet, je trouve. // C'est une question de gout. var DAY = {
// Crée les configuration de modèles pour un mois // year: l'année // month: le mois // horaires: un string représentant les horaires "3s|4|r|..." horairesToRecordConfigs: function(year, month, horaires){ // les horaires sont enregistrés sous la forme "3s|4|r|..." // sépare les horaires pour avoir un array ["3s","4","r",...] var horairesEnArray = horaires.split("|"); var day = 0; return horairesEnArray.map(function(h){ var date = new Date(year,month,day); var config = DAY.createRecordConfig(h,date); day ++; return config; } ); },
// Crée un string qui représente les horaires "3s|4|r|..." // à partir d'un array de modèles Day recordsToHoraires: function(records){ // On tire des records un array qui contient les horaires dans l'ordre // par exemple ["3s","4","r",...] var horairesEnArray = records.map(function(record){ // C'est un modèle. Il faut utiliser la méthode get // http://docs.sencha.com/touch/1-1/#!/api/Ext.data.Model return record.get("horaire"); }); // retourner un string "3s|4|r|..." return horairesEnArray.join("|"); },
// Retourne un array de configurations pour le modèle Day, // toutes avec l'horaire "-", // pour tous les jours du mois indiqué makeEmptyRecordConfigs: function(year, month){ // prendre l'array de dates fourni par datesOfMonth() // et le transformer en array de configurations pour le modèle Day. var datesData = DAY.datesOfMonth(year,month).map( function(d){ return DAY.createRecordConfig("-",d);} ); return datesData; },
// Crée la configuration pour un modèle Day createRecordConfig: function(l_horaire, la_date){ // la méthode format est ajoutée à Date par sencha // voir http://docs.sencha.com/touch/1-1/#!/api/Date return { date: la_date.format("D d.m.Y"), horaire: l_horaire }; },
// Retourne un array contenant les objets Date du mois indiqué datesOfMonth: function(year, month){
var date = new Date(year, month, 1); var month = date.getMonth() var dates = []; date.setDate(1); while (month === date.getMonth()) { dates.push(date); // la méthode add est ajoutée par sencha // à la classe Date fournie par javascript date = date.add(Date.DAY, 1); } return dates;
}
}
</source>
Pour finir nous examinons une application dans un état plus avancé, mais qui est loin d'être finie. L'exercice constitue à décortiquer l'application, et à la compléter:
- Mettre tout en haut la barre des taches qui est tout en bas.
- Ajouter une rangée de boutons
- Désactiver la fonctionnalité de chaque bouton et la remplacer par un alert.
- Voir comment fonctionne l'enregistrement de données.
- Voir comment l'application envoie des données ailleurs par ajax.
- L'application ne fonctionne que pour un mois de l'année. Comment peut-on laisser l'utilisateur choisir le mois
- Choisir un composant graphique
- Ajouter un bouton qui appelle ce composant graphique
- Gérer l'affichage en fonction du mois
- Gérer la nouvelle donnée du mois pour l'enregistrement et l'envoi par ajax.
- L'application n'est pas très belle, comment pourrait on l'améliorer?
- Créer une meilleure template pour afficher les rangées.
- Peut-on remplacer les boutons par du css/html fait à la main? (Je n'en suis pas sur. J'aimerais bien avoir une espèce de bouton qui reste appuyé en fonction de la rangée sélectionnée.)