<!DOCTYPE html>
<html>

  <head>
    <title>Angular Schnellstart</title>
    
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.6/semantic.css" />
    <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
    <link rel="stylesheet" href="vendor/calendar.css">
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.6/semantic.js"></script>
    <script src="vendor/calendar.js"></script>
    
    <script src="https://unpkg.com/core-js/client/shim.min.js"></script>
    <script src="https://unpkg.com/zone.js@0.6.25?main=browser"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3"></script>
    <script src="https://unpkg.com/systemjs@0.19.27/dist/system.src.js"></script>
    <script src="systemjs.config.js"></script>
    <script>
      System.import('./app/main')
    </script>
    
  </head>

  <body class="ui container">
    <my-app>Loading...</my-app>
</html>
# Angular 2 - Use Semantic-UI Calendar

In this sample I wrap a jQuery calendar plugin called [semantic-ui-calendar](https://github.com/mdehoog/Semantic-UI-Calendar).
My goal is to use the calendar in an Angular app without caring about jQuery anymore.
System.config({
  transpiler: 'ts',
  typescriptOptions: {
    'experimentalDecorators': true
  },
  packages: {
    app: { defaultExtension: 'ts' },
    rxjs: { }
  },
  meta: {
    'typescript': { 'exports': 'ts' }
  },
  paths: { 'npm:': 'https://unpkg.com/' },
  map: {
    '@angular/common':                   'npm:@angular/common@2.2.0/bundles/common.umd.js',
    '@angular/compiler':                 'npm:@angular/compiler@2.2.0/bundles/compiler.umd.js',
    '@angular/core':                     'npm:@angular/core@2.2.0/bundles/core.umd.js',
    '@angular/forms':                    'npm:@angular/forms@2.2.0/bundles/forms.umd.js',
    '@angular/http':                     'npm:@angular/http@2.2.0/bundles/http.umd.js',
    '@angular/platform-browser':         'npm:@angular/platform-browser@2.2.0/bundles/platform-browser.umd.js',
    '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic@2.2.0/bundles/platform-browser-dynamic.umd.js',
    '@angular/router':                   'npm:@angular/router@3.2.0/bundles/router.umd.js',
    'rxjs':                              'npm:rxjs',
    'ts':                                'npm:plugin-typescript@4.0.10/lib/plugin.js',
    'typescript':                        'npm:typescript@2.0.8/lib/typescript.js'
  }
});
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';

import { AppComponent } from './app.component';
import { CalendarComponent } from './calendar.component';

import { CalendarOptions } from './shared/calendar.options';
import { GermanCalendarOptions } from './shared/german-calendar.options';
import { CalendarOptions } from './shared/calendar.options';

@NgModule({
  imports:      [BrowserModule, FormsModule],
  declarations: [AppComponent, CalendarComponent],
  bootstrap:    [AppComponent],
  providers: [
    { provide: CalendarOptions, useClass: GermanCalendarOptions }
  ]
})
export class AppModule { }
import { Inject, Optional, Component } from '@angular/core';
import { GermanCalendarOptions } from './shared/german-calendar.options';

@Component({
  selector: 'my-app',
  template: `
    <h2>Calendar without any options</h2>
    <sui-calendar></sui-calendar>

    <h2>Calendar with <code>german date</code></h2>
    <strong>Selected Date</strong> <code>{{ today | date:'dd.MM.yyyy hh:mm' }}</code>
    <sui-calendar type="date" [(ngModel)]="today"></sui-calendar>
    
    <h2><code>min-</code> and <code>max-Date</code></h2>
    <small>November - December</small>
    <sui-calendar 
      type="date" 
      [(ngModel)]="today2"
      [minDate]="minDate"
      [maxDate]="maxDate">
    </sui-calendar>
  `
})
export class AppComponent {
  today = new Date();
  today2 = new Date();
  minDate = new Date(2016, 10, 1);
  maxDate = new Date(2016, 11, 30);
}
/*!
 * # Semantic UI 0.0.3 - Calendar
 * http://github.com/semantic-org/semantic-ui/
 *
 *
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 */


/*******************************
            Popup
*******************************/

.ui.calendar .ui.popup {
  max-width: none;
  padding: 0;
  border: none;
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
}


/*******************************
            Calendar
*******************************/

.ui.calendar .calendar:focus {
  outline: 0;
}


/*******************************
            Grid
*******************************/

.ui.calendar .ui.popup .ui.grid {
  display: block;
  white-space: nowrap;
}
.ui.calendar .ui.popup .ui.grid > .column {
  width: auto;
}


/*******************************
            Table
*******************************/

.ui.calendar .ui.table.year,
.ui.calendar .ui.table.month,
.ui.calendar .ui.table.minute {
  min-width: 15em;
}
.ui.calendar .ui.table.day {
  min-width: 18em;
}
.ui.calendar .ui.table.hour {
  min-width: 20em;
}
.ui.calendar .ui.table tr th,
.ui.calendar .ui.table tr td {
  padding: 0.5em;
  white-space: nowrap;
}
.ui.calendar .ui.table tr th {
  border-left: none;
}
.ui.calendar .ui.table tr th .icon {
  margin: 0;
}
.ui.calendar .ui.table tr th .icon {
  margin: 0;
}
.ui.calendar .ui.table tr:first-child th {
  position: relative;
  padding-left: 0;
  padding-right: 0;
}
.ui.calendar .ui.table.day tr:first-child th {
  border: none;
}
.ui.calendar .ui.table.day tr:nth-child(2) th {
  padding-top: 0.2em;
  padding-bottom: 0.3em;
}
.ui.calendar .ui.table tr td {
  padding-left: 0.1em;
  padding-right: 0.1em;
}
.ui.calendar .ui.table tr .link {
  cursor: pointer;
}
.ui.calendar .ui.table tr .prev.link {
  width: 14.28571429%;
  position: absolute;
  left: 0;
}
.ui.calendar .ui.table tr .next.link {
  width: 14.28571429%;
  position: absolute;
  right: 0;
}
.ui.calendar .ui.table tr .disabled {
  pointer-events: none;
  color: rgba(40, 40, 40, 0.3);
}

/*--------------
     States
---------------*/

.ui.calendar .ui.table tr td.today {
  font-weight: bold;
}
.ui.calendar .ui.table tr td.range {
  background: rgba(0, 0, 0, 0.05);
  color: rgba(0, 0, 0, 0.95);
  box-shadow: none;
}
.ui.calendar .ui.table.inverted tr td.range {
  background: rgba(255, 255, 255, 0.08);
  color: #ffffff;
  box-shadow: none;
}
.ui.calendar .calendar:focus .ui.table tbody tr td.focus,
.ui.calendar .calendar.active .ui.table tbody tr td.focus {
  box-shadow: inset 0 0 0 1px #85B7D9;
}
.ui.calendar .calendar:focus .ui.table.inverted tbody tr td.focus,
.ui.calendar .calendar.active .ui.table.inverted tbody tr td.focus {
  box-shadow: inset 0 0 0 1px #85B7D9;
}


/*******************************
         Theme Overrides
*******************************/
/*
 * # Semantic UI 0.0.4 - Calendar
 * http://github.com/semantic-org/semantic-ui/
 *
 *
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 */

;
(function ($, window, document, undefined) {

  "use strict";

  window = (typeof window != 'undefined' && window.Math == Math)
    ? window
    : (typeof self != 'undefined' && self.Math == Math)
    ? self
    : Function('return this')()
  ;

  $.fn.calendar = function (parameters) {

    var
      $allModules = $(this),

      moduleSelector = $allModules.selector || '',

      time = new Date().getTime(),
      performance = [],

      query = arguments[0],
      methodInvoked = (typeof query == 'string'),
      queryArguments = [].slice.call(arguments, 1),
      returnedValue
      ;

    $allModules
      .each(function () {
        var
          settings = ( $.isPlainObject(parameters) )
            ? $.extend(true, {}, $.fn.calendar.settings, parameters)
            : $.extend({}, $.fn.calendar.settings),

          className = settings.className,
          namespace = settings.namespace,
          selector = settings.selector,
          formatter = settings.formatter,
          parser = settings.parser,
          metadata = settings.metadata,
          error = settings.error,

          eventNamespace = '.' + namespace,
          moduleNamespace = 'module-' + namespace,

          $module = $(this),
          $input = $module.find(selector.input),
          $container = $module.find(selector.popup),
          $activator = $module.find(selector.activator),

          element = this,
          instance = $module.data(moduleNamespace),

          isTouch,
          isTouchDown = false,
          focusDateUsedForRange = false,
          module
          ;

        module = {

          initialize: function () {
            module.debug('Initializing calendar for', element);

            isTouch = module.get.isTouch();
            module.setup.popup();
            module.setup.inline();
            module.setup.input();
            module.setup.date();
            module.create.calendar();

            module.bind.events();
            module.instantiate();
          },

          instantiate: function () {
            module.verbose('Storing instance of calendar');
            instance = module;
            $module.data(moduleNamespace, instance);
          },

          destroy: function () {
            module.verbose('Destroying previous calendar for', element);
            $module.removeData(moduleNamespace);
            module.unbind.events();
          },

          setup: {
            popup: function () {
              if (settings.inline) {
                return;
              }
              if (!$activator.length) {
                $activator = $module.children().first();
                if (!$activator.length) {
                  return;
                }
              }
              if ($.fn.popup === undefined) {
                module.error(error.popup);
                return;
              }
              if (!$container.length) {
                //prepend the popup element to the activator's parent so that it has less chance of messing with
                //the styling (eg input action button needs to be the last child to have correct border radius)
                $container = $('<div/>').addClass(className.popup).prependTo($activator.parent());
              }
              $container.addClass(className.calendar);
              var onVisible = settings.onVisible;
              var onHidden = settings.onHidden;
              if (!$input.length) {
                //no input, $container has to handle focus/blur
                $container.attr('tabindex', '0');
                onVisible = function () {
                  module.focus();
                  return settings.onVisible.apply($container, arguments);
                };
                onHidden = function () {
                  module.blur();
                  return settings.onHidden.apply($container, arguments);
                };
              }
              var onShow = function () {
                //reset the focus date onShow
                module.set.focusDate(module.get.date());
                module.set.mode(settings.startMode);
                return settings.onShow.apply($container, arguments);
              };
              var on = settings.on || ($input.length ? 'focus' : 'click');
              var options = $.extend({}, settings.popupOptions, {
                popup: $container,
                on: on,
                hoverable: on === 'hover',
                onShow: onShow,
                onVisible: onVisible,
                onHide: settings.onHide,
                onHidden: onHidden
              });
              module.popup(options);
            },
            inline: function () {
              if ($activator.length && !settings.inline) {
                return;
              }
              $container = $('<div/>').addClass(className.calendar).appendTo($module);
              if (!$input.length) {
                $container.attr('tabindex', '0');
              }
            },
            input: function () {
              if (settings.touchReadonly && $input.length && isTouch) {
                $input.prop('readonly', true);
              }
            },
            date: function () {
              if ($input.length) {
                var val = $input.val();
                var date = parser.date(val, settings);
                module.set.date(date, settings.formatInput, false);
              }
            }
          },

          create: {
            calendar: function () {
              var i, r, c, p, row, cell, pageGrid;

              var mode = module.get.mode();
              var today = new Date();
              var date = module.get.date();
              var focusDate = module.get.focusDate();
              var display = focusDate || date || settings.initialDate || today;
              display = module.helper.dateInRange(display);

              if (!focusDate) {
                focusDate = display;
                module.set.focusDate(focusDate, false, false);
              }

              var isYear = mode === 'year';
              var isMonth = mode === 'month';
              var isDay = mode === 'day';
              var isHour = mode === 'hour';
              var isMinute = mode === 'minute';
              var isTimeOnly = settings.type === 'time';

              var multiMonth = Math.max(settings.multiMonth, 1);
              var monthOffset = !isDay ? 0 : module.get.monthOffset();

              var minute = display.getMinutes();
              var hour = display.getHours();
              var day = display.getDate();
              var startMonth = display.getMonth() + monthOffset;
              var year = display.getFullYear();

              var columns = isDay ? 7 : isHour ? 4 : 3;
              var columnsString = columns === 7 ? 'seven' : columns === 4 ? 'four' : 'three';
              var rows = isDay || isHour ? 6 : 4;
              var pages = isDay ? multiMonth : 1;

              var container = $container;
              container.empty();
              if (pages > 1) {
                pageGrid = $('<div/>').addClass(className.grid).appendTo(container);
              }

              for (p = 0; p < pages; p++) {
                if (pages > 1) {
                  var pageColumn = $('<div/>').addClass(className.column).appendTo(pageGrid);
                  container = pageColumn;
                }

                var month = startMonth + p;
                var firstMonthDayColumn = (new Date(year, month, 1).getDay() - settings.firstDayOfWeek % 7 + 7) % 7;
                if (!settings.constantHeight && isDay) {
                  var requiredCells = new Date(year, month + 1, 0).getDate() + firstMonthDayColumn;
                  rows = Math.ceil(requiredCells / 7);
                }

                var yearChange = isYear ? 10 : isMonth ? 1 : 0;
                var monthChange = isDay ? 1 : 0;
                var dayChange = isHour || isMinute ? 1 : 0;
                var prevNextDay = isHour || isMinute ? day : 1;
                var prevDate = new Date(year - yearChange, month - monthChange, prevNextDay - dayChange, hour);
                var nextDate = new Date(year + yearChange, month + monthChange, prevNextDay + dayChange, hour);

                var prevLast = isYear ? new Date(Math.ceil(year / 10) * 10 - 9, 0, 0) :
                  isMonth ? new Date(year, 0, 0) : isDay ? new Date(year, month, 0) : new Date(year, month, day, -1);
                var nextFirst = isYear ? new Date(Math.ceil(year / 10) * 10 + 1, 0, 1) :
                  isMonth ? new Date(year + 1, 0, 1) : isDay ? new Date(year, month + 1, 1) : new Date(year, month, day + 1);

                var table = $('<table/>').addClass(className.table).addClass(columnsString + ' column').addClass(mode).appendTo(container);

                //no header for time-only mode
                if (!isTimeOnly) {
                  var thead = $('<thead/>').appendTo(table);

                  row = $('<tr/>').appendTo(thead);
                  cell = $('<th/>').attr('colspan', '' + columns).appendTo(row);

                  var headerDate = isYear || isMonth ? new Date(year, 0, 1) :
                    isDay ? new Date(year, month, 1) : new Date(year, month, day, hour, minute);
                  var headerText = $('<span/>').addClass(className.link).appendTo(cell);
                  headerText.text(formatter.header(headerDate, mode, settings));
                  var newMode = isMonth ? (settings.disableYear ? 'day' : 'year') :
                    isDay ? (settings.disableMonth ? 'year' : 'month') : 'day';
                  headerText.data(metadata.mode, newMode);

                  if (p === 0) {
                    var prev = $('<span/>').addClass(className.prev).appendTo(cell);
                    prev.data(metadata.focusDate, prevDate);
                    prev.toggleClass(className.disabledCell, !module.helper.isDateInRange(prevLast, mode));
                    $('<i/>').addClass(className.prevIcon).appendTo(prev);
                  }

                  if (p === pages - 1) {
                    var next = $('<span/>').addClass(className.next).appendTo(cell);
                    next.data(metadata.focusDate, nextDate);
                    next.toggleClass(className.disabledCell, !module.helper.isDateInRange(nextFirst, mode));
                    $('<i/>').addClass(className.nextIcon).appendTo(next);
                  }

                  if (isDay) {
                    row = $('<tr/>').appendTo(thead);
                    for (i = 0; i < columns; i++) {
                      cell = $('<th/>').appendTo(row);
                      cell.text(formatter.dayColumnHeader((i + settings.firstDayOfWeek) % 7, settings));
                    }
                  }
                }

                var tbody = $('<tbody/>').appendTo(table);
                i = isYear ? Math.ceil(year / 10) * 10 - 9 : isDay ? 1 - firstMonthDayColumn : 0;
                for (r = 0; r < rows; r++) {
                  row = $('<tr/>').appendTo(tbody);
                  for (c = 0; c < columns; c++, i++) {
                    var cellDate = isYear ? new Date(i, month, 1, hour, minute) :
                      isMonth ? new Date(year, i, 1, hour, minute) : isDay ? new Date(year, month, i, hour, minute) :
                        isHour ? new Date(year, month, day, i) : new Date(year, month, day, hour, i * 5);
                    var cellText = isYear ? i :
                      isMonth ? settings.text.monthsShort[i] : isDay ? cellDate.getDate() :
                        formatter.time(cellDate, settings, true);
                    cell = $('<td/>').addClass(className.cell).appendTo(row);
                    cell.text(cellText);
                    cell.data(metadata.date, cellDate);
                    var adjacent = isDay && cellDate.getMonth() !== ((month + 12) % 12);
                    var disabled = adjacent || !module.helper.isDateInRange(cellDate, mode) || settings.isDisabled(cellDate, mode);
                    var active = module.helper.dateEqual(cellDate, date, mode);
                    cell.toggleClass(className.adjacentCell, adjacent);
                    cell.toggleClass(className.disabledCell, disabled);
                    cell.toggleClass(className.activeCell, active && !adjacent);
                    if (!isHour && !isMinute) {
                      cell.toggleClass(className.todayCell, !adjacent && module.helper.dateEqual(cellDate, today, mode));
                    }
                    if (module.helper.dateEqual(cellDate, focusDate, mode)) {
                      //ensure that the focus date is exactly equal to the cell date
                      //so that, if selected, the correct value is set
                      module.set.focusDate(cellDate, false, false);
                    }
                  }
                }

                if (settings.today) {
                  var todayRow = $('<tr/>').appendTo(tbody);
                  var todayButton = $('<td/>').attr('colspan', '' + columns).addClass(className.today).appendTo(todayRow);
                  todayButton.text(formatter.today(settings));
                  todayButton.data(metadata.date, today);
                }

                module.update.focus(false, table);
              }
            }
          },

          update: {
            focus: function (updateRange, container) {
              container = container || $container;
              var mode = module.get.mode();
              var date = module.get.date();
              var focusDate = module.get.focusDate();
              var startDate = module.get.startDate();
              var endDate = module.get.endDate();
              var rangeDate = (updateRange ? focusDate : null) || date || (!isTouch ? focusDate : null);

              container.find('td').each(function () {
                var cell = $(this);
                var cellDate = cell.data(metadata.date);
                if (!cellDate) {
                  return;
                }
                var disabled = cell.hasClass(className.disabledCell);
                var active = cell.hasClass(className.activeCell);
                var adjacent = cell.hasClass(className.adjacentCell);
                var focused = module.helper.dateEqual(cellDate, focusDate, mode);
                var inRange = !rangeDate ? false :
                  ((!!startDate && module.helper.isDateInRange(cellDate, mode, startDate, rangeDate)) ||
                  (!!endDate && module.helper.isDateInRange(cellDate, mode, rangeDate, endDate)));
                cell.toggleClass(className.focusCell, focused && (!isTouch || isTouchDown) && !adjacent);
                cell.toggleClass(className.rangeCell, inRange && !active && !disabled);
              });
            }
          },

          refresh: function () {
            module.create.calendar();
          },

          bind: {
            events: function () {
              $container.on('mousedown' + eventNamespace, module.event.mousedown);
              $container.on('touchstart' + eventNamespace, module.event.mousedown);
              $container.on('mouseup' + eventNamespace, module.event.mouseup);
              $container.on('touchend' + eventNamespace, module.event.mouseup);
              $container.on('mouseover' + eventNamespace, module.event.mouseover);
              if ($input.length) {
                $input.on('input' + eventNamespace, module.event.inputChange);
                $input.on('focus' + eventNamespace, module.event.inputFocus);
                $input.on('blur' + eventNamespace, module.event.inputBlur);
                $input.on('click' + eventNamespace, module.event.inputClick);
                $input.on('keydown' + eventNamespace, module.event.keydown);
              } else {
                $container.on('keydown' + eventNamespace, module.event.keydown);
              }
            }
          },

          unbind: {
            events: function () {
              $container.off(eventNamespace);
              if ($input.length) {
                $input.off(eventNamespace);
              }
            }
          },

          event: {
            mouseover: function (event) {
              var target = $(event.target);
              var date = target.data(metadata.date);
              var mousedown = event.buttons === 1;
              if (date) {
                module.set.focusDate(date, false, true, mousedown);
              }
            },
            mousedown: function (event) {
              if ($input.length) {
                //prevent the mousedown on the calendar causing the input to lose focus
                event.preventDefault();
              }
              isTouchDown = event.type.indexOf('touch') >= 0;
              var target = $(event.target);
              var date = target.data(metadata.date);
              if (date) {
                module.set.focusDate(date, false, true, true);
              }
            },
            mouseup: function (event) {
              //ensure input has focus so that it receives keydown events for calendar navigation
              module.focus();
              event.preventDefault();
              event.stopPropagation();
              isTouchDown = false;
              var target = $(event.target);
              var parent = target.parent();
              if (parent.data(metadata.date) || parent.data(metadata.focusDate) || parent.data(metadata.mode)) {
                //clicked on a child element, switch to parent (used when clicking directly on prev/next <i> icon element)
                target = parent;
              }
              var date = target.data(metadata.date);
              var focusDate = target.data(metadata.focusDate);
              var mode = target.data(metadata.mode);
              if (date) {
                var forceSet = target.hasClass(className.today);
                module.selectDate(date, forceSet);
              }
              else if (focusDate) {
                module.set.focusDate(focusDate);
              }
              else if (mode) {
                module.set.mode(mode);
              }
            },
            keydown: function (event) {
              if (event.keyCode === 27 || event.keyCode === 9) {
                //esc || tab
                module.popup('hide');
              }

              if (module.popup('is visible')) {
                if (event.keyCode === 37 || event.keyCode === 38 || event.keyCode === 39 || event.keyCode === 40) {
                  //arrow keys
                  var mode = module.get.mode();
                  var bigIncrement = mode === 'day' ? 7 : mode === 'hour' ? 4 : 3;
                  var increment = event.keyCode === 37 ? -1 : event.keyCode === 38 ? -bigIncrement : event.keyCode == 39 ? 1 : bigIncrement;
                  increment *= mode === 'minute' ? 5 : 1;
                  var focusDate = module.get.focusDate() || module.get.date() || new Date();
                  var year = focusDate.getFullYear() + (mode === 'year' ? increment : 0);
                  var month = focusDate.getMonth() + (mode === 'month' ? increment : 0);
                  var day = focusDate.getDate() + (mode === 'day' ? increment : 0);
                  var hour = focusDate.getHours() + (mode === 'hour' ? increment : 0);
                  var minute = focusDate.getMinutes() + (mode === 'minute' ? increment : 0);
                  var newFocusDate = new Date(year, month, day, hour, minute);
                  if (settings.type === 'time') {
                    newFocusDate = module.helper.mergeDateTime(focusDate, newFocusDate);
                  }
                  if (module.helper.isDateInRange(newFocusDate, mode)) {
                    module.set.focusDate(newFocusDate);
                  }
                } else if (event.keyCode === 13) {
                  //enter
                  var mode = module.get.mode();
                  var date = module.get.focusDate();
                  if (date && !settings.isDisabled(date, mode)) {
                    module.selectDate(date);
                  }
                }
              }

              if (event.keyCode === 38 || event.keyCode === 40) {
                //arrow-up || arrow-down
                event.preventDefault(); //don't scroll
                module.popup('show');
              }
            },
            inputChange: function () {
              var val = $input.val();
              var date = parser.date(val, settings);
              module.set.date(date, false);
            },
            inputFocus: function () {
              $container.addClass(className.active);
            },
            inputBlur: function () {
              $container.removeClass(className.active);
              if (settings.formatInput) {
                var date = module.get.date();
                var text = formatter.datetime(date, settings);
                $input.val(text);
              }
            },
            inputClick: function () {
              module.popup('show');
            }
          },

          get: {
            date: function () {
              return $module.data(metadata.date) || null;
            },
            focusDate: function () {
              return $module.data(metadata.focusDate) || null;
            },
            startDate: function () {
              var startModule = module.get.calendarModule(settings.startCalendar);
              return (startModule ? startModule.get.date() : $module.data(metadata.startDate)) || null;
            },
            endDate: function () {
              var endModule = module.get.calendarModule(settings.endCalendar);
              return (endModule ? endModule.get.date() : $module.data(metadata.endDate)) || null;
            },
            monthOffset: function () {
              return $module.data(metadata.monthOffset) || 0;
            },
            mode: function () {
              //only returns valid modes for the current settings
              var mode = $module.data(metadata.mode) || settings.startMode;
              var validModes = module.get.validModes();
              if ($.inArray(mode, validModes) >= 0) {
                return mode;
              }
              return settings.type === 'time' ? 'hour' :
                settings.type === 'month' ? 'month' :
                  settings.type === 'year' ? 'year' : 'day';
            },
            validModes: function () {
              var validModes = [];
              if (settings.type !== 'time') {
                if (!settings.disableYear || settings.type === 'year') {
                  validModes.push('year');
                }
                if (!(settings.disableMonth || settings.type === 'year') || settings.type === 'month') {
                  validModes.push('month');
                }
                if (settings.type.indexOf('date') >= 0) {
                  validModes.push('day');
                }
              }
              if (settings.type.indexOf('time') >= 0) {
                validModes.push('hour');
                if (!settings.disableMinute) {
                  validModes.push('minute');
                }
              }
              return validModes;
            },
            isTouch: function () {
              try {
                document.createEvent('TouchEvent');
                return true;
              }
              catch (e) {
                return false;
              }
            },
            calendarModule: function (selector) {
              if (!selector) {
                return null;
              }
              if (!(selector instanceof $)) {
                selector = $module.parent().children(selector).first();
              }
              //assume range related calendars are using the same namespace
              return selector.data(moduleNamespace);
            }
          },

          set: {
            date: function (date, updateInput, fireChange) {
              updateInput = updateInput !== false;
              fireChange = fireChange !== false;
              date = module.helper.sanitiseDate(date);
              date = module.helper.dateInRange(date);

              var text = formatter.datetime(date, settings);
              if (fireChange && settings.onChange.call(element, date, text) === false) {
                return false;
              }

              module.set.focusDate(date);

              var mode = module.get.mode();
              if (settings.isDisabled(date, mode)) {
                return false;
              }

              var endDate = module.get.endDate();
              if (!!endDate && !!date && date > endDate) {
                //selected date is greater than end date in range, so clear end date
                module.set.endDate(undefined);
              }
              module.set.dataKeyValue(metadata.date, date);

              if (updateInput && $input.length) {
                $input.val(text);
              }
            },
            startDate: function (date, refreshCalendar) {
              date = module.helper.sanitiseDate(date);
              var startModule = module.get.calendarModule(settings.startCalendar);
              if (startModule) {
                startModule.set.date(date);
              }
              module.set.dataKeyValue(metadata.startDate, date, refreshCalendar);
            },
            endDate: function (date, refreshCalendar) {
              date = module.helper.sanitiseDate(date);
              var endModule = module.get.calendarModule(settings.endCalendar);
              if (endModule) {
                endModule.set.date(date);
              }
              module.set.dataKeyValue(metadata.endDate, date, refreshCalendar);
            },
            focusDate: function (date, refreshCalendar, updateFocus, updateRange) {
              date = module.helper.sanitiseDate(date);
              date = module.helper.dateInRange(date);
              var isDay = module.get.mode() === 'day';
              var oldFocusDate = module.get.focusDate();
              if (isDay && date && oldFocusDate) {
                var yearDelta = date.getFullYear() - oldFocusDate.getFullYear();
                var monthDelta = yearDelta * 12 + date.getMonth() - oldFocusDate.getMonth();
                if (monthDelta) {
                  var monthOffset = module.get.monthOffset() - monthDelta;
                  module.set.monthOffset(monthOffset, false);
                }
              }
              var changed = module.set.dataKeyValue(metadata.focusDate, date, refreshCalendar);
              updateFocus = (updateFocus !== false && changed && refreshCalendar === false) || focusDateUsedForRange != updateRange;
              focusDateUsedForRange = updateRange;
              if (updateFocus) {
                module.update.focus(updateRange);
              }
            },
            monthOffset: function (monthOffset, refreshCalendar) {
              var multiMonth = Math.max(settings.multiMonth, 1);
              monthOffset = Math.max(1 - multiMonth, Math.min(0, monthOffset));
              module.set.dataKeyValue(metadata.monthOffset, monthOffset, refreshCalendar);
            },
            mode: function (mode, refreshCalendar) {
              module.set.dataKeyValue(metadata.mode, mode, refreshCalendar);
            },
            dataKeyValue: function (key, value, refreshCalendar) {
              var oldValue = $module.data(key);
              var equal = oldValue === value || (oldValue <= value && oldValue >= value); //equality test for dates and string objects
              if (value) {
                $module.data(key, value);
              } else {
                $module.removeData(key);
              }
              refreshCalendar = refreshCalendar !== false && !equal;
              if (refreshCalendar) {
                module.create.calendar();
              }
              return !equal;
            }
          },

          selectDate: function (date, forceSet) {
            var mode = module.get.mode();
            var complete = forceSet || mode === 'minute' ||
              (settings.disableMinute && mode === 'hour') ||
              (settings.type === 'date' && mode === 'day') ||
              (settings.type === 'month' && mode === 'month') ||
              (settings.type === 'year' && mode === 'year');
            if (complete) {
              var canceled = module.set.date(date) === false;
              if (!canceled && settings.closable) {
                module.popup('hide');
                //if this is a range calendar, show the end date calendar popup and focus the input
                var endModule = module.get.calendarModule(settings.endCalendar);
                if (endModule) {
                  endModule.popup('show');
                  endModule.focus();
                }
              }
            } else {
              var newMode = mode === 'year' ? (!settings.disableMonth ? 'month' : 'day') :
                mode === 'month' ? 'day' : mode === 'day' ? 'hour' : 'minute';
              module.set.mode(newMode);
              if (mode === 'hour' || (mode === 'day' && module.get.date())) {
                //the user has chosen enough to consider a valid date/time has been chosen
                module.set.date(date);
              } else {
                module.set.focusDate(date);
              }
            }
          },

          changeDate: function (date) {
            module.set.date(date);
          },

          clear: function () {
            module.set.date(undefined);
          },

          popup: function () {
            return $activator.popup.apply($activator, arguments);
          },

          focus: function () {
            if ($input.length) {
              $input.focus();
            } else {
              $container.focus();
            }
          },
          blur: function () {
            if ($input.length) {
              $input.blur();
            } else {
              $container.blur();
            }
          },

          helper: {
            sanitiseDate: function (date) {
              if (!date) {
                return undefined;
              }
              if (!(date instanceof Date)) {
                date = parser.date('' + date, settings);
              }
              if (isNaN(date.getTime())) {
                return undefined;
              }
              return date;
            },
            dateDiff: function (date1, date2, mode) {
              mode = mode || 'day';
              var isTimeOnly = settings.type === 'time';
              var isYear = mode === 'year';
              var isYearOrMonth = isYear || mode === 'month';
              var isMinute = mode === 'minute';
              var isHourOrMinute = isMinute || mode === 'hour';
              //only care about a minute accuracy of 5
              date1 = new Date(
                isTimeOnly ? 2000 : date1.getFullYear(),
                isTimeOnly ? 0 : isYear ? 0 : date1.getMonth(),
                isTimeOnly ? 1 : isYearOrMonth ? 1 : date1.getDate(),
                !isHourOrMinute ? 0 : date1.getHours(),
                !isMinute ? 0 : 5 * Math.floor(date1.getMinutes() / 5));
              date2 = new Date(
                isTimeOnly ? 2000 : date2.getFullYear(),
                isTimeOnly ? 0 : isYear ? 0 : date2.getMonth(),
                isTimeOnly ? 1 : isYearOrMonth ? 1 : date2.getDate(),
                !isHourOrMinute ? 0 : date2.getHours(),
                !isMinute ? 0 : 5 * Math.floor(date2.getMinutes() / 5));
              return date2.getTime() - date1.getTime();
            },
            dateEqual: function (date1, date2, mode) {
              return !!date1 && !!date2 && module.helper.dateDiff(date1, date2, mode) === 0;
            },
            isDateInRange: function (date, mode, minDate, maxDate) {
              if (!minDate && !maxDate) {
                var startDate = module.get.startDate();
                minDate = startDate && settings.minDate ? new Date(Math.max(startDate, settings.minDate)) : startDate || settings.minDate;
                maxDate = settings.maxDate;
              }
              minDate = minDate && new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate(), minDate.getHours(), 5 * Math.ceil(minDate.getMinutes() / 5));
              return !(!date ||
              (minDate && module.helper.dateDiff(date, minDate, mode) > 0) ||
              (maxDate && module.helper.dateDiff(maxDate, date, mode) > 0));
            },
            dateInRange: function (date, minDate, maxDate) {
              if (!minDate && !maxDate) {
                var startDate = module.get.startDate();
                minDate = startDate && settings.minDate ? new Date(Math.max(startDate, settings.minDate)) : startDate || settings.minDate;
                maxDate = settings.maxDate;
              }
              minDate = minDate && new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate(), minDate.getHours(), 5 * Math.ceil(minDate.getMinutes() / 5));
              var isTimeOnly = settings.type === 'time';
              return !date ? date :
                (minDate && module.helper.dateDiff(date, minDate, 'minute') > 0) ?
                  (isTimeOnly ? module.helper.mergeDateTime(date, minDate) : minDate) :
                  (maxDate && module.helper.dateDiff(maxDate, date, 'minute') > 0) ?
                    (isTimeOnly ? module.helper.mergeDateTime(date, maxDate) : maxDate) :
                    date;
            },
            mergeDateTime: function (date, time) {
              return (!date || !time) ? time :
                new Date(date.getFullYear(), date.getMonth(), date.getDate(), time.getHours(), time.getMinutes());
            }
          },

          setting: function (name, value) {
            module.debug('Changing setting', name, value);
            if ($.isPlainObject(name)) {
              $.extend(true, settings, name);
            }
            else if (value !== undefined) {
              if ($.isPlainObject(settings[name])) {
                $.extend(true, settings[name], value);
              }
              else {
                settings[name] = value;
              }
            }
            else {
              return settings[name];
            }
          },
          internal: function (name, value) {
            module.debug('Changing internal', name, value);
            if (value !== undefined) {
              if ($.isPlainObject(name)) {
                $.extend(true, module, name);
              }
              else {
                module[name] = value;
              }
            }
            else {
              return module[name];
            }
          },
          debug: function () {
            if (!settings.silent && settings.debug) {
              if (settings.performance) {
                module.performance.log(arguments);
              }
              else {
                module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
                module.debug.apply(console, arguments);
              }
            }
          },
          verbose: function () {
            if (!settings.silent && settings.verbose && settings.debug) {
              if (settings.performance) {
                module.performance.log(arguments);
              }
              else {
                module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
                module.verbose.apply(console, arguments);
              }
            }
          },
          error: function () {
            if (!settings.silent) {
              module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
              module.error.apply(console, arguments);
            }
          },
          performance: {
            log: function (message) {
              var
                currentTime,
                executionTime,
                previousTime
                ;
              if (settings.performance) {
                currentTime = new Date().getTime();
                previousTime = time || currentTime;
                executionTime = currentTime - previousTime;
                time = currentTime;
                performance.push({
                  'Name': message[0],
                  'Arguments': [].slice.call(message, 1) || '',
                  'Element': element,
                  'Execution Time': executionTime
                });
              }
              clearTimeout(module.performance.timer);
              module.performance.timer = setTimeout(module.performance.display, 500);
            },
            display: function () {
              var
                title = settings.name + ':',
                totalTime = 0
                ;
              time = false;
              clearTimeout(module.performance.timer);
              $.each(performance, function (index, data) {
                totalTime += data['Execution Time'];
              });
              title += ' ' + totalTime + 'ms';
              if (moduleSelector) {
                title += ' \'' + moduleSelector + '\'';
              }
              if ((console.group !== undefined || console.table !== undefined) && performance.length > 0) {
                console.groupCollapsed(title);
                if (console.table) {
                  console.table(performance);
                }
                else {
                  $.each(performance, function (index, data) {
                    console.log(data['Name'] + ': ' + data['Execution Time'] + 'ms');
                  });
                }
                console.groupEnd();
              }
              performance = [];
            }
          },
          invoke: function (query, passedArguments, context) {
            var
              object = instance,
              maxDepth,
              found,
              response
              ;
            passedArguments = passedArguments || queryArguments;
            context = element || context;
            if (typeof query == 'string' && object !== undefined) {
              query = query.split(/[\. ]/);
              maxDepth = query.length - 1;
              $.each(query, function (depth, value) {
                var camelCaseValue = (depth != maxDepth)
                    ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
                    : query
                  ;
                if ($.isPlainObject(object[camelCaseValue]) && (depth != maxDepth)) {
                  object = object[camelCaseValue];
                }
                else if (object[camelCaseValue] !== undefined) {
                  found = object[camelCaseValue];
                  return false;
                }
                else if ($.isPlainObject(object[value]) && (depth != maxDepth)) {
                  object = object[value];
                }
                else if (object[value] !== undefined) {
                  found = object[value];
                  return false;
                }
                else {
                  module.error(error.method, query);
                  return false;
                }
              });
            }
            if ($.isFunction(found)) {
              response = found.apply(context, passedArguments);
            }
            else if (found !== undefined) {
              response = found;
            }
            if ($.isArray(returnedValue)) {
              returnedValue.push(response);
            }
            else if (returnedValue !== undefined) {
              returnedValue = [returnedValue, response];
            }
            else if (response !== undefined) {
              returnedValue = response;
            }
            return found;
          }
        };

        if (methodInvoked) {
          if (instance === undefined) {
            module.initialize();
          }
          module.invoke(query);
        }
        else {
          if (instance !== undefined) {
            instance.invoke('destroy');
          }
          module.initialize();
        }
      })
    ;
    return (returnedValue !== undefined)
      ? returnedValue
      : $allModules
      ;
  };

  $.fn.calendar.settings = {

    name: 'Calendar',
    namespace: 'calendar',

    silent: false,
    debug: false,
    verbose: false,
    performance: false,

    type: 'datetime',     // picker type, can be 'datetime', 'date', 'time', 'month', or 'year'
    firstDayOfWeek: 0,    // day for first day column (0 = Sunday)
    constantHeight: true, // add rows to shorter months to keep day calendar height consistent (6 rows)
    today: false,         // show a 'today/now' button at the bottom of the calendar
    closable: true,       // close the popup after selecting a date/time
    monthFirst: true,     // month before day when parsing/converting date from/to text
    touchReadonly: true,  // set input to readonly on touch devices
    inline: false,        // create the calendar inline instead of inside a popup
    on: null,             // when to show the popup (defaults to 'focus' for input, 'click' for others)
    initialDate: null,    // date to display initially when no date is selected (null = now)
    startMode: false,     // display mode to start in, can be 'year', 'month', 'day', 'hour', 'minute' (false = 'day')
    minDate: null,        // minimum date/time that can be selected, dates/times before are disabled
    maxDate: null,        // maximum date/time that can be selected, dates/times after are disabled
    ampm: true,           // show am/pm in time mode
    disableYear: false,   // disable year selection mode
    disableMonth: false,  // disable month selection mode
    disableMinute: false, // disable minute selection mode
    formatInput: true,    // format the input text upon input blur and module creation
    startCalendar: null,  // jquery object or selector for another calendar that represents the start date of a date range
    endCalendar: null,    // jquery object or selector for another calendar that represents the end date of a date range
    multiMonth: 1,        // show multiple months when in 'day' mode

    // popup options ('popup', 'on', 'hoverable', and show/hide callbacks are overridden)
    popupOptions: {
      position: 'bottom left',
      lastResort: 'bottom left',
      prefer: 'opposite',
      hideOnScroll: false
    },

    text: {
      days: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
      months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
      monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
      today: 'Today',
      now: 'Now',
      am: 'AM',
      pm: 'PM'
    },

    formatter: {
      header: function (date, mode, settings) {
        return mode === 'year' ? settings.formatter.yearHeader(date, settings) :
          mode === 'month' ? settings.formatter.monthHeader(date, settings) :
            mode === 'day' ? settings.formatter.dayHeader(date, settings) :
              mode === 'hour' ? settings.formatter.hourHeader(date, settings) :
                settings.formatter.minuteHeader(date, settings);
      },
      yearHeader: function (date, settings) {
        var decadeYear = Math.ceil(date.getFullYear() / 10) * 10;
        return (decadeYear - 9) + ' - ' + (decadeYear + 2);
      },
      monthHeader: function (date, settings) {
        return date.getFullYear();
      },
      dayHeader: function (date, settings) {
        var month = settings.text.months[date.getMonth()];
        var year = date.getFullYear();
        return month + ' ' + year;
      },
      hourHeader: function (date, settings) {
        return settings.formatter.date(date, settings);
      },
      minuteHeader: function (date, settings) {
        return settings.formatter.date(date, settings);
      },
      dayColumnHeader: function (day, settings) {
        return settings.text.days[day];
      },
      datetime: function (date, settings) {
        if (!date) {
          return '';
        }
        var day = settings.type === 'time' ? '' : settings.formatter.date(date, settings);
        var time = settings.type.indexOf('time') < 0 ? '' : settings.formatter.time(date, settings, false);
        var separator = settings.type === 'datetime' ? ' ' : '';
        return day + separator + time;
      },
      date: function (date, settings) {
        if (!date) {
          return '';
        }
        var day = date.getDate();
        var month = settings.text.months[date.getMonth()];
        var year = date.getFullYear();
        return settings.type === 'year' ? year :
          settings.type === 'month' ? month + ' ' + year :
          (settings.monthFirst ? month + ' ' + day : day + ' ' + month) + ', ' + year;
      },
      time: function (date, settings, forCalendar) {
        if (!date) {
          return '';
        }
        var hour = date.getHours();
        var minute = date.getMinutes();
        var ampm = '';
        if (settings.ampm) {
          ampm = ' ' + (hour < 12 ? settings.text.am : settings.text.pm);
          hour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
        }
        return hour + ':' + (minute < 10 ? '0' : '') + minute + ampm;
      },
      today: function (settings) {
        return settings.type === 'date' ? settings.text.today : settings.text.now;
      }
    },

    parser: {
      date: function (text, settings) {
        if (!text) {
          return null;
        }
        text = ('' + text).trim().toLowerCase();
        if (text.length === 0) {
          return null;
        }

        var i, j, k;
        var minute = -1, hour = -1, day = -1, month = -1, year = -1;
        var isAm = undefined;

        var isTimeOnly = settings.type === 'time';
        var isDateOnly = settings.type.indexOf('time') < 0;

        var words = text.split(settings.regExp.dateWords);
        var numbers = text.split(settings.regExp.dateNumbers);

        if (!isDateOnly) {
          //am/pm
          isAm = $.inArray(settings.text.am.toLowerCase(), words) >= 0 ? true :
            $.inArray(settings.text.pm.toLowerCase(), words) >= 0 ? false : undefined;

          //time with ':'
          for (i = 0; i < numbers.length; i++) {
            var number = numbers[i];
            if (number.indexOf(':') >= 0) {
              if (hour < 0 || minute < 0) {
                var parts = number.split(':');
                for (k = 0; k < Math.min(2, parts.length); k++) {
                  j = parseInt(parts[k]);
                  if (isNaN(j)) {
                    j = 0;
                  }
                  if (k === 0) {
                    hour = j % 24;
                  } else {
                    minute = j % 60;
                  }
                }
              }
              numbers.splice(i, 1);
            }
          }
        }

        if (!isTimeOnly) {
          //textual month
          for (i = 0; i < words.length; i++) {
            var word = words[i];
            if (word.length <= 0) {
              continue;
            }
            word = word.substring(0, Math.min(word.length, 3));
            for (j = 0; j < settings.text.months.length; j++) {
              var monthString = settings.text.months[j];
              monthString = monthString.substring(0, Math.min(word.length, Math.min(monthString.length, 3))).toLowerCase();
              if (monthString === word) {
                month = j + 1;
                break;
              }
            }
            if (month >= 0) {
              break;
            }
          }

          //year > 59
          for (i = 0; i < numbers.length; i++) {
            j = parseInt(numbers[i]);
            if (isNaN(j)) {
              continue;
            }
            if (j > 59) {
              year = j;
              numbers.splice(i, 1);
              break;
            }
          }

          //numeric month
          if (month < 0) {
            for (i = 0; i < numbers.length; i++) {
              k = i > 1 || settings.monthFirst ? i : i === 1 ? 0 : 1;
              j = parseInt(numbers[k]);
              if (isNaN(j)) {
                continue;
              }
              if (1 <= j && j <= 12) {
                month = j;
                numbers.splice(k, 1);
                break;
              }
            }
          }

          //day
          for (i = 0; i < numbers.length; i++) {
            j = parseInt(numbers[i]);
            if (isNaN(j)) {
              continue;
            }
            if (1 <= j && j <= 31) {
              day = j;
              numbers.splice(i, 1);
              break;
            }
          }

          //year <= 59
          if (year < 0) {
            for (i = numbers.length - 1; i >= 0; i--) {
              j = parseInt(numbers[i]);
              if (isNaN(j)) {
                continue;
              }
              if (j < 99) {
                j += 2000;
              }
              year = j;
              numbers.splice(i, 1);
              break;
            }
          }
        }

        if (!isDateOnly) {
          //hour
          if (hour < 0) {
            for (i = 0; i < numbers.length; i++) {
              j = parseInt(numbers[i]);
              if (isNaN(j)) {
                continue;
              }
              if (0 <= j && j <= 23) {
                hour = j;
                numbers.splice(i, 1);
                break;
              }
            }
          }

          //minute
          if (minute < 0) {
            for (i = 0; i < numbers.length; i++) {
              j = parseInt(numbers[i]);
              if (isNaN(j)) {
                continue;
              }
              if (0 <= j && j <= 59) {
                minute = j;
                numbers.splice(i, 1);
                break;
              }
            }
          }
        }

        if (minute < 0 && hour < 0 && day < 0 && month < 0 && year < 0) {
          return null;
        }

        if (minute < 0) {
          minute = 0;
        }
        if (hour < 0) {
          hour = 0;
        }
        if (day < 0) {
          day = 1;
        }
        if (month < 0) {
          month = 1;
        }
        if (year < 0) {
          year = new Date().getFullYear();
        }

        if (isAm !== undefined) {
          if (isAm) {
            if (hour === 12) {
              hour = 0;
            }
          } else if (hour < 12) {
            hour += 12;
          }
        }

        var date = new Date(year, month - 1, day, hour, minute);
        if (date.getMonth() !== month - 1 || date.getFullYear() !== year) {
          //month or year don't match up, switch to last day of the month
          date = new Date(year, month, 0, hour, minute);
        }
        return isNaN(date.getTime()) ? null : date;
      }
    },

    // callback when date changes, return false to cancel the change
    onChange: function (date, text) {
      return true;
    },

    // callback before show animation, return false to prevent show
    onShow: function () {
    },

    // callback after show animation
    onVisible: function () {
    },

    // callback before hide animation, return false to prevent hide
    onHide: function () {
    },

    // callback after hide animation
    onHidden: function () {
    },

    // is the given date disabled?
    isDisabled: function (date, mode) {
      return false;
    },

    selector: {
      popup: '.ui.popup',
      input: 'input',
      activator: 'input'
    },

    regExp: {
      dateWords: /[^A-Za-z\u00C0-\u024F]+/g,
      dateNumbers: /[^\d:]+/g
    },

    error: {
      popup: 'UI Popup, a required component is not included in this page',
      method: 'The method you called is not defined.'
    },

    className: {
      calendar: 'calendar',
      active: 'active',
      popup: 'ui popup',
      grid: 'ui equal width grid',
      column: 'column',
      table: 'ui celled center aligned unstackable table',
      prev: 'prev link',
      next: 'next link',
      prevIcon: 'chevron left icon',
      nextIcon: 'chevron right icon',
      link: 'link',
      cell: 'link',
      disabledCell: 'disabled',
      adjacentCell: 'adjacent',
      activeCell: 'active',
      rangeCell: 'range',
      focusCell: 'focus',
      todayCell: 'today',
      today: 'today link'
    },

    metadata: {
      date: 'date',
      focusDate: 'focusDate',
      startDate: 'startDate',
      endDate: 'endDate',
      mode: 'mode',
      monthOffset: 'monthOffset'
    }
  };

})(jQuery, window, document);
import { Component, 
         Inject,
         ViewChild, 
         AfterViewInit, 
         Input,
         ElementRef,
         forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

import { CalendarOptions } from './shared/calendar.options';

@Component({
  selector: 'sui-calendar',
  template: `
    <div class="ui calendar" #calendar>
      <div class="ui input left icon">
        <i class="calendar icon"></i>
        <input (change)=useGermandDateTabCompletion($event)
               type="text"
               placeholder="Date/Time">
      </div>
    </div>
  `,
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CalendarComponent),
    multi: true
  }]
})
export class CalendarComponent implements AfterViewInit, ControlValueAccessor {
@ViewChild('calendar') componentRoot: ElementRef;
  @Input() placeholder: string;
  @Input() type: string;
  @Input() minDate: Date;
  @Input() maxDate: Date;
  
