Durchsuchbare Dokumentation aufrufen

Zurück zur Dokumentationsübersicht

agorum.calendar

Dieses Widget stellt einen Kalender dar, um Termine in einer Tages-, Wochen- und Monatsansicht darzustellen und zu bearbeiten.

Verwendung


Folgendes Skript dient als Grundgerüst für den Aufbau eines Kalenders. Ein vollständiges Beispiel finden Sie am Ende des Dokuments.

let aguila = require('common/aguila');

// create the calendar widget
let widget = aguila.create({
  type: 'agorum.single',
  width: 800,
  height: 600,
  items: [
    {
      // initialize the calendar widget
      type: 'agorum.calendar',
      name: 'calendar',
      flexible: true,
      
      // locale of calendar, eg. de, en
      locale: 'de',
      
      // timeZone of calendar, e.g. Europe/Berlin
      timeZone: 'local',
      
      // scroll time (time to start in dayGrid)
      // optional, Standard: '06:00:00'
      scrollTime: '09:00:00',
      
      // slot duration
      // optional, Standard: '00:30:00'
      slotDuration: '00:30:00',
     
      // current active date-time for displaying
      // optional, Standard: current date
      activeTime: new Date(),
      
      // set active date-time
      // optional, Standard: current date
      now: new Date(),
      
      // set active view
      // optional: Standard: dayGridMonth
      // possible values: dayGridMonth, timeGridWeek, timeGridDay, list
      view: 'timeGridWeek', 

      // set initial events
      events: [],
      
      // set calendar not readOnly
      // optional, Standard: false
      readOnly: false,
      
      // show navigation header
      // optional, Standard: false
      header: true,

      // optional, Standard, siehe Beispiel
      headerToolbar: {
        left: 'prev,next today',
        center: 'title',
        right: 'dayGridMonth,timeGridWeek,timeGridDay'  // listYear
      },
      
      // show week numbers
      // optional, Standard: false
      weekNumbers: true,
      
      // show now indicator
      // optional, Standard: false
      nowIndicator: true,
      
      // show navigation links (click on day)
      // optional, Standard: false
      navLinks: true,
      
      // setup business hours and week days
      // optional, Standard: as defined below
      businessHours: {
        daysOfWeek: [ 1, 2, 3, 4, 5 ],
        startTime: '09:00',
        endTime: '17:00'
      }
    }
  ]
 
});

// get widgets
let calendar = widget.down('calendar');

/**
 * events
 */

/**
 * fired, when an event is clicked
 *
 * info: {
 *   id: 'id-of-event',
 *   start: from-date-time
 *   end: to-date-time
 * }
 */
calendar.on('eventClicked', info => {
  console.log('eventClicked', info);
});

/**
 * fired, when event is changed
 * changes can be stored in a database here
 *
 * info: {
 *   id: 'id-of-event',
 *   start: new-start-date-time,
 *   end: new-end-date-time
 * }
 */
calendar.on('eventChanged', info => {
  console.log('eventChanged', info);
});

/**
 * fired, when an event is added
 * event can be saved to a database here
 *
 * info: {
 *   start: start-date-time,
 *   end: end-date-time
 * }
 */ 
calendar.on('eventAdded', info => {
  console.log('eventAdded', info);
});

/**
 * fired, when the calendar widget wants new events for the given range
 * here events can be fetched from a database or a REST service
 *
 * info: {
 *   start: from-date-time
 *   end: to-date-time
 * }
 */
calendar.on('fetchEvents', info => {
  console.log('fetchEvents', info);
});

/**
 * fired, when view is changed
 *
 * view: string (dayGridMonth, timeGridWeek, timeGridDay, list)
 */
calendar.on('viewChanged', view => {
  console.log('viewChanged', view);
});

/**
 * fired, when activeTime is changed (normally start of week, month or day)
 *
 * activeTime: the date-time
 */
calendar.on('activeTimeChanged', activeTime => {
  console.log('activeTimeChanged', activeTime);
});

/**
 * fired, when now is changed (the current selected date-time)
 *
 * now: the date-time
 */
calendar.on('nowChanged', now => {
  console.log('nowChanged', now);
});

/**
 * fired, when activeStart is changed (the start of fetched events)
 *
 * activeStart: the date-time
 */
calendar.on('activeStartChanged', activeStart => {
  console.log('activeStartChanged', activeStart);
});

