OTRS API Reference JavaScript

Source: Core.Agent.AppointmentCalendar.js

// --
// Copyright (C) 2001-2020 OTRS AG, https://otrs.com/
// --
// This software comes with ABSOLUTELY NO WARRANTY. For details, see
// the enclosed file COPYING for license information (GPL). If you
// did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
// --

/*global Clipboard */

"use strict";

var Core = Core || {};
Core.Agent = Core.Agent || {};

/**
 * @namespace Core.Agent.AppointmentCalendar
 * @memberof Core.Agent
 * @author OTRS AG
 * @description
 *      This namespace contains the appointment calendar functions.
 */
Core.Agent.AppointmentCalendar = (function (TargetNS) {

    // Appointment days cache and ready flag
    var AppointmentDaysCache,
        AppointmentDaysCacheRefreshed = false,
        AJAXCounter = 0,
        CurrentView,
        CalendarSources = {};

    /**
     * @name Init
     * @memberof Core.Agent.AppointmentCalendar
     * @description
     *      Initializes the appointment calendar control.
     */
    TargetNS.Init = function () {
        var CalendarConfig = Core.Config.Get('CalendarConfig'),
            ResourceConfig,
            $CalendarObj = $('#calendar'),
            $DatepickerObj,
            ClipboardJS,
            CurrentAppointment = [];

        if (Core.Config.Get('Action') == 'AgentAppointmentAgendaOverview') {
            TargetNS.AgentAppointmentAgendaOverview();
            return;
        }

        if (!CalendarConfig) {
            return;
        }

        $DatepickerObj = $('<div />')
            .prop('id', 'Datepicker')
            .addClass('Hidden')
            .insertAfter($CalendarObj),

        ClipboardJS = new Clipboard('.CopyToClipboard');

        if (Core.Config.Get('TeamID')) {
            ResourceConfig = {
                url: Core.Config.Get('CGIHandle'),
                type: 'POST',
                data: {
                    ChallengeToken: $('#ChallengeToken').val(),
                    Action: 'AgentAppointmentTeamList',
                    Subaction: 'ListResources',
                    TeamID: Core.Config.Get('TeamID')
                }
            };
        }

        // Initialize calendar
        $CalendarObj.fullCalendar({
            header: {
                left: 'month,agendaWeek,agendaDay timelineMonth,timelineWeek,timelineDay',
                center: 'title',
                right: 'jump,today prev,next'
            },
            customButtons: {
                jump: {
                    text: Core.Language.Translate('Jump'),
                    click: function() {
                        ShowDatepicker($CalendarObj, $DatepickerObj, $(this));
                    }
                }
            },
            defaultView: Core.Config.Get('DefaultView'),
            allDayText: Core.Language.Translate('All-day'),
            isRTL: Core.Config.Get('IsRTLLanguage'),
            columnFormat: 'ddd, D MMM',
            timeFormat: 'HH:mm',
            slotLabelFormat: 'HH:mm',
            titleFormat: 'D MMM YYYY',
            weekNumbers: true,
            weekNumberTitle: '#',
            weekNumberCalculation: 'ISO',
            eventLimit: true,
            eventLimitText: Core.Language.Translate('more'),
            height: 600,
            editable: true,
            selectable: true,
            firstDay: Core.Config.Get('CalendarWeekDayStart'),
            monthNames: [
                Core.Language.Translate('January'),
                Core.Language.Translate('February'),
                Core.Language.Translate('March'),
                Core.Language.Translate('April'),
                Core.Language.Translate('May_long'),
                Core.Language.Translate('June'),
                Core.Language.Translate('July'),
                Core.Language.Translate('August'),
                Core.Language.Translate('September'),
                Core.Language.Translate('October'),
                Core.Language.Translate('November'),
                Core.Language.Translate('December')
            ],
            monthNamesShort: [
                Core.Language.Translate('Jan'),
                Core.Language.Translate('Feb'),
                Core.Language.Translate('Mar'),
                Core.Language.Translate('Apr'),
                Core.Language.Translate('May'),
                Core.Language.Translate('Jun'),
                Core.Language.Translate('Jul'),
                Core.Language.Translate('Aug'),
                Core.Language.Translate('Sep'),
                Core.Language.Translate('Oct'),
                Core.Language.Translate('Nov'),
                Core.Language.Translate('Dec')
            ],
            dayNames: [
                Core.Language.Translate('Sunday'),
                Core.Language.Translate('Monday'),
                Core.Language.Translate('Tuesday'),
                Core.Language.Translate('Wednesday'),
                Core.Language.Translate('Thursday'),
                Core.Language.Translate('Friday'),
                Core.Language.Translate('Saturday')
            ],
            dayNamesShort: [
                Core.Language.Translate('Sun'),
                Core.Language.Translate('Mon'),
                Core.Language.Translate('Tue'),
                Core.Language.Translate('Wed'),
                Core.Language.Translate('Thu'),
                Core.Language.Translate('Fri'),
                Core.Language.Translate('Sat')
            ],
            buttonText: {
                today: Core.Language.Translate('Today'),
                month: Core.Language.Translate('Month'),
                week: Core.Language.Translate('Week'),
                day: Core.Language.Translate('Day'),
                timelineMonth: Core.Language.Translate('Timeline Month'),
                timelineWeek: Core.Language.Translate('Timeline Week'),
                timelineDay: Core.Language.Translate('Timeline Day'),
                jump: Core.Language.Translate('Jump'),
                prevDatepicker: Core.Language.Translate('Previous'),
                nextDatepicker: Core.Language.Translate('Next')
            },
            schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source',
            slotDuration: '00:30:00',
            forceEventDuration: true,
            nowIndicator: true,
            timezone: 'local',
            resourceAreaWidth: '21%',
            views: {
                month: {
                    titleFormat: 'MMMM YYYY',
                    columnFormat: 'dddd'
                },
                agendaDay: {
                    titleFormat: 'D MMM YYYY',
                    resources: false
                },
                timelineMonth: {
                    slotDuration: '24:00:00',
                    duration: {
                        months: 1
                    },
                    slotLabelFormat: [
                        'D'
                    ]
                },
                timelineWeek: {
                    slotDuration: '02:00:00',
                    duration: {
                        week: 1
                    },
                    slotLabelFormat: [
                        'ddd, D MMM',
                        'HH'
                    ]
                },
                timelineDay: {
                    slotDuration: '00:30:00',
                    duration: {
                        days: 1
                    },
                    slotLabelFormat: [
                        'ddd, D MMM',
                        'HH:mm'
                    ]
                }
            },
            loading: function(IsLoading) {
                if (IsLoading) {
                    $('.CalendarWidget').addClass('Loading');
                } else {
                    $('.CalendarWidget').removeClass('Loading');
                }
            },
            viewRender: function(View) {

                // Check if we are on a timeline view.
                if (View.name === 'timelineWeek' || View.name === 'timelineDay') {

                    // Add calendar week number to timeline view titles.
                    window.setTimeout(function () {
                        $CalendarObj.find('.fc-toolbar > div > h2').append(
                            $('<span />').addClass('fc-week-number')
                                .text(View.start.format(' #W'))
                        );
                    }, 0);

                    // Initialize restore settings button.
                    RestoreDefaultSettingsInit();
                }

                // Remember view selection
                if (CurrentView !== undefined && CurrentView !== View.name) {
                    Core.AJAX.FunctionCall(
                        Core.Config.Get('CGIHandle'),
                        {
                            ChallengeToken: $('#ChallengeToken').val(),
                            Action: 'AgentAppointmentEdit',
                            Subaction: 'UpdatePreferences',
                            OverviewScreen: Core.Config.Get('OverviewScreen') ? Core.Config.Get('OverviewScreen') : 'CalendarOverview',
                            DefaultView: View.name
                        },
                        function (Response) {
                            if (!Response.Success) {
                                Core.Debug.Log('Error updating user preferences!');
                            }
                        }
                    );
                }
                CurrentView = View.name;
            },
            select: function(Start, End, JSEvent, View, Resource) {
                var Data = {
                    Start: Start,
                    End: End,
                    JSEvent: JSEvent,
                    View: View,
                    Resource: Resource
                };
                TargetNS.OpenEditDialog(Data);
                $CalendarObj.fullCalendar('unselect');
            },
            eventClick: function(CalEvent, JSEvent) {
                var Data = {
                    Start: CalEvent.start,
                    End: CalEvent.end,
                    CalEvent: CalEvent
                };
                TargetNS.OpenEditDialog(Data);
                JSEvent.stopPropagation();
                return false;
            },
            eventDrop: function(CalEvent, Delta, RevertFunc) {
                var Data = {
                    CalEvent: CalEvent,
                    PreviousAppointment: CurrentAppointment,
                    Delta: Delta,
                    RevertFunc: RevertFunc
                };
                UpdateAppointment(Data);
            },
            eventResize: function(CalEvent, Delta, RevertFunc) {
                var Data = {
                    CalEvent: CalEvent,
                    PreviousAppointment: CurrentAppointment,
                    Delta: Delta,
                    RevertFunc: RevertFunc
                };
                UpdateAppointment(Data);
            },
            eventRender: function(CalEvent, $Element) {
                var $IconContainer,
                    $Icon;

                if (CalEvent.allDay
                    || CalEvent.recurring
                    || CalEvent.parentId
                    || CalEvent.notification
                    || CalEvent.ticketAppointmentType) {

                    // Create container and icon element
                    $IconContainer = $('<div />').addClass('Icons');
                    $Icon = $('<i />').addClass('fa');

                    // Mark appointment with appropriate icon(s)
                    if (CalEvent.allDay) {
                        $Icon.clone()
                            .addClass('fa-sun-o')
                            .appendTo($IconContainer);
                    }
                    if (CalEvent.recurring) {
                        $Icon.clone()
                            .addClass('fa-repeat')
                            .appendTo($IconContainer);
                    }
                    if (CalEvent.parentId) {
                        $Icon.clone()
                            .addClass('fa-link')
                            .appendTo($IconContainer);
                    }
                    if (CalEvent.notification) {
                        $Icon.clone()
                            .addClass('fa-bell')
                            .appendTo($IconContainer);
                    }
                    if (CalEvent.ticketAppointmentType) {
                        $Icon.clone()
                            .addClass('fa-char-' + Core.Config.Get('TicketAppointmentConfig')[CalEvent.ticketAppointmentType].Mark)
                            .appendTo($IconContainer);
                    }

                    // Prepend container to the appointment
                    $Element.find('.fc-content')
                        .prepend($IconContainer);
                }
            },
            eventResizeStart: function(CalEvent) {
                CurrentAppointment.start = CalEvent.start;
                CurrentAppointment.end = CalEvent.end;
            },
            eventDragStart: function(CalEvent) {
                CurrentAppointment.start = CalEvent.start;
                CurrentAppointment.end = CalEvent.end;
                CurrentAppointment.resourceIds = CalEvent.resourceIds;
            },
            eventMouseover: function(CalEvent, JSEvent) {
                var $TooltipObj,
                    PosX = 0,
                    PosY = 0,
                    TooltipHTML = Core.Template.Render('Agent/AppointmentCalendar/AppointmentTooltip', {
                        'PluginList': Core.Config.Get('PluginList'),
                        'CalEvent': CalEvent,
                        'TooltipTemplateResource': Core.Config.Get('TooltipTemplateResource') || 0,
                        'TicketAppointmentConfig': Core.Config.Get('TicketAppointmentConfig')
                    }),
                    DocumentVisibleLeft = $(document).scrollLeft() + $(window).width(),
                    DocumentVisibleTop = $(document).scrollTop() + $(window).height(),
                    LastXPosition,
                    LastYPosition;

                if (!JSEvent) {
                    JSEvent = window.event;
                }
                if (JSEvent.pageX || JSEvent.pageY) {
                    PosX = JSEvent.pageX;
                    PosY = JSEvent.pageY;
                } else if (JSEvent.clientX || JSEvent.clientY) {
                    PosX = JSEvent.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
                    PosY = JSEvent.clientY + document.body.scrollTop + document.documentElement.scrollTop;
                }

                // Increase positions so the tooltip do not overlap with mouse pointer
                PosX += 10;
                PosY += 10;

                if (TooltipHTML.length > 0) {

                    // Create tooltip object
                    $TooltipObj = $(TooltipHTML)
                        .offset({
                            top: PosY,
                            left: PosX
                        })
                        .appendTo('body');

                    // Re-calculate top position if needed
                    LastYPosition = PosY + $TooltipObj.height();
                    if (LastYPosition > DocumentVisibleTop) {
                        PosY = PosY - $TooltipObj.height();
                        $TooltipObj.css('top', PosY + 'px');
                    }

                    // Re-calculate left position if needed
                    LastXPosition = PosX + $TooltipObj.width();
                    if (LastXPosition > DocumentVisibleLeft) {
                        PosX = PosX - $TooltipObj.width() - 30;
                        $TooltipObj.css('left', PosX + 'px');
                    }

                    // Show the tooltip
                    $TooltipObj.fadeIn('fast');
                }
            },
            eventMouseout: function() {
                $('.AppointmentTooltip').fadeOut("fast").remove();
            },
            eventAfterAllRender: function () {

                // If the first day in timelineMonth view is not Saturday or Sunday, the first div.fc-bgevent should be removed.
                // There is a bug from fullcalendar and it can be solved here. See bug#14764.
                if (!$('.fc-timelineMonth-view .fc-slats td.fc-widget-content:eq(0)').hasClass('fc-sat')
                    && !$('.fc-timelineMonth-view .fc-slats td.fc-widget-content:eq(0)').hasClass('fc-sun')) {
                    $('.fc-timelineMonth-view .fc-bgevent-container .fc-bgevent:eq(0)').remove();
                }
            },
            events: Core.Config.Get('WorkingHoursConfig'),
            resources: ResourceConfig,
            resourceColumns: [
                {
                    labelText: Core.Language.Translate('Name'),
                    field: 'title'
                }
            ],
            resourceLabelText: Core.Language.Translate('Resources')
        });

        // Initialize datepicker
        $DatepickerObj.datepicker({
            showOn: 'button',
            buttonText: Core.Language.Translate('Jump'),
            constrainInput: true,
            prevText: Core.Language.Translate('Previous'),
            nextText: Core.Language.Translate('Next'),
            firstDay: Core.Config.Get('CalendarWeekDayStart'),
            showMonthAfterYear: 0,
            monthNames: [
                Core.Language.Translate('January'),
                Core.Language.Translate('February'),
                Core.Language.Translate('March'),
                Core.Language.Translate('April'),
                Core.Language.Translate('May_long'),
                Core.Language.Translate('June'),
                Core.Language.Translate('July'),
                Core.Language.Translate('August'),
                Core.Language.Translate('September'),
                Core.Language.Translate('October'),
                Core.Language.Translate('November'),
                Core.Language.Translate('December')
            ],
            monthNamesShort: [
                Core.Language.Translate('Jan'),
                Core.Language.Translate('Feb'),
                Core.Language.Translate('Mar'),
                Core.Language.Translate('Apr'),
                Core.Language.Translate('May'),
                Core.Language.Translate('Jun'),
                Core.Language.Translate('Jul'),
                Core.Language.Translate('Aug'),
                Core.Language.Translate('Sep'),
                Core.Language.Translate('Oct'),
                Core.Language.Translate('Nov'),
                Core.Language.Translate('Dec')
            ],
            dayNames: [
                Core.Language.Translate('Sunday'),
                Core.Language.Translate('Monday'),
                Core.Language.Translate('Tuesday'),
                Core.Language.Translate('Wednesday'),
                Core.Language.Translate('Thursday'),
                Core.Language.Translate('Friday'),
                Core.Language.Translate('Saturday')
            ],
            dayNamesShort: [
                Core.Language.Translate('Sun'),
                Core.Language.Translate('Mon'),
                Core.Language.Translate('Tue'),
                Core.Language.Translate('Wed'),
                Core.Language.Translate('Thu'),
                Core.Language.Translate('Fri'),
                Core.Language.Translate('Sat')
            ],
            dayNamesMin: [
                Core.Language.Translate('Su'),
                Core.Language.Translate('Mo'),
                Core.Language.Translate('Tu'),
                Core.Language.Translate('We'),
                Core.Language.Translate('Th'),
                Core.Language.Translate('Fr'),
                Core.Language.Translate('Sa')
            ],
            isRTL: Core.Config.Get('IsRTLLanguage'),
            onSelect: function(DateText) {
                $CalendarObj.fullCalendar('gotoDate', new Date(DateText));
                $('#DatepickerOverlay').remove();
                $DatepickerObj.hide();
            },
            beforeShowDay: function(DateObject) {
                if (AppointmentDaysCacheRefreshed) {
                    return CheckDate(DateObject);
                } else {
                    return [true];
                }
            },
            onChangeMonthYear: function(Year, Month) {
                AppointmentDays($DatepickerObj, Year, Month);
            }
        });

        $.each(CalendarConfig, function (Index, Calendar) {
            CalendarSources[Calendar.CalendarID] = {
                url: Core.Config.Get('CGIHandle'),
                type: 'POST',
                data: {
                    ChallengeToken: $("#ChallengeToken").val(),
                    Action: 'AgentAppointmentList',
                    Subaction: 'ListAppointments',
                    CalendarID: Calendar.CalendarID,
                    ResourceID: Core.Config.Get('ResourceID'),
                    TeamID: Core.Config.Get('TeamID')
                },
                color: Calendar.Color,
                textColor: Calendar.TextColor,
                borderColor: 'rgba(0, 0, 0, 0.2)',
                startParam: 'StartTime',
                endParam: 'EndTime',

                // Workaround for removeEventSource.
                googleCalendarId: Calendar.CalendarID,

                eventDataTransform: function(AppointmentData) {
                    var TicketAppointmentConfig = Core.Config.Get('TicketAppointmentConfig');

                    return {
                        id: AppointmentData.AppointmentID,
                        parentId: AppointmentData.ParentID,
                        start: AppointmentData.StartTime,
                        startDate: AppointmentData.StartDate,
                        end: AppointmentData.EndTime,
                        endDate: AppointmentData.EndDate,
                        title: AppointmentData.Title,
                        description: AppointmentData.Description,
                        location: AppointmentData.Location,
                        calendarId: AppointmentData.CalendarID,
                        allDay: parseInt(AppointmentData.AllDay, 10) ? true : false,
                        recurring: parseInt(AppointmentData.Recurring, 10) ? true : false,
                        teamIds: AppointmentData.TeamID,
                        teamNames: AppointmentData.TeamNames,
                        resourceIds: AppointmentData.ResourceID,
                        resourceNames: AppointmentData.ResourceNames,
                        pluginData: AppointmentData.PluginData,
                        calendarName: Calendar.CalendarName,
                        calendarColor: Calendar.Color,
                        notification: AppointmentData.NotificationDate.length ? true : false,
                        notificationDate: AppointmentData.NotificationDate,
                        ticketAppointmentType: AppointmentData.TicketAppointmentStartDate,
                        startEditable: typeof AppointmentData.TicketAppointmentStartDate === 'undefined' ? true :
                            typeof TicketAppointmentConfig[AppointmentData.TicketAppointmentStartDate].NoDrag !== 'undefined' ? false : true,
                        durationEditable: typeof AppointmentData.TicketAppointmentEndDate === 'undefined' ? true :
                            typeof TicketAppointmentConfig[AppointmentData.TicketAppointmentStartDate].NoDrag !== 'undefined' ? true : false
                    };
                }
            };
        });

        $('.CalendarSwitch input[type="checkbox"]').each(function () {
            CalendarSwitchInit($(this));
        });

        // Auto open appointment create screen.
        if (Core.Config.Get('AppointmentCreate')) {
            TargetNS.OpenEditDialog({
                Start: Core.Config.Get('AppointmentCreate').Start ? $.fullCalendar.moment(Core.Config.Get('AppointmentCreate').Start) : $.fullCalendar.moment().add(1, 'hours').startOf('hour'),
                End: Core.Config.Get('AppointmentCreate').End ? $.fullCalendar.moment(Core.Config.Get('AppointmentCreate').End) : $.fullCalendar.moment().add(2, 'hours').startOf('hour'),
                PluginKey: Core.Config.Get('AppointmentCreate').PluginKey ? Core.Config.Get('AppointmentCreate').PluginKey : null,
                Search: Core.Config.Get('AppointmentCreate').Search ? Core.Config.Get('AppointmentCreate').Search : null,
                ObjectID: Core.Config.Get('AppointmentCreate').ObjectID ? Core.Config.Get('AppointmentCreate').ObjectID : null
            });
        }

        // Auto open appointment edit screen
        else if (Core.Config.Get('AppointmentID')) {
            TargetNS.OpenEditDialog({ CalEvent: { id: Core.Config.Get('AppointmentID') } });
        }

        $('#AppointmentCreateButton')
            .off('click.AppointmentCalendar')
            .on('click.AppointmentCalendar', function () {
                TargetNS.OpenEditDialog({
                    Start: $.fullCalendar.moment().add(1, 'hours').startOf('hour'),
                    End: $.fullCalendar.moment().add(2, 'hours').startOf('hour')
                });

                return false;
            });

        CalendarSettingsInit();

        ResourceSettingsInit();

        $('#Team').on('change', function() {
            $('#ChangeTeamForm').submit();
        });

        Core.UI.Table.InitTableFilter($('#FilterCalendars'), $('#Calendars'));

        ClipboardJS.on('success', function (Event) {
            $(Event.trigger).hide()
                .fadeIn();
            Event.clearSelection();
        });

        ClipboardJS.on('error', function(Event) {
            Core.Form.ErrorTooltips.InitTooltip($(Event.trigger), Core.Language.Translate('Press Ctrl+C (Cmd+C) to copy to clipboard'));
            $(Event.trigger).focus();
        });
    };

    /**
     * @private
     * @name ShowDatepicker
     * @memberof Core.Agent.AppointmentCalendar
     * @param {jQueryObject} $CalendarObj - Calendar control object.
     * @param {jQueryObject} $DatepickerObj - Datepicker control object.
     * @param {jQueryObject} $JumpButton - Datepicker button object.
     * @description
     *      Show date picker control.
     */
    function ShowDatepicker($CalendarObj, $DatepickerObj, $JumpButton) {
        var CurrentDate = $CalendarObj.fullCalendar('getDate'),
            Year = CurrentDate.format('YYYY'),
            Month = CurrentDate.format('M');

        $('<div />').prop('id', 'DatepickerOverlay')
            .appendTo($('body'))
            .on('click.AppointmentCalendar', function () {
                $(this).remove();
                $DatepickerObj.hide();
            });

        AppointmentDays($DatepickerObj, Year, Month);

        $DatepickerObj.datepicker('setDate', CurrentDate.toDate())
            .css({
                left: parseInt($JumpButton.offset().left - $DatepickerObj.outerWidth() + $JumpButton.outerWidth(), 10),
                top: parseInt($JumpButton.offset().top - $DatepickerObj.outerHeight() + $JumpButton.outerHeight(), 10)
            }).fadeIn('fast');
    }

    /**
     * @private
     * @name CheckDate
     * @memberof Core.Agent.AppointmentCalendar
     * @function
     * @param {DateObject} DateObject - A JS date object to check.
     * @returns {Array} First element is always true, second element contains the name of a CSS
     *                  class, third element a description for the date.
     * @description
     *      Check if date has an appointment.
     */
    function CheckDate(DateObject) {
        var DateMoment = $.fullCalendar.moment(DateObject),
            DayAppointments = AppointmentDaysCache[DateMoment.format('YYYY-MM-DD')],
            DayClass = DayAppointments ? 'Highlight' : '',
            DayDescription = DayAppointments ? DayAppointments.toString() : '';

        return [true, DayClass, DayDescription];
    }

    /**
     * @private
     * @name AppointmentDays
     * @memberof Core.Agent.AppointmentCalendar
     * @param {jQueryObject} $DatepickerObj - Datepicker control object.
     * @param {Integer} Year - Selected year.
     * @param {Integer} Month - Selected month (1-12).
     * @description
     *      Caches the list of appointment days for later use.
     */
    function AppointmentDays($DatepickerObj, Year, Month) {
        var StartTime = $.fullCalendar.moment(Year + '-' + Month, 'YYYY-M').startOf('month'),
            EndTime = $.fullCalendar.moment(Year + '-' + Month, 'YYYY-M').add(1, 'months').startOf('month'),
            Data = {
                ChallengeToken: $('#ChallengeToken').val(),
                Action: 'AgentAppointmentList',
                Subaction: 'AppointmentDays',
                StartTime: StartTime.format('YYYY-MM-DD'),
                EndTime: EndTime.format('YYYY-MM-DD')
            };

        $DatepickerObj.addClass('AJAXLoading');
        AppointmentDaysCacheRefreshed = false;

        Core.AJAX.FunctionCall(
            Core.Config.Get('CGIHandle'),
            Data,
            function (Response) {
                if (Response) {
                    AppointmentDaysCache = Response;
                    AppointmentDaysCacheRefreshed = true;
                    $DatepickerObj.removeClass('AJAXLoading');

                    // Refresh the date picker because this call is asynchronous
                    $DatepickerObj.datepicker('refresh');
                }
            }
        );
    }

    /**
     * @public
     * @name ShowWaitingDialog
     * @memberof Core.Agent.AppointmentCalendar
     * @description
     *      Shows waiting dialog.
     */
    TargetNS.ShowWaitingDialog = function () {
        Core.UI.Dialog.ShowContentDialog('<div class="Spacing Center"><span class="AJAXLoader" title="' + Core.Language.Translate('Loading...') + '"></span></div>', Core.Language.Translate('Loading...'), '10px', 'Center', true);
    }

    /**
     * @public
     * @name OpenEditDialog
     * @memberof Core.Agent.AppointmentCalendar
     * @param {Object} AppointmentData - Hash with appointment data.
     * @param {Moment} AppointmentData.Start - Moment object with start date/time.
     * @param {Moment} AppointmentData.End - Moment object with end date/time.
     * @param {Object} AppointmentData.CalEvent - Calendar event object (FullCalendar).
     * @param {Object} AppointmentData.Resource - Calendar resource object (FullCalendar).
     * @description
     *      This method opens the appointment dialog after selecting a time period or an appointment.
     */
    TargetNS.OpenEditDialog = function (AppointmentData) {
        var Data = {
            ChallengeToken: $('#ChallengeToken').val(),
            Action: 'AgentAppointmentEdit',
            Subaction: 'EditMask',
            AppointmentID: AppointmentData.CalEvent ? AppointmentData.CalEvent.id : null,
            StartYear: !AppointmentData.CalEvent ? AppointmentData.Start.year() : null,
            StartMonth: !AppointmentData.CalEvent ? AppointmentData.Start.month() + 1 : null,
            StartDay: !AppointmentData.CalEvent ? AppointmentData.Start.date() : null,
            StartHour: !AppointmentData.CalEvent ? AppointmentData.Start.hour() : null,
            StartMinute: !AppointmentData.CalEvent ? AppointmentData.Start.minute() : null,
            EndYear: !AppointmentData.CalEvent ? AppointmentData.End.year() : null,
            EndMonth: !AppointmentData.CalEvent ? AppointmentData.End.month() + 1 : null,
            EndDay: !AppointmentData.CalEvent ? AppointmentData.End.date() : null,
            EndHour: !AppointmentData.CalEvent ? AppointmentData.End.hour() : null,
            EndMinute: !AppointmentData.CalEvent ? AppointmentData.End.minute() : null,
            AllDay: !AppointmentData.CalEvent ? (AppointmentData.End.hasTime() ? '0' : '1') : null,
            TeamID: AppointmentData.Resource ? [ AppointmentData.Resource.TeamID ] : null,
            ResourceID: AppointmentData.Resource ? [ AppointmentData.Resource.id ] : null,
            PluginKey: AppointmentData.PluginKey ? AppointmentData.PluginKey : null,
            Search: AppointmentData.Search ? AppointmentData.Search : null,
            ObjectID: AppointmentData.ObjectID ? AppointmentData.ObjectID : null
        };

        // Make end time for all day appointments inclusive
        if (Data.AllDay && Data.AllDay === '1') {
            AppointmentData.End.subtract(1, 'day');
            Data.EndYear = AppointmentData.End.year();
            Data.EndMonth = AppointmentData.End.month() + 1;
            Data.EndDay = AppointmentData.End.date();
            Data.EndHour = AppointmentData.End.hour();
            Data.EndMinute = AppointmentData.End.minute();
        }

        function EditDialog() {
            TargetNS.ShowWaitingDialog();
            Core.AJAX.FunctionCall(
                Core.Config.Get('CGIHandle'),
                Data,
                function (HTML) {
                    Core.UI.Dialog.ShowContentDialog(HTML, Core.Language.Translate('Appointment'), '10px', 'Center', true, undefined, true);
                    Core.UI.InputFields.Activate($('.Dialog:visible'));

                    TargetNS.AgentAppointmentEdit();
                }, 'html'
            );
        }

        // Repeating event
        if (AppointmentData.CalEvent && AppointmentData.CalEvent.parentId) {
            Core.UI.Dialog.ShowDialog({
                Title: Core.Language.Translate('This is a repeating appointment'),
                HTML: Core.Language.Translate('Would you like to edit just this occurrence or all occurrences?'),
                Modal: true,
                CloseOnClickOutside: true,
                CloseOnEscape: true,
                PositionTop: '20%',
                PositionLeft: 'Center',
                Buttons: [
                    {
                        Label: Core.Language.Translate('All occurrences'),
                        Class: 'Primary CallForAction',
                        Function: function() {
                            Data.AppointmentID = AppointmentData.CalEvent.parentId;
                            EditDialog();
                        }
                    },
                    {
                        Label: Core.Language.Translate('Just this occurrence'),
                        Class: 'CallForAction',
                        Function: EditDialog
                    },
                    {
                        Type: 'Close',
                        Label: Core.Language.Translate('Close this dialog')
                    }
                ]
            });
        } else {
            EditDialog();
        }
    }

    /**
     * @private
     * @name UpdateAppointment
     * @memberof Core.Agent.AppointmentCalendar
     * @param {Object} AppointmentData - Hash with appointment data.
     * @param {Object} AppointmentData.CalEvent - Calendar event object (FullCalendar).
     * @description
     *      This method updates the appointment with supplied data.
     */
    function UpdateAppointment(AppointmentData) {
        var Data = {
            ChallengeToken: $('#ChallengeToken').val(),
            Action: 'AgentAppointmentEdit',
            Subaction: 'EditAppointment',
            AppointmentID: AppointmentData.CalEvent.id,
            StartYear: AppointmentData.CalEvent.start.year(),
            StartMonth: AppointmentData.CalEvent.start.month() + 1,
            StartDay: AppointmentData.CalEvent.start.date(),
            StartHour: AppointmentData.CalEvent.start.hour(),
            StartMinute: AppointmentData.CalEvent.start.minute(),
            EndYear: AppointmentData.CalEvent.end.year(),
            EndMonth: AppointmentData.CalEvent.end.month() + 1,
            EndDay: AppointmentData.CalEvent.end.date(),
            EndHour: AppointmentData.CalEvent.end.hour(),
            EndMinute: AppointmentData.CalEvent.end.minute(),
            AllDay: AppointmentData.CalEvent.end.hasTime() ? '0' : '1',
            Recurring: AppointmentData.CalEvent.recurring ? '1' : '0',
            TeamID: AppointmentData.CalEvent.teamIds ? AppointmentData.CalEvent.teamIds : undefined,
            ResourceID: AppointmentData.CalEvent.resourceIds ? AppointmentData.CalEvent.resourceIds :
                AppointmentData.CalEvent.resourceId ? [ AppointmentData.CalEvent.resourceId ] : undefined
        };

        // Assigned resource didn't change
        if (
            AppointmentData.CalEvent.resourceId
            && AppointmentData.PreviousAppointment.resourceIds
            && $.inArray(
                AppointmentData.CalEvent.resourceId,
                AppointmentData.PreviousAppointment.resourceIds
            ) !== -1
        ) {
            Data.ResourceID = AppointmentData.PreviousAppointment.resourceIds;
        }

        function Update() {
            Core.UI.Dialog.CloseDialog($('.Dialog:visible'));
            Core.AJAX.FunctionCall(
                Core.Config.Get('CGIHandle'),
                Data,
                function (Response) {
                    if (Response.Success) {
                        $('#calendar').fullCalendar('refetchEvents');
                    } else {
                        AppointmentData.RevertFunc();
                    }

                    // Close the dialog
                    Core.UI.Dialog.CloseDialog($('.Dialog:visible'));
                }
            );
        }

        // Make end time for all day appointments inclusive
        if (AppointmentData.CalEvent.allDay) {
            AppointmentData.CalEvent.end.subtract(1, 'day');
            Data.EndYear = AppointmentData.CalEvent.end.year();
            Data.EndMonth = AppointmentData.CalEvent.end.month() + 1;
            Data.EndDay = AppointmentData.CalEvent.end.date();
            Data.EndHour = AppointmentData.CalEvent.end.hour();
            Data.EndMinute = AppointmentData.CalEvent.end.minute();
        }

        // Repeating event
        if (AppointmentData.CalEvent.parentId) {
            Core.UI.Dialog.ShowDialog({
                Title: Core.Language.Translate('This is a repeating appointment'),
                HTML: Core.Language.Translate('Would you like to edit just this occurrence or all occurrences?'),
                Modal: true,
                CloseOnClickOutside: true,
                CloseOnEscape: true,
                PositionTop: '20%',
                PositionLeft: 'Center',
                Buttons: [
                    {
                        Label: Core.Language.Translate('All occurrences'),
                        Class: 'Primary CallForAction',
                        Function: function() {
                            Data.AppointmentID = AppointmentData.CalEvent.parentId;
                            Data.Recurring = '1';
                            Data.UpdateDelta = AppointmentData.Delta.asSeconds();
                            if (
                                AppointmentData.CalEvent.start.diff(AppointmentData.PreviousAppointment.start, 'seconds')
                                === Data.UpdateDelta
                                &&
                                AppointmentData.CalEvent.end.diff(AppointmentData.PreviousAppointment.end, 'seconds')
                                === Data.UpdateDelta
                            ) {
                                Data.UpdateType = 'Both';
                            }
                            else if (
                                AppointmentData.PreviousAppointment.start.diff(AppointmentData.CalEvent.start, 'seconds')
                                === Data.UpdateDelta
                            ) {
                                Data.UpdateType = 'StartTime';
                                Data.UpdateDelta = Data.UpdateDelta * -1;
                            }
                            else if (
                                AppointmentData.CalEvent.end.diff(AppointmentData.PreviousAppointment.end, 'seconds')
                                === Data.UpdateDelta
                            ) {
                                Data.UpdateType = 'EndTime';
                            }
                            Update();
                        }
                    },
                    {
                        Label: Core.Language.Translate('Just this occurrence'),
                        Class: 'CallForAction',
                        Function: function() {
                            AppointmentData.CalEvent.parentId = null;
                            Update();
                        }
                    },
                    {
                        Type: 'Close',
                        Label: Core.Language.Translate('Close this dialog'),
                        Function: function() {
                            Core.UI.Dialog.CloseDialog($('.Dialog:visible'));
                            AppointmentData.RevertFunc();
                        }
                    }
                ]
            });
        } else {
            Update();
        }
    }

    /**
     * @private
     * @name CalendarSwitchInit
     * @memberof Core.Agent.AppointmentCalendar
     * @param {jQueryObject} $CalendarSwitch - calendar checkbox element.
     * @description
     *      This method initializes calendar checkbox behavior and loads multiple calendars to the
     *      FullCalendar control.
     */
    function CalendarSwitchInit($CalendarSwitch) {

        // Initialize enabled sources
        if ($CalendarSwitch.prop('checked')) {
            $('#calendar').fullCalendar('addEventSource', CalendarSources[$CalendarSwitch.data('id')]);
        }

        // Register change event handler
        $CalendarSwitch.off('change.AppointmentCalendar').on('change.AppointmentCalendar', function() {
            if ($('.CalendarSwitch input:checked').length > Core.Config.Get('CalendarLimit')) {
                $CalendarSwitch.prop('checked', false);
                Core.UI.Dialog.ShowAlert(Core.Language.Translate('Too many active calendars'), Core.Language.Translate('Please either turn some off first or increase the limit in configuration.'));
            } else {
                CalendarSwitchSource($CalendarSwitch);
            }
        });
    }

    /**
     * @private
     * @name CalendarSwitchSource
     * @memberof Core.Agent.AppointmentCalendar
     * @param {jQueryObject} $CalendarSwitch - calendar checkbox element.
     * @description
     *      This method enables/disables calendar source in FullCalendar control and stores
     *      selection to user preferences.
     */
    function CalendarSwitchSource($CalendarSwitch) {
        var CalendarSelection = [];

        // Show/hide the calendar appointments
        if ($CalendarSwitch.prop('checked')) {
            $('#calendar').fullCalendar('addEventSource', CalendarSources[$CalendarSwitch.data('id')]);
        } else {
            $('#calendar').fullCalendar('removeEventSource', CalendarSources[$CalendarSwitch.data('id')]);
        }

        // Get all checked calendars
        $.each($('.CalendarSwitch input:checked'), function (Index, Element) {
            CalendarSelection.push($(Element).data('id'));
        });

        // Store selection in user preferences
        Core.AJAX.FunctionCall(
            Core.Config.Get('CGIHandle'),
            {
                ChallengeToken: $('#ChallengeToken').val(),
                Action: 'AgentAppointmentEdit',
                Subaction: 'UpdatePreferences',
                OverviewScreen: Core.Config.Get('OverviewScreen') ? Core.Config.Get('OverviewScreen') : 'CalendarOverview',
                CalendarSelection: JSON.stringify(CalendarSelection)
            },
            function (Response) {
                if (!Response.Success) {
                    Core.Debug.Log('Error updating user preferences!');
                }
            }
        );
    }

    /**
     * @name LocationLinkInit
     * @memberof Core.Agent.AppointmentCalendar
     * @param {jQueryObject} $LocationObj - location field.
     * @param {jQueryObject} $LocationLinks - location links.
     * @description
     *      This method initializes location link behavior.
     */
    TargetNS.LocationLinkInit = function ($LocationObj, $LocationLinks) {
        var LocationValue = $LocationObj.val() || $LocationObj.text();

        $LocationLinks.each(function (Index, Element) {
            var $LinkObj = $(Element),
                BaseURL  = $LinkObj.data('baseUrl');

            // Update link URL and show it if location is set
            if (LocationValue.length > 0 && LocationValue !== '-') {
                $LinkObj.attr('href', BaseURL + encodeURIComponent(LocationValue))
                    .fadeIn('fast');
            }

            // Otherwise, hide the link
            else {
                $LinkObj.fadeOut('fast');
            }
        });

        // Register key up event handler
        $LocationObj.off('keyup.AppointmentCalendar').on('keyup.AppointmentCalendar', function() {
            TargetNS.LocationLinkInit($LocationObj, $LocationLinks);
        });
    }

    /**
     * @name AllDayInit
     * @memberof Core.Agent.AppointmentCalendar
     * @param {jQueryObject} $AllDay - all day checkbox element.
     * @description
     *      This method initializes all day checkbox behavior.
     */
    TargetNS.AllDayInit = function ($AllDay) {
        if ($('#StartMonth:disabled').length > 0) {
            return;
        }

        // Show/hide the start hour/minute and complete end time
        if ($AllDay.prop('checked')) {
            $('#StartHour,#StartMinute,#EndHour,#EndMinute').val('0')
                .prop('disabled', true)
                .prop('readonly', true);
        } else {
            $('#StartHour,#StartMinute,#EndHour,#EndMinute')
                .prop('disabled', false)
                .prop('readonly', false);
        }

        // Register change event handler
        $AllDay.off('change.AppointmentCalendar').on('change.AppointmentCalendar', function() {
            TargetNS.AllDayInit($AllDay);
        });
    }

    /**
     * @name RecurringInit
     * @memberof Core.Agent.AppointmentCalendar
     * @param {Object} Fields - Array with references to recurring fields.
     * @param {jQueryObject} Fields.$Recurring - field with recurring flag.
     * @param {jQueryObject} Fields.$RecurrenceType - drop down with recurrence type
     * @param {jQueryObject} Fields.$RecurrenceCustomType - Recurrence pattern
     * @param {jQueryObject} Fields.$RecurrenceCustomTypeStringDiv - Custom type drop-down
     * @param {jQueryObject} Fields.$RecurrenceIntervalText - interval text
     * @param {jQueryObject} Fields.$RecurrenceCustomWeeklyDiv - contains week days table
     * @param {jQueryObject} Fields.$RecurrenceCustomMonthlyDiv - contains month days table
     * @param {jQueryObject} Fields.$RecurrenceCustomYearlyDiv - contains months table
     * @param {jQueryObject} Fields.$RecurrenceLimitDiv - layer with recurrence limit fields.
     * @param {jQueryObject} Fields.$RecurrenceLimit - drop down with recurrence limit field.
     * @param {jQueryObject} Fields.$RecurrenceCountDiv - layer with reccurence count field.
     * @param {jQueryObject} Fields.$RecurrenceUntilDiv - layer with reccurence until fields.
     * @param {jQueryObject} Fields.$RecurrenceUntilDay - select field for reccurence until day.
     * @description
     *      This method initializes recurrence fields behavior.
     */
    TargetNS.RecurringInit = function (Fields) {

        function RecInit(Fields) {
            var Days = [],
                MonthDays = [],
                Months = [],
                Index;

            if ($('input#Days').length) {
                Days = $('input#Days').val().split(',');
            }
            if ($('input#MonthDays').length) {
                MonthDays = $('input#MonthDays').val().split(',');
            }
            if ($('input#MonthDays').length) {
                Months = $('input#Months').val().split(',');
            }

            for (Index = 0; Index < Days.length; Index++) {
                if (Days[Index] != "") {
                    $("#RecurrenceCustomWeeklyDiv button[value=" + Days[Index] + "]")
                        .addClass("fc-state-active");
                }
            }

            for (Index = 0; Index < MonthDays.length; Index++) {
                if (MonthDays[Index] != "") {
                    $("#RecurrenceCustomMonthlyDiv button[value=" + MonthDays[Index] + "]")
                        .addClass("fc-state-active");
                }
            }

            for (Index = 0; Index < Months.length; Index++) {
                if (Months[Index] != "") {
                    $("#RecurrenceCustomYearlyDiv button[value=" + Months[Index] + "]")
                        .addClass("fc-state-active");
                }
            }

            if (Fields.$RecurrenceType.val() == 0) {
                Fields.$Recurring.val(0);
                Fields.$RecurrenceCustomTypeStringDiv.hide();
                Fields.$RecurrenceCustomWeeklyDiv.hide();
                Fields.$RecurrenceCustomMonthlyDiv.hide();
                Fields.$RecurrenceCustomYearlyDiv.hide();

                Fields.$RecurrenceLimitDiv.hide();
                Fields.$RecurrenceCountDiv.hide();
                Fields.$RecurrenceUntilDiv.hide();

                // Skip validation of RecurrenceUntil fields
                Fields.$RecurrenceUntilDay.addClass('ValidationIgnore');
            }
            else if (Fields.$RecurrenceType.val() == 'Custom') {
                Fields.$Recurring.val(1);
                Fields.$RecurrenceCustomTypeStringDiv.show();

                if (Fields.$RecurrenceCustomType.val()=="CustomDaily") {
                    Fields.$RecurrenceCustomWeeklyDiv.hide();
                    Fields.$RecurrenceCustomMonthlyDiv.hide();
                    Fields.$RecurrenceCustomYearlyDiv.hide();
                    Fields.$RecurrenceIntervalText.find('span')
                        .hide()
                        .end()
                        .find('.TextDay')
                        .show();
                }
                else if (Fields.$RecurrenceCustomType.val()=="CustomWeekly") {
                    Fields.$RecurrenceCustomWeeklyDiv.show();

                    Fields.$RecurrenceCustomMonthlyDiv.hide();
                    Fields.$RecurrenceCustomYearlyDiv.hide();
                    Fields.$RecurrenceIntervalText.find('span')
                        .hide()
                        .end()
                        .find('.TextWeek')
                        .show();
                }
                else if (Fields.$RecurrenceCustomType.val()=="CustomMonthly") {
                    Fields.$RecurrenceCustomMonthlyDiv.show();

                    Fields.$RecurrenceCustomWeeklyDiv.hide();
                    Fields.$RecurrenceCustomYearlyDiv.hide();
                    Fields.$RecurrenceIntervalText.find('span')
                        .hide()
                        .end()
                        .find('.TextMonth')
                        .show();
                }
                else if (Fields.$RecurrenceCustomType.val()=="CustomYearly") {
                    Fields.$RecurrenceCustomYearlyDiv.show();

                    Fields.$RecurrenceCustomWeeklyDiv.hide();
                    Fields.$RecurrenceCustomMonthlyDiv.hide();
                    Fields.$RecurrenceIntervalText.find('span')
                        .hide()
                        .end()
                        .find('.TextYear')
                        .show();
                }

                Fields.$RecurrenceLimitDiv.show();
                Fields.$RecurrenceLimit.off('change.AppointmentCalendar').on('change.AppointmentCalendar', function() {
                    if ($(this).val() == 1) {
                        Fields.$RecurrenceCountDiv.hide();
                        Fields.$RecurrenceUntilDiv.show();

                        // Resume validation of RecurrenceUntil fields
                        Fields.$RecurrenceUntilDay.removeClass('ValidationIgnore');
                    } else {
                        Fields.$RecurrenceUntilDiv.hide();
                        Fields.$RecurrenceCountDiv.show();

                        // Skip validation of RecurrenceUntil fields
                        Fields.$RecurrenceUntilDay.addClass('ValidationIgnore');
                    }
                }).trigger('change.AppointmentCalendar');

                Core.UI.InputFields.Activate(Fields.$RecurrenceCustomTypeStringDiv);
                Core.UI.InputFields.Activate(Fields.$RecurrenceLimitDiv);
            }
            else {
                Fields.$Recurring.val(1);
                Fields.$RecurrenceLimitDiv.show();

                Fields.$RecurrenceCustomTypeStringDiv.hide();
                Fields.$RecurrenceCustomWeeklyDiv.hide();
                Fields.$RecurrenceCustomMonthlyDiv.hide();
                Fields.$RecurrenceCustomYearlyDiv.hide();

                Fields.$RecurrenceLimit.off('change.AppointmentCalendar').on('change.AppointmentCalendar', function() {
                    if ($(this).val() == 1) {
                        Fields.$RecurrenceCountDiv.hide();
                        Fields.$RecurrenceUntilDiv.show();

                        // Resume validation of RecurrenceUntil fields
                        Fields.$RecurrenceUntilDay.removeClass('ValidationIgnore');
                    } else {
                        Fields.$RecurrenceUntilDiv.hide();
                        Fields.$RecurrenceCountDiv.show();

                        // Skip validation of RecurrenceUntil fields
                        Fields.$RecurrenceUntilDay.addClass('ValidationIgnore');
                    }
                }).trigger('change.AppointmentCalendar');
                Core.UI.InputFields.Activate(Fields.$RecurrenceLimitDiv);
            }

            Fields.$RecurrenceCustomType.off('change.AppointmentCalendar').on('change.AppointmentCalendar', function() {
                TargetNS.RecurringInit(Fields);
            });
        }

        Fields.$RecurrenceType.off('change.AppointmentCalendar').on('change.AppointmentCalendar', function() {
            RecInit(Fields);
        }).trigger('change.AppointmentCalendar');
    }

    /**
     * @name NotificationInit
     * @memberof Core.Agent.AppointmentCalendar
     * @param {Object} Fields - Array with references to reminder fields.
     * @param {jQueryObject} Fields.$Notification - drop down with system notification selection.
     * @param {jQueryObject} Fields.$NotificationCustomStringDiv - custom selection of system notification date
     * @description
     *      This method initializes the reminder section behavior.
     */
    TargetNS.NotificationInit = function (Fields) {
        var NotificationCustomStringDiv = Fields.$NotificationCustomStringDiv.attr('id');

        if (Fields.$NotificationTemplate.val() !== 'Custom') {

            // hide the custom fields
            Fields.$NotificationCustomStringDiv.hide();
        }
        else {

            // custom field is needed
            Fields.$NotificationCustomStringDiv.show();

            // initialize modern fields on custom selection
            Core.UI.InputFields.InitSelect($('select.Modernize'));
        }

        // disable enable the different custom fields
        if (Fields.$NotificationCustomRelativeInput.prop('checked')) {

            // enable relative date fields
            Fields.$NotificationCustomRelativeInput.val(1);
            Fields.$NotificationCustomRelativeUnitCount.prop('disabled', false).prop('readonly', false);
            Fields.$NotificationCustomRelativeUnit.prop('disabled', false);
            Fields.$NotificationCustomRelativePointOfTime.prop('disabled', false);

            // disable the custom date time fields
            Fields.$NotificationCustomDateTimeInput.val('');
            Fields.$NotificationCustomDateTimeDay.prop('disabled', true);
            Fields.$NotificationCustomDateTimeMonth.prop('disabled', true);
            Fields.$NotificationCustomDateTimeYear.prop('disabled', true);
            Fields.$NotificationCustomDateTimeHour.prop('disabled', true);
            Fields.$NotificationCustomDateTimeMinute.prop('disabled', true);
        }
        else {

            // enable the custom date time input
            Fields.$NotificationCustomDateTimeInput.val(1);
            Fields.$NotificationCustomDateTimeInput.prop('checked', true);
            Fields.$NotificationCustomDateTimeDay.prop('disabled', false);
            Fields.$NotificationCustomDateTimeMonth.prop('disabled', false);
            Fields.$NotificationCustomDateTimeYear.prop('disabled', false);
            Fields.$NotificationCustomDateTimeHour.prop('disabled', false);
            Fields.$NotificationCustomDateTimeMinute.prop('disabled', false);

            // disable relative date fields
            Fields.$NotificationCustomRelativeInput.val('');
            Fields.$NotificationCustomRelativeInput.prop('checked', false);
            Fields.$NotificationCustomRelativeUnitCount.prop('disabled', true).prop('readonly', true);
            Fields.$NotificationCustomRelativeUnit.prop('disabled', true);
            Fields.$NotificationCustomRelativePointOfTime.prop('disabled', true);
        }

        // TODO: Workaround for InputFields bug in the framework (disabled attribute not checked after initialization)
        Core.UI.InputFields.Deactivate('#' + Core.App.EscapeSelector(NotificationCustomStringDiv));
        Core.UI.InputFields.Activate('#' + Core.App.EscapeSelector(NotificationCustomStringDiv));

        // Register change event handler
        Fields.$NotificationTemplate.off('change.AppointmentCalendar').on('change.AppointmentCalendar', function() {
            TargetNS.NotificationInit(Fields);
        });

        Fields.$NotificationCustomRelativeInput.off('change.AppointmentCalendar').on('change.AppointmentCalendar', function() {

            // handle radio buttons
            Fields.$NotificationCustomRelativeInput.prop('checked', true);
            Fields.$NotificationCustomDateTimeInput.prop('checked', false);

            // process changes
            TargetNS.NotificationInit(Fields);
        });

        Fields.$NotificationCustomDateTimeInput.off('change.AppointmentCalendar').on('change.AppointmentCalendar', function() {

            // handle radio buttons
            Fields.$NotificationCustomDateTimeInput.prop('checked', true);
            Fields.$NotificationCustomRelativeInput.prop('checked', false);

            // process changes
            TargetNS.NotificationInit(Fields);
        });
    }

    /**
     * @name TeamInit
     * @memberof Core.Agent.AppointmentCalendar
     * @param {jQueryObject} $TeamIDObj - field with list of teams.
     * @param {jQueryObject} $ResourceIDObj - field with list of resources.
     * @param {jQueryObject} $TeamValueObj - field with read only values for team.
     * @param {jQueryObject} $ResourceValueObj - field with read only values for resources.
     * @description
     *      This method initializes team fields behavior.
     */
    TargetNS.TeamInit = function ($TeamIDObj, $ResourceIDObj, $TeamValueObj, $ResourceValueObj) {
        $TeamIDObj.off('change.AppointmentCalendar').on('change.AppointmentCalendar', function () {
            var Data = {
                ChallengeToken: $('#ChallengeToken').val(),
                Action: 'AgentAppointmentEdit',
                Subaction: 'TeamUserList',
                TeamID: $TeamIDObj.val()
            };

            ToggleAJAXLoader($ResourceIDObj, true);

            Core.AJAX.FunctionCall(
                Core.Config.Get('CGIHandle'),
                Data,
                function (Response) {
                    var SelectedID = $ResourceIDObj.val();
                    if (Response.TeamUserList) {
                        $ResourceIDObj.empty();
                        $.each(Response.TeamUserList, function (Index, Value) {
                            var NewOption = new Option(Value, Index);

                            // Overwrite option text, because of wrong html quoting of text content.
                            // (This is needed for IE.)
                            NewOption.innerHTML = Core.App.EscapeHTML(Value);

                            // Restore selection
                            if (SelectedID && SelectedID.indexOf(Index) > -1) {
                                NewOption.selected = true;
                            }

                            $ResourceIDObj.append(NewOption);
                        });

                        // Trigger custom redraw event for InputFields
                        $ResourceIDObj.trigger('redraw.InputField');
                    }
                    ToggleAJAXLoader($ResourceIDObj, false);
                }
            );
        });

        function CollapseValues($ValueObj, Values) {
            $ValueObj.html('');
            $.each(Values, function (Index, Value) {
                var Count = Values.length,
                    TooltipIndex = $('.Dialog').css('z-index');

                if (Index < 2) {
                    $ValueObj.html($ValueObj.html() + Value + '<br>');
                } else {
                    Values.splice(-Count, 2);
                    $('<a />').attr('href', '#')
                        .addClass('DialogTooltipLink')
                        .text(Core.Language.Translate('+%s more', Count-2))
                        .off('click.AppointmentCalendar')
                        .on('click.AppointmentCalendar', function (Event) {
                            var $TooltipObj,
                                PosX = 0,
                                PosY = 0,
                                TooltipHTML = '<p>' + Values.join('<br>') + '</p>',
                                DocumentVisibleLeft = $(document).scrollLeft() + $(window).width(),
                                DocumentVisibleTop = $(document).scrollTop() + $(window).height(),
                                LastXPosition,
                                LastYPosition;

                            // Close existing tooltips
                            $('.DialogTooltip').remove();

                            if (Event.pageX || Event.pageY) {
                                PosX = Event.pageX;
                                PosY = Event.pageY;
                            } else if (Event.clientX || Event.clientY) {
                                PosX = Event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
                                PosY = Event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
                            }

                            // Increase positions so the tooltip do not overlap with mouse pointer
                            PosX += 10;
                            PosY += 5;

                            if (typeof TooltipIndex !== 'undefined') {
                                TooltipIndex = parseInt(TooltipIndex, 10) + 500;
                            }
                            else{
                                TooltipIndex = 4000;
                            }

                            // Create tooltip object
                            $TooltipObj = $('<div/>').addClass('AppointmentTooltip DialogTooltip Hidden')
                                .offset({
                                    top: PosY,
                                    left: PosX
                                })
                                .html(TooltipHTML)
                                .css('z-index', TooltipIndex)
                                .css('width', 'auto')
                                .off('click.AppointmentCalendar')
                                .on('click.AppointmentCalendar', function (Event) {
                                    Event.stopPropagation();
                                })
                                .appendTo('body');

                            // Re-calculate top position if needed
                            LastYPosition = PosY + $TooltipObj.height();
                            if (LastYPosition > DocumentVisibleTop) {
                                PosY = PosY - $TooltipObj.height();
                                $TooltipObj.css('top', PosY + 'px');
                            }

                            // Re-calculate left position if needed
                            LastXPosition = PosX + $TooltipObj.width();
                            if (LastXPosition > DocumentVisibleLeft) {
                                PosX = PosX - $TooltipObj.width() - 30;
                                $TooltipObj.css('left', PosX + 'px');
                            }

                            // Show the tooltip
                            $TooltipObj.fadeIn('fast');

                            // Close tooltip on any outside click
                            $(document).off('click.AppointmentCalendar')
                                .on('click.AppointmentCalendar', function (Event) {
                                    $('.Close').on('click', function(){
                                        $('.DialogTooltip').remove();
                                    });
                                    if (!$(Event.target).closest('.DialogTooltipLink').length) {
                                        $('.DialogTooltip').remove();
                                        $(document).off('click.AppointmentCalendar');
                                    }
                                });
                        })
                        .appendTo($ValueObj);
                    return false;
                }
            });
        }

        // Collapse read only values
        if ($TeamValueObj.length) {
            CollapseValues($TeamValueObj, $TeamValueObj.html().split('<br>'));
        }
        if ($ResourceValueObj.length) {
            CollapseValues($ResourceValueObj, $ResourceValueObj.html().split('<br>'));
        }
    }

    /**
     * @private
     * @name CalendarSettingsInit
     * @memberof Core.Agent.AppointmentCalendar
     * @description
     *      This method initializes calendar settings behavior.
     */
    function CalendarSettingsInit() {
        var $CalendarSettingsObj = $('#CalendarSettingsButton'),
            CalendarSettingsDialogHTML;

        if (!$CalendarSettingsObj.length) {
            return;
        }

        CalendarSettingsDialogHTML = Core.Template.Render('Agent/AppointmentCalendar/CalendarSettingsDialog', {
            'ShownAppointmentsStrg': Core.Config.Get('ShownAppointmentsStrg')
        });

        // Calendar settings button
        $CalendarSettingsObj.off('click.AppointmentCalendar').on('click.AppointmentCalendar', function (Event) {
            Core.UI.Dialog.ShowContentDialog(CalendarSettingsDialogHTML, Core.Language.Translate('Settings'), '10px', 'Center', true,
                [
                    {
                        Label: Core.Language.Translate('Save'),
                        Class: 'Primary',
                        Function: function () {
                            var $ShownAppointments = $('#ShownAppointments'),
                                Data = {
                                    ChallengeToken: $('#ChallengeToken').val(),
                                    Action: 'AgentAppointmentEdit',
                                    Subaction: 'UpdatePreferences',
                                    OverviewScreen: Core.Config.Get('OverviewScreen') ? Core.Config.Get('OverviewScreen') : 'CalendarOverview',
                                    ShownAppointments: $ShownAppointments.val()
                                };

                            Core.Agent.AppointmentCalendar.ShowWaitingDialog();

                            Core.AJAX.FunctionCall(
                                Core.Config.Get('CGIHandle'),
                                Data,
                                function (Response) {
                                    if (!Response.Success) {
                                        Core.Debug.Log('Error updating user preferences!');
                                    }
                                    window.location.href = Core.Config.Get('Baselink') + 'Action=AgentAppointment' + (Core.Config.Get('OverviewScreen') ? Core.Config.Get('OverviewScreen') : 'CalendarOverview');
                                }
                            );
                        }
                    }
                ], true);

            Event.preventDefault();
            Event.stopPropagation();

            return false;
        });

        // Show settings dialog immediately
        if (Core.Config.Get('CalendarSettingsShow')) {
            $CalendarSettingsObj.trigger('click.AppointmentCalendar');
        }
    }

    /**
     * @private
     * @name ResourceSettingsInit
     * @memberof Core.Agent.AppointmentCalendar
     * @description
     *      This method initializes resource settings behavior.
     */
    function ResourceSettingsInit() {
        var $ResourceSettingsObj = $('#ResourceSettingsButton'),
            ResourceSettingsDialogHTML;

        if (!$ResourceSettingsObj.length) {
            return;
        }

        ResourceSettingsDialogHTML = Core.Template.Render('Agent/AppointmentCalendar/ResourceSettingsDialog', Core.Config.Get('ShownResourceSettings'));

        // Resource settings button
        $ResourceSettingsObj.off('click.AppointmentCalendar').on('click.AppointmentCalendar', function (Event) {
            Core.UI.Dialog.ShowContentDialog(ResourceSettingsDialogHTML, Core.Language.Translate('Settings'), '10px', 'Center', true,
                [
                    {
                        Label: Core.Language.Translate('Save'),
                        Class: 'Primary',
                        Function: function () {
                            var $ListContainer = $('.AllocationListContainer').find('.AssignedFields'),
                                ShownResources = [],
                                Data = {
                                    ChallengeToken: $('#ChallengeToken').val(),
                                    Action: 'AgentAppointmentEdit',
                                    Subaction: 'UpdatePreferences',
                                    OverviewScreen: Core.Config.Get('OverviewScreen') ? Core.Config.Get('OverviewScreen') : 'ResourceOverview',
                                    TeamID: $('#Team').val()
                                };

                            if (isJQueryObject($ListContainer) && $ListContainer.length) {
                                $.each($ListContainer.find('li'), function() {
                                    ShownResources.push($(this).attr('data-fieldname'));
                                });
                            }
                            Data.ShownResources = JSON.stringify(ShownResources);

                            Core.Agent.AppointmentCalendar.ShowWaitingDialog();

                            Core.AJAX.FunctionCall(
                                Core.Config.Get('CGIHandle'),
                                Data,
                                function (Response) {
                                    if (!Response.Success) {
                                        Core.Debug.Log('Error updating user preferences!');
                                    }
                                    location.reload();
                                }
                            );
                        }
                    }
                ], true);

            Event.preventDefault();
            Event.stopPropagation();

            Core.Agent.TableFilters.SetAllocationList();

            return false;
        });

        // Initialize restore settings button.
        RestoreDefaultSettingsInit();
    }

    /**
     * @private
     * @name ResourceSettingsInit
     * @memberof Core.Agent.AppointmentCalendar
     * @description
     *      Adds restore default settings button (trash can) to view and initializes its behavior.
     */
    function RestoreDefaultSettingsInit() {
        var $RestoreSettingsObj;

        // Skip if button is not needed or we already have one.
        if (
            !Core.Config.Get('RestoreDefaultSettings')
            || $('#RestoreDefaultSettings').length > 0
            )
        {
            return;
        }

        // Create the button.
        $RestoreSettingsObj = $('<a />').attr('id', 'RestoreDefaultSettings')
            .attr('href', '#')
            .attr('title', Core.Language.Translate('Restore default settings'))
            .append($('<i />').addClass('fa fa-trash'));

        // Add it to the column header.
        $('tr.fc-super + tr .fc-cell-content').append($RestoreSettingsObj);

        // Register click handler.
        $RestoreSettingsObj.off('click.AppointmentCalendar').on('click.AppointmentCalendar', function (Event) {
            Core.AJAX.FunctionCall(
                Core.Config.Get('CGIHandle'),
                {
                    ChallengeToken: $('#ChallengeToken').val(),
                    Action: 'AgentAppointmentEdit',
                    Subaction: 'UpdatePreferences',
                    OverviewScreen: Core.Config.Get('OverviewScreen') ? Core.Config.Get('OverviewScreen') : 'ResourceOverview',
                    RestoreDefaultSettings: 'ShownResources',
                    TeamID: $('#Team').val()
                },
                function (Response) {
                    if (!Response.Success) {
                        Core.Debug.Log('Error updating user preferences!');
                    }
                    location.reload();
                }
            );

            Event.preventDefault();
            Event.stopPropagation();

            return false;
        });
    }

    /**
     * @private
     * @name ToggleAJAXLoader
     * @memberof Core.Agent.AppointmentCalendar
     * @function
     * @param {jQueryObject} $Element - Object reference of the field which is updated
     * @param {Boolean} Show - Show or hide the AJAX loader image
     * @description
     *      Shows and hides an ajax loader for every element which is updates via ajax.
     */
    function ToggleAJAXLoader($Element, Show) {
        var $Loader = $('#AJAXLoader' + $Element.attr('id')),
            LoaderHTML = '<span id="AJAXLoader' + $Element.attr('id') + '" class="AJAXLoader"></span>';

        // Element not present
        if (!$Element.length) {
            return;
        }

        // Show or hide the loader
        if (Show) {
            if (!$Loader.length) {
                $Element.after(LoaderHTML);
            } else {
                $Loader.show();
            }
        } else {
            if ($Loader.length) {
                $Loader.hide();
            }
        }
    }

    /**
     * @name PluginInit
     * @memberof Core.Agent.AppointmentCalendar
     * @param {jQueryObject} $PluginFields - fields with different plugin searches.
     * @description
     *      This method initializes plugin fields behavior.
     */
    TargetNS.PluginInit = function ($PluginFields) {

        function InitRemoveButtons() {
            $('.RemoveButton').off('click.AppointmentCalendar').on('click.AppointmentCalendar', function () {
                var $RemoveObj = $(this),
                    PluginKey = $RemoveObj.data('pluginKey'),
                    $PluginDataObj = $('#Plugin_' + Core.App.EscapeSelector(PluginKey)),
                    PluginData = JSON.parse($PluginDataObj.val()),
                    LinkID = $RemoveObj.data('linkId').toString(),
                    $Parent = $RemoveObj.parent();

                PluginData.splice(PluginData.indexOf(LinkID), 1);
                $PluginDataObj.val(JSON.stringify(PluginData));

                $Parent.remove();

                return false;
            });
        }

        function AddLink(PluginKey, PluginURL, LinkID, LinkName) {
            var $PluginContainerObj = $('#PluginContainer_' + Core.App.EscapeSelector(PluginKey)),
                $PluginDataObj = $('#Plugin_' + Core.App.EscapeSelector(PluginKey)),
                PluginData = JSON.parse($PluginDataObj.val()),
                $ExistingLinks = $PluginContainerObj.find('.Link_' + Core.App.EscapeSelector(LinkID)),
                $LinkContainerObj = $('<div />'),
                $URLObj = $('<a />'),
                $RemoveObj = $('<a />'),
                LinkURL = PluginURL.replace('%s', LinkID);

            if ($ExistingLinks.length > 0) {
                return;
            }

            PluginData.push(LinkID);
            $PluginDataObj.val(JSON.stringify(PluginData));

            $LinkContainerObj.addClass('Link_' + Core.App.EscapeSelector(LinkID));

            $URLObj.attr('href', LinkURL)
                .attr('target', '_blank')
                .text(LinkName)
                .appendTo($LinkContainerObj);

            $RemoveObj.attr('href', '#')
                .addClass('RemoveButton')
                .data('pluginKey', PluginKey)
                .data('linkId', LinkID)
                .append(
                    $('<i />').addClass('fa fa-minus-square-o')
                )
                .appendTo($LinkContainerObj);

            $LinkContainerObj.appendTo($PluginContainerObj);
            InitRemoveButtons();
        }

        $PluginFields.each(function () {
            var $Element = $(this),
                PluginKey = $Element.data('pluginKey'),
                PluginURL = $Element.data('pluginUrl');

            // Skip already initialized fields
            if ($Element.hasClass('ui-autocomplete-input')) {
                return true;
            }

            Core.UI.Autocomplete.Init($Element, function (Request, Response) {
                    var URL = Core.Config.Get('CGIHandle'),
                        CurrentAJAXNumber = ++AJAXCounter,
                        Data = {
                            ChallengeToken: $('#ChallengeToken').val(),
                            Action: 'AgentAppointmentPluginSearch',
                            PluginKey: PluginKey,
                            Term: Request.term + '*',
                            MaxResults: 20
                        };

                    $Element.data('AutoCompleteXHR', Core.AJAX.FunctionCall(URL, Data, function (Result) {
                        var Data = [];

                        // Check if the result is from the latest ajax request
                        if (AJAXCounter !== CurrentAJAXNumber) {
                            return false;
                        }

                        $.each(Result, function () {
                            Data.push({
                                label: this.Value,
                                key:  this.Key,
                                value: this.Value
                            });
                        });
                        Response(Data);
                    }));
            }, function (Event, UI) {
                Event.stopPropagation();
                $Element.val('').trigger('select.Autocomplete');
                AddLink(PluginKey, PluginURL, UI.item.key, UI.item.label);

                return false;
            }, PluginKey);
        });

        InitRemoveButtons();
    }

    /**
     * @name EditAppointment
     * @param {Object} Data - Hash with call and appointment data.
     * @param {Integer} Data.AppointmentID - Appointment ID.
     * @param {Integer} Data.Action - Edit action.
     * @param {Integer} Data.Subaction - Edit subaction.
     * @param {Integer} Data.Title - Appointment title.
     * @param {Integer} Data.Description - Appointment description.
     * @param {Integer} Data.Location - Appointment location.
     * @param {Integer} Data.StartYear - Appointment start year.
     * @param {Integer} Data.StartMonth - Appointment start month.
     * @param {Integer} Data.StartDay - Appointment start day.
     * @param {Integer} Data.StartHour - Appointment start hour.
     * @param {Integer} Data.StartMinute - Appointment start minute.
     * @param {Integer} Data.EndYear - Appointment end year.
     * @param {Integer} Data.EndMonth - Appointment end month.
     * @param {Integer} Data.EndDay - Appointment end day.
     * @param {Integer} Data.EndHour - Appointment end hour.
     * @param {Integer} Data.EndMinute - Appointment end minute.
     * @param {Integer} Data.AllDay - Is appointment an all-day appointment (0|1)?
     * @description
     *      This method submits an edit appointment call to the backend and refreshes the view.
      */
    TargetNS.EditAppointment = function (Data) {
        if (Core.Config.Get('Action') === 'AgentAppointmentAgendaOverview') {
            $('.OverviewControl').addClass('Loading');
        }

        Core.AJAX.FunctionCall(
            Core.Config.Get('CGIHandle'),
            Data,
            function (Response) {
                if (Response.Success) {
                    $('#calendar').fullCalendar('refetchEvents');

                    // Close the dialog
                    Core.UI.Dialog.CloseDialog($('.Dialog:visible'));

                    // Reload page if necessary
                    if (Core.Config.Get('Action') === 'AgentAppointmentAgendaOverview') {
                        window.location.reload();
                    }
                }
                else {
                    if (Response.Error) {
                        alert(Response.Error);
                    }
                }
            }
        );
    };

    /**
     * @name AgentAppointmentAgendaOverview
     * @memberof Core.Agent.AppointmentCalendar
     * @description
     *      This method initializes functionalities for AgentAppointmentAgentOverview screen.
     */
    TargetNS.AgentAppointmentAgendaOverview = function () {
        var Config = Core.Config.Get('AppointmentAgendaOverview');

        $('#AppointmentCreateButton').off('click').on('click', function () {
            Core.Agent.AppointmentCalendar.OpenEditDialog({
                Start: $.fullCalendar.moment(Config.Start).add(1, 'hours').startOf('hour'),
                End: $.fullCalendar.moment(Config.Start).add(2, 'hours').startOf('hour')
            });

            return false;
        });
        $('.MasterAction').bind('click', function () {
            var $MasterActionLink = $(this).find('.MasterActionLink'),
                AppointmentID = $MasterActionLink.data('appointmentId');

            Core.Agent.AppointmentCalendar.OpenEditDialog({
                CalEvent: {
                    id: AppointmentID
                }
            });

            return false;
        });
    }

    /**
     * @name AgentAppointmentAgendaOverview
     * @memberof Core.Agent.AppointmentCalendar
     * @description
     *      This method initializes functionalities for AgentAppointmentEdit screen.
     */
    TargetNS.AgentAppointmentEdit = function () {
        Core.Form.Validate.Init();
        TargetNS.LocationLinkInit($('#Location'), $('.LocationLink'));
        TargetNS.AllDayInit($('#AllDay'));
        TargetNS.RecurringInit({
            $Recurring: $('#Recurring'),
            $RecurrenceType: $('#RecurrenceType'),
            $RecurrenceCustomType: $('#RecurrenceCustomType'),
            $RecurrenceCustomTypeStringDiv: $('#RecurrenceCustomTypeStringDiv'),
            $RecurrenceIntervalText: $('#RecurrenceIntervalText'),
            $RecurrenceCustomWeeklyDiv: $('#RecurrenceCustomWeeklyDiv'),
            $RecurrenceCustomMonthlyDiv: $('#RecurrenceCustomMonthlyDiv'),
            $RecurrenceCustomYearlyDiv: $('#RecurrenceCustomYearlyDiv'),
            $RecurrenceLimitDiv: $('#RecurrenceLimitDiv'),
            $RecurrenceLimit: $('#RecurrenceLimit'),
            $RecurrenceUntilDiv: $('#RecurrenceUntilDiv'),
            $RecurrenceUntilDay: $('#RecurrenceUntilDay'),
            $RecurrenceCountDiv: $('#RecurrenceCountDiv')
        });
        TargetNS.NotificationInit({
            $NotificationTemplate: $('#NotificationTemplate'),
            $NotificationCustomStringDiv: $('#NotificationCustomStringDiv'),
            $NotificationCustomRelativeInput: $('#NotificationCustomRelativeInput'),
            $NotificationCustomRelativeUnitCount: $('#NotificationCustomRelativeUnitCount'),
            $NotificationCustomRelativeUnit: $('#NotificationCustomRelativeUnit'),
            $NotificationCustomRelativePointOfTime: $('#NotificationCustomRelativePointOfTime'),
            $NotificationCustomDateTimeInput: $('#NotificationCustomDateTimeInput'),
            $NotificationCustomDateTimeDay: $('#NotificationCustomDateTimeDay'),
            $NotificationCustomDateTimeMonth: $('#NotificationCustomDateTimeMonth'),
            $NotificationCustomDateTimeYear: $('#NotificationCustomDateTimeYear'),
            $NotificationCustomDateTimeHour: $('#NotificationCustomDateTimeHour'),
            $NotificationCustomDateTimeMinute: $('#NotificationCustomDateTimeMinute')
        });
        TargetNS.TeamInit($('#TeamID'), $('#ResourceID'), $('label[for="TeamID"] + div.Field p.ReadOnlyValue'), $('label[for="ResourceID"] + div.Field p.ReadOnlyValue'));

        if (Core.Config.Get('CalendarPermissionLevel') > 1) {
            TargetNS.PluginInit($('.PluginField'), $('#ChallengeToken').val());
        }

        Core.Form.Validate.SetSubmitFunction($('#EditAppointmentForm'), function (Form) {
            var Data = new Object(),
                Days = new Array(),
                MonthDays = new Array(),
                Months = new Array();

            // serialize days
            $("#RecurrenceCustomWeeklyDiv button.fc-state-active").each(function(){
                Days.push($(this).val());
            });
            $("input#Days").val(Days.join());

            // serialize month days
            $("#RecurrenceCustomMonthlyDiv button.fc-state-active").each(function(){
                MonthDays.push($(this).val());
            });
            $("input#MonthDays").val(MonthDays.join());

            // serialize months
            $("#RecurrenceCustomYearlyDiv button.fc-state-active").each(function(){
                Months.push($(this).val());
            });
            $("input#Months").val(Months.join());

            $(Form).find('input,textarea,select').each(function (Index, Element) {
                if (Element.type == 'checkbox') {
                    Data[Element.id] = $(Element).prop('checked') ? '1' : '0';
                } else {
                    Data[Element.id] = $(Element).val();
                }
            });

            TargetNS.ShowWaitingDialog();
            TargetNS.EditAppointment(Data);
        });

        $(".ButtonTable button").on("click", function() {
            $(this).toggleClass("fc-state-active");
        });

        // Duplicate appointment
        $('#EditFormCopy').on('click', function() {
            // Reset AppointmentID, remove Copy & Delete buttons
            $("#EditAppointmentForm input#AppointmentID").val("");
            $('#EditAppointmentForm').submit();
        });

        // Save the appointment
        $('#EditFormSubmit').on('click', function() {
            $('#EditAppointmentForm').submit();
        });

        // Delete the appointment
        if (Core.Config.Get('EditAppointmentID')) {
            $('#EditFormDelete').on('click', function() {
                if (window.confirm(Core.Language.Translate('Are you sure you want to delete this appointment? This operation cannot be undone.'))) {
                    Core.Agent.AppointmentCalendar.ShowWaitingDialog();
                    Core.Agent.AppointmentCalendar.EditAppointment({
                        AppointmentID: Core.Config.Get('EditAppointmentID'),
                        Action: 'AgentAppointmentEdit',
                        Subaction: 'DeleteAppointment'
                    });
                }
            });
        }

        // Edit parent appointment
        if (Core.Config.Get('EditParentID')) {
            $('#EditParent').on('click', function() {
                var Data = {
                    ChallengeToken: $("#ChallengeToken").val(),
                    Action: 'AgentAppointmentEdit',
                    Subaction: 'EditMask',
                    AppointmentID: Core.Config.Get('EditParentID')
                };

                Core.Agent.AppointmentCalendar.ShowWaitingDialog();
                Core.AJAX.FunctionCall(
                    Core.Config.Get('CGIHandle'),
                    Data,
                    function (HTML) {
                        Core.UI.Dialog.ShowContentDialog(HTML, Core.Language.Translate('Appointment'), '10px', 'Center', true, undefined, true);
                        Core.UI.InputFields.Activate($('.Dialog:visible'));
                        TargetNS.AgentAppointmentEdit();
                    }, 'html'
                );
            });
        }

        // Close the dialog
        $('#EditFormCancel').on('click', function() {
            Core.UI.Dialog.CloseDialog($('.Dialog:visible'));
        });

        // Initialize datepicker buttons.
        if (Core.Config.Get('CalendarPermissionLevel') > 2) {
            $.each(['Start', 'End', 'RecurrenceUntil', 'NotificationCustomDateTime'], function (Index, Prefix) {
                Core.UI.Datepicker.Init({
                    Day: $('#' + Core.App.EscapeSelector(Prefix) + 'Day'),
                    Month: $('#' + Core.App.EscapeSelector(Prefix) + 'Month'),
                    Year: $('#' + Core.App.EscapeSelector(Prefix) + 'Year'),
                    Hour: $('#' + Core.App.EscapeSelector(Prefix) + 'Hour'),
                    Minute: $('#' + Core.App.EscapeSelector(Prefix) + 'Minute'),
                    WeekDayStart: Core.Config.Get('CalendarWeekDayStart')
                });
            });
        }
    }

    Core.Init.RegisterNamespace(TargetNS, 'APP_MODULE');

    return TargetNS;
}(Core.Agent.AppointmentCalendar || {}));