  $control;
  customOptions;
  
  propagateDate = (date: Date) => { };
  
  get options() {
    return Object.assign(
      {},
      this.customOptions,
      { type: this.type },
      { minDate: this.minDate },
      { maxDate: this.maxDate },
      { onChange: (date) => this.emit(date) });
  }

  constructor(@Inject(CalendarOptions) options) {
    this.customOptions = options;
  }
  
  ngAfterViewInit() {
    this.useSemanticUiCalendar();
  }
  
  useSemanticUiCalendar() {
    this.$control = $(this.componentRoot.nativeElement).calendar(this.options);
  }
  
  useGermandDateTabCompletion(evr) {
    if (this.options.type !== 'date') { return; }
    
    const parsePattern = /^(\d{2})(\d{2})(\d{2}|\d{4})$/;
    let matches = parsePattern.exec(evr.target.value);
    
    if(!matches && matches.length !== 5) { return; }

    let [, day, month, year] = matches;
    console.log(day, month, year);
    this.$control.calendar('set date', new Date(year, month-1, day));
    this.propagateChange(this.$control.calendar('get date'));
  }

  emit(date: Date) {
    if (!date || typeof date.getMonth !== 'function') { return; }
    
    if (this.options.type === 'date') {
      date = date.setHours(0, 0, 0, 0);
    }
    
    this.propagateChange(new Date(date));
  }
  