/**
 * fired, when activeEnd is changed (the end of fetched events)
 *
 * activeEnd: the date-time
 */
calendar.on('activeEndChanged', activeEnd => {
  console.log('activeEndChanged', activeEnd);
});

widget;

Ablauf der Terminselektierung


Lädt der Kalender oder ändert sich der Datumsbereich, löst das Event fetchEvents aus.

Einen Termin zum Kalender setzen

Details zum Event selbst (siehe Parameter events).

let event = {
  start: new Date('2020-01-01T13:00:00Z'),
  end: new Date('2020-01-01T14:00:00Z'),
  title: 'Test Termin',
  id: 'Eindeutige ID',
  description: 'Eine Beschreibung',
  color: '#00FF00', // Farbe
  mark: 'orange', // Farbe: Markierung linker Rand
  textColor: 'white', // Textfarbe
  readOnly: false, // Bearbeitbar: false/true
  groupId: 'ID einer Gruppe', // etwa für Wiederholtermine
  cls: 'custom-stylesheet-classname' // optional
};

calendar.events = [ event ];

Optionale Parameter


locale (string)

Definiert die Sprache des Kalenders.


Beispiel

calendar.locale = 'de'; // Standard: en


Beispiel: Sprache der aktuellen agorum core-Session

calendar.locale = sc.locale;

activeTime (date)

Definiert einen Zeitpunkt, der erscheint.


Beispiel

calendar.activeTime = new Date('2020-01-01T00:00:00Z'); // Standard: aktuelle Zeit

now (date)

Definiert den aktiven Zeitpunkt des Kalenders und markiert den aktuellen Zeitpunkt durch eine Linie.


Beispiel

calendar.now = new Date('2020-01-01T00:00:00Z'); // Standard: aktuelle Zeit

view (string)

Definiert die Darstellung.

Wert Beschreibung
timeGridDay Tagesansicht
timeGridWeek Wochenansicht
dayGridMonth (Standard) Monatsansicht
list Liste


Beispiel

calendar.view = 'timeGridWeek';

events (array)

Setzt Termine.


Beispiel

let event = {
  start: new Date('2020-01-01T13:00:00Z'),
  end: new Date('2020-01-01T14:00:00Z'),
  title: 'Test Termin',
  id: 'Eindeutige ID',
  description: 'Eine Beschreibung',
  color: '#00FF00', // Farbe
  mark: 'orange', // Farbmarkierung
  textColor: 'white',
  readOnly: false, // Bearbeitbar: false/true
  groupId: 'ID einer Gruppe', // etwa für Wiederholtermine
  cls: 'custom-stylesheet-classname' // optional
};

calendar.events = [ event ];


Beispiel: Ganztagestermin

let event = {
  startDay: '2020-01-01',
  title: 'Feiertag',
  description: 'Ein schöner Feiertag',
  color: '#00FF00', // Farbe
  mark: 'orange', // Farbmarkierung
  textColor: 'white',
  readOnly: true, // Bearbeitbar: false/true
};

calendar.events = [ event ];

readOnly (boolean)

Erlaubt die Änderung von Terminen im Kalender.

Wert Beschreibung
true Benutzer können die Termine im Kalender NICHT ändern.
false (Standard) Benutzer können die Termine im Kalender ändern.


Beispiel

calendar.readOnly = true;

header (boolean)

Wert Beschreibung
true Blendet den Header des Kalenders über die Schaltflächen für die Navigation und Ansicht ein.
false (Standard) Blendet den Header des Kalenders über die Schaltflächen für die Navigation und Ansicht aus.


Beispiel

calendar.header = true;

headerToolbar (object)

Stellt den Header des Kalenders individuell ein.


Beispiel: Standard

calendar.headerToolbar= {
  left: 'prev,next today',
  center: 'title',
  right: 'dayGridMonth,timeGridWeek,timeGridDay'
};


Beispiel: Mit Terminübersicht

calendar.headerToolbar = {
  left: 'prev,next today',
  center: 'title',
  right: 'dayGridMonth,timeGridWeek,timeGridDay,listYear'
};

weekNumbers (boolean)

Wert Beschreibung
true Blendet Kalenderwochen ein.
false (Standard) Blendet Kalenderwochen aus.


Beispiel

calendar.weekNumbers = true;

nowIndicator (boolean)

