Saját prokejtekhez kiválóan alkalmas naptár-modul a fullcalendar. Vannak viszont olyan esetek, amikor kissé eltérő viselkedést szeretnénk alkalmazni.
Ilyen lehet például egy csoport-naptár, amelyen első ránézésre szeretnénk látni, ki mikor mennyire elfoglalt:
Fullcalendar kicsit másképp, az új verzió

Ezzel szemben az eredeti verzióban kicsit nehezebb az eligazodás, ha ugyanezt az információt szeretnénk kinyerni. Az időpontok ugyan el vannak tolódva picit, hogy akkor is lehessen látni mindent, ha egy időben több időpont van, de azt már sokkal nehezebb kibogarászni, hogy melyik időpont kihez tartozik, és kinek mikor van szabad ideje:
Fullcalendar kicsit másképp, az eredeti verzió
Az itt bemutatott kiegészítés a fullcalendar-1.5.2 verzióval lett tesztelve, nagy valószínűség szerint az újabb verziókkal is működik, a régebbiekkel nem biztos.

Elméleti rész

A fullcalendar alapbeállításait és telepítését nem részletezem, erre sok más helyen rengeteg leírás van. Itt a kiegészítő leírására szorítkozok, amennyiben kérdés merül fel, azokat szívesen megválaszolom a tartalom alatt.
Amit használunk a fullcalendar szolgáltatásai közül:

  • eventAfterRender callback
  • event, element és view belső objektumok
  • az event tulajdonságát, hogy bármilyen felhasználói információt képes hordozni
  • az események színezésének lehetőségét

Mivel a fullcalendar kódját nem kellene módosítani, hogy a későbbi frissítéseket is tudjuk használni, ezért a már meglévő hozzáépítési lehetőségeket használjuk ki.
Az eventAfterRender callback függvény minden esemény kirajzolása befejeztével meghívódik, paraméterei közül kettő az éppen aktuális eseményre mutat, a harmadik pedig maga a naptár.

Maga az esemény tudja, hogy hányadik al-oszlopban kell megjelennie, valamint hordozza a megjeleítéséhez szükséges szín-információkat is. A naptár beépített függvényeiben kiszámítjuk az események új helyét, és átpozícionáljuk őket, egy új szélességi mérettel együtt.

Gyakorlat

Lépések:
Az események adatai közé betesszük azt az információt, hogy egy napon belül hányadik al-oszlopban szeretnénk az adott eseményt megjeleníteni:

events: [
  {
    title: 'Repeating Event',
    start: new Date(2011, 12, 13, 13, 40),
    order: 1,
    allDay: false
  }
]

Ezen kívül felvehetünk színezért is, ezt maga a fullcalendar kezeli, ez egy a beépített lehetőségek közül. (Azért nem az al-oszlop számához kötöm a színt, mert így további többletinformációt lehet felvenni, akár árnyalatok segítségével.)

events: [
  {
    title: 'Repeating Event',
    start: new Date(2011, 12, 13, 13, 40),
    order: 1,
    backgroundColor: '#7a1',
    borderColor: '#7a1',
    allDay: false
  }
]

A példában szereplő naptár csak a napok töredékét mutatja, 8-tól este 8-ig, nincs benne teljes napra vonatkozó bejegyzés, valamint alapból a heti nézetet jeleníti meg:

$('#calendar').fullCalendar({
  [...]
  defaultView: 'agendaWeek',
  minTime: '8:00',
  maxTime: '20:00',
  allDaySlot: false,
  [...]
});

Az események helyzetét kicsit bonyolultabb meghatározni, mivel egy-két hasznos információ nem elérhető a callback függvényből. Ennek ellenére egész jó közelítő információt lehet kiszedni az elérhető adatokból. Az első pont: csak akkor szeretnénk átméretezgetni az eseményeket, ha az agendaWeek naptárban vagyunk. Emellett szükségünk lesz olyan dolgokra is, mint:

  • egy nap szélessége pixelben,
  • az időpontok oszlopának szélessége,
  • az aktuális esemény bal szélének helyzete (ebből számítjuk ki, hogy melyik oszlopban van),
  • az események közti szünet mértéke (könyebb olvashatóság)

A kód, majd utána egy rövid magyarázat:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$('#calendar').fullCalendar({
  [...]
  eventAfterRender: function (event, element, view) {
    if (view.name == 'agendaWeek') {                            // only run on agendaWeek view
      var dayNumber = 0,                                        // 0 = first day of displayed week
          elementsLeftPosition = element.position().left,       // copy left position for calcs
          dayColumnWidth = view.getColWidth(),                  // how wide is one day's column
          separationBetweenEvents = 4,                          // this amount of pixel will be between each event after calculation
          order = event.order || 1,                             // give a default 1 on order for calculations
          timeColWidth = $('.fc-agenda-axis').outerWidth() - 2, // first column's width, this is the hour-column
          typeCounts = 3;                                       // how many different subcolumns should be there
      // calculate which day-column the event is in
      while (elementsLeftPosition > (timeColWidth + dayColumnWidth)) {
        elementsLeftPosition -= dayColumnWidth;
        dayNumber += 1;
      }
      // set new width for event
      element.width(dayColumnWidth / typeCounts - separationBetweenEvents);
      // set new left position for event
      element.css({
        left: timeColWidth +                                    // has to be measured from left edge of calendar
            separationBetweenEvents +                           // gives the first separation from time column
            ((dayColumnWidth / typeCounts) * (order - 1)) +     // order shows the sub-column count
            (dayNumber * dayColumnWidth)                        // moves to the right day-column
      });
    }
  },
  [...]
});

A 4. sorban leellenőrizzük melyik nézetben vagyunk, ugyanis a számítások csak az agendaWeek nézetre érvényesek, a többiben elcsúszást okozhatnak.
A dayNumber és elementsLeftPosition változók segítségével számoljuk ki, hogy hányadik nap oszlopában van az esemény, ide kell ugyanis visszatenni egy kis igazítás után.
separationBetweenEvents-nek megfelelő pixel távolság kerül két esemény közé, ha egymás mellé kerülnek, ez megkönnyíti az olvasást, így ugyanis kevésbé folynak össze az események, ha egyforma színük van.
Az order kap egy alapértelmezett 1 értéket, ha az eseményben nincs megadva, ez csak a számoláshoz kell, hogy ne toljunk ki egy eseményt se előző napra (ellenkező esetben a 21. sorban (-1)-gyel szoroznánk).
Végül az esemény left tulajdonságát frissítve elhelyezzük az eseményt a megfelelő nap al-oszlopában.

Összefoglalás

A fent bemutatott megoldás csoport-naptár létrehozását teszi lehetővé fullcalendar alapokra. Nincsenek átlapolások, és minden esemény a neki megfelelő al-oszlopban marad, akkor is, ha nincs mellette más esemény.
Előnyei:

  • egy al-oszlophoz tartozó eseményt áthelyezni is csak a saját al-oszlopába lehet,
  • a csoport-tagok foglaltsága ránézésre leolvasható,
  • a typeCounts növelésével akárhány al-oszlop kialakítható (túl nagy szám esetén kérdés az olvashatóság)

Ötlet:

  • az események hozhatnák magukkal a typeCounts értékét, így ennek változtásakor – új csapattag felvétele – nem kellene a kódon módosítani