Horaires de ma femme

De Wikilipo
Révision datée du 2 décembre 2011 à 12:20 par Andreaskundig (discussion | contributions) (Page créée avec « 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 ell... »)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à la navigation Aller à la recherche

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:

  1. Mettre tout en haut la barre des taches qui est tout en bas.
  2. Ajouter une rangée de boutons
  3. Désactiver la fonctionnalité de chaque bouton et la remplacer par un alert.
  4. Voir comment fonctionne l'enregistrement de données.
  5. Voir comment l'application envoie des données ailleurs par ajax.
  6. L'application ne fonctionne que pour un mois de l'année. Comment peut-on laisser l'utilisateur choisir le mois
    1. Choisir un composant graphique
    2. Ajouter un bouton qui appelle ce composant graphique
    3. Gérer l'affichage en fonction du mois
    4. Gérer la nouvelle donnée du mois pour l'enregistrement et l'envoi par ajax.
  7. L'application n'est pas très belle, comment pourrait on l'améliorer?
    1. Créer une meilleure template pour afficher les rangées.
    2. 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.)