Wert Beschreibung
true Blendet den aktuellen Zeitpunkt im Kalender ein (Linie in der Tagesansicht und Wochenansicht).
false (Standard) Blendet den aktuellen Zeitpunkt im Kalender aus.


Beispiel

calendar.nowIndicator = true;

navLinks (boolean)

Wert Beschreibung
true Wechselt bei Klick auf die Tagesangaben zur Tagesansicht.
false (Standard) Wechselt bei Klick auf die Tagesangaben NICHT zur Tagesansicht.


Beispiel

calendar.navLinks= true;

timeZone (string)

Stellt die Zeitzone des Kalenders ein.


Beispiel

calendar.timeZone = 'Europe/Berlin'; // Standard: local (Zeitzone des Browsers)

scrollTime (string)

Definiert, zu welcher Uhrzeit das System im Standard scrollt.


Beispiel

calendar.scrollTime = '09:00:00'; // Standard: 06:00:00

slotDuraction (string)

Definiert, welche Zeitschritte zwischen den einzelnen Stundenabschnitten gelten.


Beispiel

calendar.slotDuration = '00:15:00'; // Alle 15 Minuten (pro Stunde 4 Slots), Standard: 00:30:00

businessHours (object)

Definiert, welche Zeitbereiche das System als „Arbeitszeit“ definiert.


Beispiel

Das folgende Beispiel definiert Montag (1) – Freitag (5) je von 09:00 Uhr – 17:00 Uhr als Arbeitszeit. (Sonntag = 0):

calendar.businessHours = businessHours: {
  daysOfWeek: [ 1, 2, 3, 4, 5 ],
  startTime: '09:00',
  endTime: '17:00'
};

Werte des Parameters „events“


title (string)

Definiert den Titel des Termins.

id (string)

Definiert eine eindeutige ID des Termins.

start (date)

Definiert die Startzeit des Termins (Datum + Uhrzeit in UTC).


Beispiel

new Date('2020-01-01T14:00:00Z')

end (date)

Definiert die Endzeit des Termins (Datum + Uhrzeit in UTC).


Beispiel

new Date('2020-01-01T14:00:00Z')

startDay (string)

Definiert das Startdatum von Ganztagesterminen.


Beispiel

'2020-01-01'

endDay (string)

Definiert das Enddatum von Ganztagesterminen.

Hinweis: Das Datum ist exklusiv, wenn Sie endDay definiert haben, d. h. der Termin endet vor diesem Datum und das Datum ist nicht mehr dabei. Ausnahme: startDay und endDay sind am selben Tag.


Beispiel

'2020-01-01'

description (string)

Beschreibt den Termin in Form eines Tooltips.

color (string)

Definiert optional Farbe des Termins.


Beispiele

#FF0000
red

mark (string)

Definiert die Farbmarkierung des Termins am linken Rand.


Beispiele

#FF0000 // Standard: kein Rand
orange

textColor (string)

Definiert die Farbe des Textes.


Beispiele

#FF0000 // Standard: white
orange

readOnly (boolean)

Wert Beschreibung
true Benutzer können den Termin ändern.
false (Standard) Benutzer können den Termin NICHT ändern.

groupId (string)

Gruppiert Termine mit derselben GroupId, um sie etwa zusammen verschieben zu können, etwa bei Wiederholterminen (Standard: undefined).

cls (string)

Ordnet einem Event einen CSS-Classname zu, den Sie dann über eine CSS definieren können (Standard: undefined).

Events


eventClicked

Löst aus, wenn ein Benutzer auf einen Termin klickt. 

info: {
  id: 'id-des-events',
  start: Start-Zeit (als Date-Objekt)
  end: Ende-Zeit (als Date-Objekt)
}


Beispiel

calendar.on('eventClicked', info => {
  console.log('eventClicked', info);
});

eventChanged

Löst aus, wenn sich ein Termin ändert (verschoben oder Dauer geändert).

info: {
  id: 'id-des-events',
  start: neue Start-Zeit (als Date-Objekt)
  end: neue Ende-Zeit (als Date-Objekt)
}


Beispiel

calendar.on('eventChanged', info => {
  console.log('eventChanged', info);
});

eventAdded

Löst aus, wenn ein Benutzer einen Termin hinzufügt.

info: {
  start: Start-Zeit (als Date-Objekt)
  end: Ende-Zeit (als Date-Objekt)
}


