martedì 13 dicembre 2011

FullCalendar - un calendario jQuery con agenda e drag & drop

Per un piccolo gestionale medico ultimamente mi è capitato sotto mano FullCalendar, un ottimo calendario jQuery "full-size" altamente configurabile. Già dalla home page di questo progetto emergono i punti forti del plugin: eventi di tutto il giorno o schedulati, drag & drop degli eventi, viste per mese, per settimana e vista giornaliera. Il progetto è molto ben documentato, quindi per un utilizzo base rimando ogni spiegazione al sito.
Voglio pubblicare però la configurazione che ho utilizzato, cercando di mostrarne le potenzialità:
$('#calendar_strutture').fullCalendar({
   header: {
    left: 'prev,next today',
    center: 'title',
    right: 'month' //,agendaWeek,agendaDay
   },
   firstHour: 6,
   firstDay: 1,
   allDaySlot : false,
   timeFormat: 'HH:mm',
   monthNames:['Gennaio','Febbraio','Marzo','Aprile','Maggio','Giugno','Luglio','Agosto','Settembre','Ottobre','Novembre','Dicembre'],
   monthNamesShort: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'],
   dayNames: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'],
   dayNamesShort: ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'],
   buttonText: {
    prev:     ' &#9668 ',  // left triangle
    next:     ' &#9658 ',  // right triangle
    prevYear: ' &lt;&lt; ', // <<
    nextYear: ' &lt;&lt; ', // >>
    today:    'Oggi',
    month:    'Mese',
    week:     'Settimana',
    day:      'Giorno'
   },
   columnFormat: {
    month: 'ddd',    // Mon
    week: 'd/M ddd', // Mon 9/7
    day: 'dddd d/M'  // Monday 9/7
   },
   editable: true,
   
   events: {
    url: 'lista_calendario_strutture.php',
    cache: true,
    //color: 'purple',   // a non-ajax option
          textColor: 'white' // a non-ajax option
   },
   lazyFetching: false,
     
   eventClick: function(calEvent) {
    if(confirm("Elimino dal calendario la disponibilita' della struttura per il giorno selezionato?")) {
     $.ajax({
       url: "interventi.php?section=calendario_strutture&action=delete",
       type: 'POST',
       data: {
      struttura: calEvent.title,
      data: calEvent.start.getTime()
       },
       success: function(msg){
      //alert(msg); //debug
      $('#calendar_strutture').fullCalendar('removeEvents',calEvent.id); 
       },
       error: function() {
      revertFunc();
       }
     });
    }
   },
   
   eventMouseover: function(calEvent) {
    $(this).find('.fc-event-title').html('<img src="template/default/admin/assets/images/delete.png" /> '+calEvent.title);
   },
   
   eventMouseout: function(calEvent) {
    $(this).find('.fc-event-title').html(calEvent.title);  
   },
   
   eventDrop: function(calEvent,dayDelta,minuteDelta,allDay,revertFunc) {
    //Qui ajax per spostamento evento
    $.ajax({
      url: "interventi.php?section=calendario_strutture&action=move",
      type: 'POST',
      data: {
     struttura: calEvent.title,
     movimento: dayDelta,
     nuova_data: calEvent.start.getTime()
               },
      success: function(msg){
     //alert(msg);
      },
      error: function() {
     revertFunc();
      }
    });
   },
   droppable: true,
   drop: function(date, allDay) { // this function is called when something is dropped
   
    // retrieve the dropped element's stored Event Object
    var originalEventObject = $(this).data('eventObject');
    
    // we need to copy it, so that multiple events don't have a reference to the same object
    var copiedEventObject = $.extend({}, originalEventObject);
    
    var today = new Date();
    
    // assign it the date that was reported
    copiedEventObject.id = today.getTime();
    copiedEventObject.start = date;
    copiedEventObject.allDay = allDay;
    
    //alert(copiedEventObject.title + ' ' + copiedEventObject.start.getTime());
    
    //Qui ajax per inserimento disponibilita in calendario struttura
    $.ajax({
      url: "interventi.php?section=calendario_strutture&action=insert",
      type: 'POST',
      data: {
     struttura: copiedEventObject.title,
     data: copiedEventObject.start.getTime()
               },
      success: function(msg){
     //alert(msg); //debug
     // render the event on the calendar
     $('#calendar_strutture').fullCalendar('renderEvent', copiedEventObject, false);
      }
    });
    
   }
   
  });

$('#external-events div.external-event').each(function() {

 // create an Event Object (http://arshaw.com/fullcalendar/docs/event_data/Event_Object/)
 // it doesn't need to have a start or end
 var eventObject = {
  title: $.trim($(this).text()), // use the element's text as the event title
  backgroundColor: $(this).css('background-color')
 };
 
 // store the Event Object in the DOM element so we can get to it later
 $(this).data('eventObject', eventObject);
 
 // make the event draggable using jQuery UI
 $(this).draggable({
  zIndex: 999,
  revert: true,      // will cause the event to go back to its
  revertDuration: 0  //  original position after the drag
 });
 
});

Questa prima parte, che deve essere contenuta nella parte relativa a $(document).ready(...), specifica il comportamento del calendario, in particolare:
  1. deve poter gestire, tramite chiamate ajax, lo spostamento degli eventi (eventDrop), che in questo caso particolare, sono "disponibilità" delle sale operatorie in determinate strutture ospedaliere
  2. deve gestire l'eliminazione delle disponibilità, mediante click del mouse (eventClick) sull'evento stesso (notare che quando il mouse si trova sopra l'evento viene mostrata una icona "cestino")
  3. deve gestire la creazione delle disponibilità mediante il trascinamento dall'elenco strutture sulla sinistra al calendario (drop), date una occhiata allo screenshot per capire di cosa sto parlando
  4. La parte finale del codice jQuery è relativa al punto 3, fa capire al motore javascript che i
    sulla sinistra deve gestirli come "draggable" e creare un oggetto evento (Event Object) in modo da poterlo poi gestire in FullCalendar.
Di seguito il codice html utilizzato (elenco strutture + calendario):
<div id='wrap' style="width:100%;margin: 0 auto;"> 
                 <div id='external-events' style="float: left;width: 150px;padding: 0 10px;border: 1px solid #ccc;background: #eee;text-align: left;margin-right:20px;">
                  <h4>Elenco Strutture</h4>
                                    <div class='external-event' style="background-color:#0099FF">Struttura Ospedaliera 1</div>
                                    <div class='external-event' style="background-color:#996699">Struttura Ospedaliera 2</div>

                                    </div>
                 <div id='calendar_strutture' style="width: 900px;float:left;"></div>
                 <div style='clear:both'></div>
              </div>
            </div>
Il risultato finale!