  writeValue(dateTime: Date) {
    if(!dateTime || !this.$control) { return; }
    
    this.$control.calendar('set date', dateTime, true, false);
  }
  
  registerOnChange(fn: any) {
    this.propagateChange = fn;
  }
  
  registerOnTouched(fn: any) { }
}
export class CalendarOptions {
  type: string;
  today: boolean;
  placeholder: string;
  formatter: any;
  text: any;
  ampm: boolean;
}
import { OpaqueToken } from '@angular/core';
import { CalendarOptions } from './calendar.options';

export class GermanCalendarOptions extends CalendarOptions {
  type = 'datetime';
  today = 'true';
  placeholder = 'Datum';
  firstDayOfWeek = 1;
  ampm = false;
  text = {
		days: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
		months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
		monthsShort: ['Jan', 'Feb', 'März', 'April', 'Mai', 'Juni', 'Juli', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
		today: 'Heute',
		now: 'Jetzt',
	};
  formatter = {
    date: germanDate
  };
}

function germanDate(date: Date, settings: any) {
  let today = date.getDate();
  let month = date.getMonth() + 1;
  let year = date.getFullYear();
  
  if (today < 10)
    today = `0${today}`;
    
  if (month < 10)
    month = `0${month}`;
  
  return `${today}.${month}.${year}`;
}