Beispiel

calendar.on('eventAdded', info => {
  console.log('eventAdded', info);
});

fetchEvents

Löst aus, wenn der Kalender neue Termine für die Darstellung anfordert.

info: {
  start: Start-Zeit (als Date-Objekt)
  end: Ende-Zeit (als Date-Objekt)
}


Beispiel

calendar.on('fetchEvents', info => {
  console.log('fetchEvents', info);

  // load events from somewhere (e.g. database or service)
  let events = [ ... ];

  // set loaded events to calendar
  calendar.events = events;
});

viewChanged

Löst aus, wenn sich die Ansicht des Kalenders ändert (Tagesansicht, Wochenansicht, Monatsansicht oder Liste). 

Wert Beschreibung
timeGridDay Tagesansicht
timeGridWeek Wochenansicht
dayGridMonth Monatsansicht
list Liste


Beispiel

calendar.on('viewChanged', view => {
  console.log('viewChanged', view);
});

activeTimeChanged (date)

Löst aus, wenn sich die activeTime ändert (das Datum der aktuellen Ansicht).


Beispiel

calendar.on('activeTimeChanged', activeTime => {
  console.log('activeTimeChanged', activeTime);
});

nowChanged (date)

Löst aus, wenn sich now ändert (das Datum des aktuell selektierten Tags + Uhrzeit).


Beispiel

calendar.on('nowChanged', now => {
  console.log('nowChanged', now);
});

activeStartChanged (date)

Löst aus, wenn sich activeStart ändert (das Start-Datum der aktuellen Ansicht).


Beispiel

calendar.on('activeStartChanged', activeStart => {
  console.log('activeStartChanged', activeStart);
});

activeEndChanged

Löst aus, wenn sich activeEnd ändert (das Ende-Datum der aktuellen Ansicht).


Beispiel

calendar.on('activeEndChanged', activeEnd => {
  console.log('activeEndChanged', activeEnd);
});

Beispiele


Komplettes Beispiel

Das folgende Beispiel erstellt Zufallstermine im gewählten Zeitraum und lässt die Erstellung von Terminen zu (In-Memory).

/* global sc */
let aguila = require('common/aguila');
let message = require('/agorum/roi/customers/agorum.composite/js/lib/message.js');
let templates = require('common/templates');

let events = [];
let testGlobalId = 0;

// create the calendar widget
let widget = aguila.create({
  type: 'agorum.single',
  width: 800,
  height: 600,
  docked: {
    top: {
      type: 'agorum.toolbar',
      items: [
        // go to current date
        aguila.create({
          type: 'agorum.button',
          text: 'Now'
        }).on('clicked', () => calendar.activeTime = new Date()),
        
        // switch views
        aguila.create({
          type: 'agorum.button',
          text: 'Month'
        }).on('clicked', () => changeView('dayGridMonth')),
        aguila.create({
          type: 'agorum.button',
          text: 'Week'
        }).on('clicked', () => changeView('timeGridWeek')),
        aguila.create({
          type: 'agorum.button',
          text: 'Day'
        }).on('clicked', () => changeView('timeGridDay')),
        aguila.create({
          type: 'agorum.button',
          text: 'List'
        }).on('clicked', () => changeView('list')),
        
        // toggle weekNumbers on/off
        aguila.create({
          type: 'agorum.button',
          text: 'weekNumbers'
        }).on('clicked', () => calendar.weekNumbers = !calendar.weekNumbers),
        
        // toggle readOnly on/off
        aguila.create({
          type: 'agorum.button',
          text: 'readOnly'
        }).on('clicked', () => {
          calendar.readOnly = !calendar.readOnly;
        }),
        
        // toggle header on/off
        aguila.create({
          type: 'agorum.button',
          text: 'header'
        }).on('clicked', () => {
          calendar.header = !calendar.header;
        }),
       
        // set scroll position to 03:00
        aguila.create({
          type: 'agorum.button',
          text: 'scroll'
        }).on('clicked', () => {
          calendar.scrollTime = '03:00:00';
        }),
      ]
    }
  },
  items: [
    {
      // initialize the calendar widget
      type: 'agorum.calendar',
      name: 'calendar',
      flexible: true,
      
      // locale of calendar, e.g., de, en
      // use locale of session here
      locale: sc.locale,
      
      // timeZone of calendar, e.g., Europe/Berlin
      // local = timeZone of browser
      timeZone: 'local',
      
      // scroll time (time to start in dayGrid)
      // optional, Standard: '06:00:00'
      scrollTime: '09:00:00',
      
      // slot duration
      // optional, Standard: '00:30:00'
      slotDuration: '00:30:00',
     
      // current active date-time for displaying
      // optional, Standard: current date
      activeTime: new Date(),
      
      // set active date-time
      // optional, Standard: current date
      now: new Date(),
      
      // set active view
      // optional: Standard: dayGridMonth
      // possible values: dayGridMonth, timeGridWeek, timeGridDay, list
      view: 'timeGridWeek', 

      // set initial events
      events: events,
      
      // set calendar not readOnly
      // optional, Standard: false
      readOnly: false,
      
      // show navigation header
      // optional, Standard: false
      header: true,
      
      // show week numbers
      // optional, Standard: false
      weekNumbers: true,
      
      // show now indicator
      // optional, Standard: false
      nowIndicator: true,
      
      // show navigation links (click on day)
      // optional, Standard: false
      navLinks: true,
      
      // setup business hours and weekdays
      // optional, Standard: as defined below
      businessHours: {
        daysOfWeek: [ 1, 2, 3, 4, 5 ],
        startTime: '09:00',
        endTime: '17:00'
      }
    }
  ]
 
});

// get widgets
let calendar = widget.down('calendar');

/**
 * events
 */

/**
 * fired, when an event is clicked
 *
 * info: {
 *   id: 'id-of-event',
 *   start: from-date-time
 *   end: to-date-time
 * }
 */
calendar.on('eventClicked', info => {
  console.log('eventClicked', info);
  message.alert('eventClicked', 'You clicked on: ' + info.id);
});

/**
 * fired, when event is changed
 * changes can be stored in a database here
 *
 * info: {
 *   id: 'id-of-event',
 *   start: new-start-date-time,
 *   end: new-end-date-time
 * }
 */
calendar.on('eventChanged', info => {
  console.log('eventChanged', info);
  
  // find event in events and change it
  let event = events.find(event => event.id === info.id);
  if (event) {
    // change it
    createUpdateEvent({
      start: info.start,
      end: info.end
    }, event);
    
    // update events
    calendar.events = events;
  }
});

/**
 * fired, when an event is added
 * event can be saved to a database here
 *
 * info: {
 *   start: start-date-time,
 *   end: end-date-time
 * }
 */ 
calendar.on('eventAdded', info => {
  console.log('eventAdded', info);
  
  // add a test event
  let id = buildTestId();
  events.push(createUpdateEvent({
    title: 'Event ' + id,
    id: id,
    start: info.start,
    end: info.end,
    descriptionText: 'This is an event',
    color: '#00c200'
  }));
  
  // set events to calendar
  calendar.events = events;
});

/**
 * fired, when the calendar widget wants new events for the given range
 * here events can be fetched from a database or a REST service
 *
 * info: {
 *   start: from-date-time
 *   end: to-date-time
 * }
 */
calendar.on('fetchEvents', info => {
  console.log('fetchEvents', info);
  
  // drop all events and fetch new (e.g., from index)
  // create some random events between range
  
  // please keep in mind, this is just for demo purposes
  // so events change everytime the date range is changed
  let startTS = new Date(info.start).getTime();
  let endTS = new Date(info.end).getTime();
  
  aguila.fork(() => {
    let newEvents = [];
    
    // create test appointments, depending on current view
    let amount;
    if (calendar.view === 'dayGridMonth') amount = 100;
    else if (calendar.view === 'timeGridWeek') amount = 10;
    else if (calendar.view === 'timeGridDay') amount = 5;
    else if (calendar.view === 'list') amount = 50;
    
    for (let i = 0; i < amount; i++) {
      let id = buildTestId();

      let eventTS = Math.floor(startTS + (Math.random() * (endTS - startTS)));
      let duration = Math.floor((1 + Math.random()) * 3 * 60 * 60 * 1000);

      let eventStart = new Date(eventTS);
      let eventEnd = new Date(eventTS + duration);
      newEvents.push(createUpdateEvent({
        title: 'Event ' + id,
        id: id,
        start: eventStart,
        end: eventEnd,
        descriptionText: 'This is an event',
        color: '#73a3e0'
      }));
    }
    
    return newEvents;
  }).then(newEvents => {
    // set new events to calendar
    events = newEvents;
    calendar.events = events;
  });
  
});

/**
 * fired, when view is changed
 *
 * view: string (dayGridMonth, timeGridWeek, timeGridDay, list)
 */
calendar.on('viewChanged', view => {
  console.log('viewChanged', view);
});

/**
 * fired, when activeTime is changed (normally start of week, month, or day)
 *
 * activeTime: the date-time
 */
calendar.on('activeTimeChanged', activeTime => {
  console.log('activeTimeChanged', activeTime);
});

/**
 * fired, when now is changed (the current selected date-time)
 *
 * now: the date-time
 */
calendar.on('nowChanged', now => {
  console.log('nowChanged', now);
});

/**
 * fired, when activeStart is changed (the start of fetched events)
 *
 * activeStart: the date-time
 */
calendar.on('activeStartChanged', activeStart => {
  console.log('activeStartChanged', activeStart);
});

/**
 * fired, when activeEnd is changed (the end of fetched events)
 *
 * activeEnd: the date-time
 */
calendar.on('activeEndChanged', activeEnd => {
  console.log('activeEndChanged', activeEnd);
});


/**
 * helpers
 */
// change the view of the calendar
function changeView(view) {
  calendar.view = view;
}

function getLocale() {
  return 'de';
}

// format a date with time
function formatDateTime(date) {
  return templates.fill('${aDate:dd.MM.yyyy HH:mm}', { aDate: date });
}

// test creation/update of an event
function createUpdateEvent(info, event) {
  // this is just a sample event creation
  // also used to update an existing event
  
  event = event || {};

  let start = formatDateTime(info.start || event.start);
  let end = formatDateTime(info.end || event.end);
  
  event.title = info.title || event.title;
  event.id = info.id || event.id;
  
  // descriptionText is used here to remember the description, when an event is updated
  event.descriptionText = info.descriptionText || event.descriptionText;
  
  event.description = '<b>' + event.title + '</b><br>' + 
      sc.loginUser.fullName + '<br>' + 
      start + ' - ' + end + '<br><br>' + 
      event.descriptionText;
  
  event.start = info.start || event.start;
  event.end = info.end || event.end;
  event.color = info.color || event.color;
  event.readOnly = info.readOnly || event.readOnly; 
  event.groupId = info.groupId;
  
  return event;
}

// build a test id
function buildTestId() {
  testGlobalId ++;
  return 'id-' + testGlobalId;
}

widget;

Feiertagskalender

Ein REST-Service lädt Feiertage und stellt sie in einem Kalender dar. Dabei bündelt das System die Termine pro Bundesland und gibt sie beim Tooltip aus.

let aguila = require('common/aguila');
let json = require('client/json');

let loadedYears = {};
let events = [];

// create the calendar widget
let widget = aguila.create({
  type: 'agorum.vbox',
  width: 800,
  height: 600,
  items: [
    {
      // initialize the calendar widget
      type: 'agorum.calendar',
      name: 'calendar',
      flexible: true,
      
      // locale of calendar, e.g., de, en
      locale: 'de',
      
      // timeZone of calendar, e.g., Europe/Berlin
      timeZone: 'local',
      
      // scroll time (time to start in dayGrid)
      // optional, Standard: '06:00:00'
      scrollTime: '09:00:00',
      
      // slot duration
      // optional, Standard: '00:30:00'
      slotDuration: '00:30:00',
     
      // current active date-time for displaying
      // optional, Standard: current date
      activeTime: new Date('2020-01-01T13:00:00Z'),
      
      // set active date-time
      // optional, Standard: current date
      // now: new Date(),
      
      // set active view
      // optional: Standard: dayGridMonth
      // possible values: dayGridMonth, timeGridWeek, timeGridDay, list
      view: 'timeGridWeek', 

      // set initial events
      events: [],
      
      // set calendar not readOnly
      // optional, Standard: false
      readOnly: true,
      
      // show navigation header
      // optional, Standard: false
      header: true,
      
      // show week numbers
      // optional, Standard: false
      weekNumbers: true,
      
      // show now indicator
      // optional, Standard: false
      nowIndicator: true,
      
      // show navigation links (click on day)
      // optional, Standard: false
      navLinks: true,
      
      // setup business hours and weekdays
      // optional, Standard: as defined below
      businessHours: {
        daysOfWeek: [ 1, 2, 3, 4, 5 ],
        startTime: '09:00',
        endTime: '17:00'
      }
    }
  ]
 
});

// get widgets
let calendar = widget.down('calendar');

/**
 * events
 */

/**
 * fired, when an event is clicked
 *
 * info: {
 *   id: 'id-of-event',
 *   start: from-date-time
 *   end: to-date-time
 * }
 */
calendar.on('eventClicked', info => {
  console.log('eventClicked', info);
});

/**
 * fired, when event is changed
 * changes can be stored in a database here
 *
 * info: {
 *   id: 'id-of-event',
 *   start: new-start-date-time,
 *   end: new-end-date-time
 * }
 */
calendar.on('eventChanged', info => {
  console.log('eventChanged', info);
});

/**
 * fired, when an event is added
 * event can be saved to a database here
 *
 * info: {
 *   start: start-date-time,
 *   end: end-date-time
 * }
 */ 
calendar.on('eventAdded', info => {
  console.log('eventAdded', info);
});

/**
 * fired, when the calendar widget wants new events for the given range
 * here events can be fetched from a database or a REST service
 *
 * info: {
 *   start: from-date-time
 *   end: to-date-time
 * }
 */
calendar.on('fetchEvents', info => {
  console.log('fetchEvents', info);

  let startYear = '' + (new Date(info.start).getYear() + 1900);
  let endYear = '' + (new Date(info.end).getYear() + 1900);

  // load events 2 times, for boundaries at the beginning of 
  // the year and the end.
  // make this with a promise, to prevent concurrent changes to 
  // events
  loadEvents(startYear, false).then(() => {
    loadEvents(endYear, true);
  });
  
});

function loadEvents(loadYear, append) {
  return aguila.fork(() => {
    if (loadedYears[loadYear]) {
      // year already loaded
      return;
    }

    // https://feiertage-api.de/api/?jahr=2020&nur_land=BW
    let service = json('https://feiertage-api.de/api', { 
      type: 'application/json; charset=utf-8',
      accept: 'application/json'
    })('/');

    service.query({ 
      jahr: loadYear      
    }); 

    // load events
    let response = service.get();
    
    // build a structure with events for combined for each country
    let allEvents = {};
    
    Object.keys(response).forEach(country => {
      let countryEvents = response[country];
      Object.keys(countryEvents).forEach(title => {
        let event = countryEvents[title];
        
        if (allEvents[title]) {
          // add to existing
          
          // append country to description
          allEvents[title].description += ', ' + country;
        }
        else {
          // make new

          allEvents[title] = {
            title: title,
            startDay: event.datum,
            description: 'Bundesländer: ' + country,
            color: '#0069b5',
            mark: '#00e5ff'
          };
        }
      });
    });    
    
    // convert to array
    return Object.keys(allEvents).map(title => allEvents[title]);
  }).then(newEvents => {
    if (!newEvents) return;
    
    if (append) {
      // append loaded year
      events = events.concat(newEvents);
      loadedYears[loadYear] = true;
    }
    else {
      events = newEvents;
      // reset loaded years and set new
      loadedYears = {};
      loadedYears[loadYear] = true;
    }
    calendar.events = events;
  });
}

/**
 * fired, when view is changed
 *
 * view: string (dayGridMonth, timeGridWeek, timeGridDay, list)
 */
calendar.on('viewChanged', view => {
  console.log('viewChanged', view);
});

/**
 * fired, when activeTime is changed (normally start of week, month, or day)
 *
 * activeTime: the date-time
 */
calendar.on('activeTimeChanged', activeTime => {
  console.log('activeTimeChanged', activeTime);
});

/**
 * fired, when now is changed (the current selected date-time)
 *
 * now: the date-time
 */
calendar.on('nowChanged', now => {
  console.log('nowChanged', now);
});

/**
 * fired, when activeStart is changed (the start of fetched events)
 *
 * activeStart: the date-time
 */
calendar.on('activeStartChanged', activeStart => {
  console.log('activeStartChanged', activeStart);
});

/**
 * fired, when activeEnd is changed (the end of fetched events)
 *
 * activeEnd: the date-time
 */
calendar.on('activeEndChanged', activeEnd => {
  console.log('activeEndChanged', activeEnd);
});

widget;