/* Widget module */
/**
 * Datepicker
 */
(function(){
    /**
     * Contains translations per language.
     * Eventually, CalTranslate.current will be filled with the current language
     */
    var CalTranslate = {
        en: {
            cancel: 'Cancel',
            numDays: function (num) {
                return num == 1 ? '1 day' : num + ' days'
            }
        },
        nl: {
            cancel: 'Annuleren',
            numDays: function (num) {
                return num == 1 ? '1 dag' : num + ' dagen'
            }
        },
        de: {
            cancel: 'Abbrechen',
            numDays: function (num) {
                return num == 1 ? '1 Tag' : num + ' Tagen'
            }
        }
    };

    var locale = document.documentElement.getAttribute('lang'),
        date, day, month;

    if (!locale) {
        locale = navigator.language || navigator.userLanguage || navigator.browserLanguage;
    }

    /**
     * Represents a pattern.
     * A pattern can be use to match strings against, returning the detected
     * paramters, or it can be used as a template to construct a string with
     * the specified paramters.
     *
     * E.g. Pattern('This is a {variable} pattern') matches 'This is a cool pattern',
     * returning { variable: 'cool' } and can construct 'This is a nice pattern' with
     * { variable: 'nice' } provided.
     *
     * @param String pattern the pattern, with paramters between braces ({})
     */
    function Pattern(pattern) {
        var regex, lead, identifier, pair, type, index, match;

        this.pattern = pattern;
        this.replaceIndexes = [];
        this.strippedPattern = '';

        regex = '^';
        index = 1;

        while (match = /\{([^\}]+)\}/.exec(pattern)) {
            if (match.index > 0) {
                lead = pattern.substr(0, match.index);
                regex += lead.replace(/[\.\\\[\]\(\)\+\*\-]/g, '\\$&');
                this.strippedPattern += lead;
            }

            identifier = match[1];
            pair = identifier.split(':', 2);
            type = pair.length > 1 ? pair[1] : 'any';

            this.replaceIndexes[pair[0]] = index;
            if (type in Pattern.patterns) {
                regex += '(' + Pattern.patterns[type] + ')';
            } else {
                regex += '(' + type.replace(/\\/g, '\\\\') + ')';
            }
            this.strippedPattern += '{' + pair[0] + '}';

            pattern = pattern.substr(match.index + match[0].length);

            index++;
        }

        if (pattern) {
            regex += pattern.replace(/[\.\\\[\]\(\)\+\*\-]/g, '\\$&');
            this.strippedPattern += pattern;
        }

        regex += '$';

        this.regex = new RegExp(regex);
        }

        /**
         * Tries to match a string against the pattern, returning the detected
         * parameters.
         *
         * @param String string The string to match against
         * @param Boolean braceIdentifiers Set to true to include the braces ({}) in the parameter names
         */
        Pattern.prototype.match = function(string, braceIdentifiers) {
            var result = null, identifier, matches, index;

            if (matches = string.match(this.regex)) {
                result = {};

                for (identifier in this.replaceIndexes) {
                    index = this.replaceIndexes[identifier];
                    if (braceIdentifiers) {
                        result['{'+identifier+'}'] = matches[index];
                    } else {
                        result[identifier] = matches[index];
                    }
                }
            }

            return result;
        };

        /**
         * Uses the pattern as a template to generate a string
         *
         * @param Object variables The variables
         */
        Pattern.prototype.fill = function(variables) {
            var str = this.strippedPattern, key;

            for (key in variables) {
                str = str.replace(new RegExp('\\{'+key+'\\}', 'g'), variables[key]);
            }

            return str;
        };

        /**
         * Zero-pads numbers to the specified length
         *
         * @param Number number The number
         * @param Number length The desired length
         */
        Pattern.prototype.zeroPad = function(number, length) {
            var str = ''+number;
            while (str.length < length) str = '0' + str;
            return str;
        };

        /**
         * Like fill(), but uses a Date object, filling the paramters 'day',
         * 'month', 'year', 'hours', 'minutes', 'seconds' and 'tz'.
         *
         * @param Date date The date
         */
        Pattern.prototype.fillDate = function(date) {
            var props;

            if (!date) return "";

            if (!(date instanceof Date)) {
                props = {
                    day: this.zeroPad(date.day||0, 2),
                    year: this.zeroPad(date.year||0, 2),
                    hours: this.zeroPad(date.hours||0, 2),
                    minutes: this.zeroPad(date.minutes||0, 2),
                    month: this.zeroPad(date.month||0, 2),
                    seconds: this.zeroPad(date.seconds||0, 2),
                    tz: date.tz||(new Date().getTimezoneOffset())
                };
            } else {
                props = {
                    day: this.zeroPad(date.getDate(), 2),
                    year: this.zeroPad(date.getFullYear(), 2),
                    hours: this.zeroPad(date.getHours(), 2),
                    minutes: this.zeroPad(date.getMinutes(), 2),
                    month: this.zeroPad(date.getMonth()+1, 2),
                    seconds: this.zeroPad(date.getSeconds(), 2),
                    tz: date.getTimezoneOffset()
                };
            }

            return this.fill(props);
        };

    /**
     * Predefined patterns
     */
    Pattern.patterns = {
        any: '[a-zA-Z0-9_-]+',
        int: '\\d+',
        float: '\\d*\\.?\\d+',
        url_part: '[^\/]+'
    };

    function getOffset(el) {
        var offset = { top: 0, left: 0, fixed: false };
        var offsetParent = null;
        var style;

        while (el) {
            style = getComputedStyle(el);
            if (style.transform != 'none' && (m = style.transform.match(/matrix\(([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+)\)/))) {
                offset.left += Math.round(parseFloat(m[5].replace(/^\s+/, '')));
                offset.top += Math.round(parseFloat(m[6].replace(/^\s+/, '')));
            }
            if (el.offsetParent != offsetParent) {
                offset.top += el.offsetTop;
                offset.left += el.offsetLeft;
                offsetParent = el.offsetParent;
            }
            if (style.position == 'fixed') {
                offset.fixed = true;
                break;
            }
            el = el.parentElement;
        }

        return offset;
    }

    /**
     * Represents a month table in the DOM
     *
     * @param Number year The year
     * @param Number month The month
     * @param Calendar calendar The calendar
     */
    function MonthView(year, month, calendar) {
        this.element = document.createElement('div');
        this.element.className = 'cal-month';
        this.element.setAttribute('data-month', year + '-' + month);
        this.dayCells = [];

        var table = document.createElement('table');
        var part = document.createElement('thead');
        var row = document.createElement('tr');
        var cell = document.createElement('th');
        var d, i, day, className, wd;
        var cellSpan;

        var date = new Date(year, month - 1, 1);
        var startDay = date.getDay() - calendar.startOfWeek;
        var numDays = Calendar.getNumDays(year, month);

        this.startWeek = Calendar.getWeekNumber(year, month, 1);
        this.endWeek = this.startWeek + Math.ceil((numDays + startDay) / 7) - 1;

        var prevMonth = month - 1;
        var prevYear = year;
        if (prevMonth < 1) {
            prevMonth = 12;
            prevYear--;
        }
        var nextMonth = month + 1;
        var nextYear = year;
        if (nextMonth > 12) {
            nextMonth = 1;
            nextYear++;
        }
        var numDaysPrev = Calendar.getNumDays(year, prevMonth);

        if (startDay < 0) {
            startDay += 7;
        }

        cell.setAttribute('colspan', 7);
        cell.innerHTML = '<span class="cal-month-name">' + calendar.monthSymbols[month] + '</span> <span>' + year + '</span>';
        row.appendChild(cell);
        part.appendChild(row);
        table.appendChild(part);

        row = document.createElement('tr');
        part.appendChild(row);

        for (i = 0; i < 7; i++) {
            cell = document.createElement('th');
            let ds = calendar.shortDaySymbols[(i+calendar.startOfWeek)%7];
            cell.innerHTML = ds[0] + (ds.length > 0 ? '<span class="short">' + ds.substring(1) + '</span>' : '');
            row.appendChild(cell);
        }

        part = document.createElement('tbody');
        table.appendChild(part);
        row = null;

        for (day = startDay; day > 0; day--) {
            if (!row) {
                row = document.createElement('tr');
                part.appendChild(row);
            }
            cell = document.createElement('td');
            cell.innerHTML = '<span class="cal-day cal-day-othermonth" id="cal-day-' + prevYear + '-' + (prevMonth < 10 ? '0' : '') + prevMonth + '-' + (1 + numDaysPrev - day) + '">' + (1 + numDaysPrev - day) + '</span>';

            row.appendChild(cell);
        }

        for (day = 1; day <= numDays; day++) {
            d = (startDay + day - 1) % 7;

            if (d == 0) {
                row = document.createElement('tr');
                part.appendChild(row);
            }
            cell = document.createElement('td');
            className = 'cal-day';
            wd = (d + calendar.startOfWeek)%7;
            if (wd == 0 || wd == 6) {
                className += ' cal-day-weekend';
            }
            cellSpan = document.createElement('span');
            cellSpan.className = className;
            cellSpan.setAttribute('id', 'cal-day-' + year + '-' + (month < 10 ? '0' : '') + month + '-' + (day < 10 ? '0' : '') + day);
            cellSpan.innerHTML = day;
            cell.appendChild(cellSpan);
            this.dayCells.push(cellSpan);
            // cell.innerHTML = '<span class="' + className + '" id="cal-day-' + year + '-' + (month < 10 ? '0' : '') + month + '-' + (day < 10 ? '0' : '') + day + '">' + day + '</span>';
            row.appendChild(cell);
        }

        if (row) {
            d++;
            for (i = d; i < 7; i++) {
                cell = document.createElement('td');
                // cell.innerHTML = '&nbsp;';
                cell.innerHTML = '<span class="cal-day cal-day-othermonth" id="cal-day-' + nextYear + '-' + (nextMonth < 10 ? '0' : '') + nextMonth + '-' + (1 + i - d) + '">' + (1 + i - d) + '</span>';
                row.appendChild(cell);
            }
        }

        this.element.appendChild(table);
        }

        MonthView.prototype.addRangeElement = function(rangeElement) {
            this.element.appendChild(rangeElement);
        };

        MonthView.prototype.unmarkDates = function(className) {
            var i, cells = this.element.querySelectorAll('.cal-day.'+className);
            for (i = 0; i < cells.length; i++) {
                cells[i].classList.remove(className);
            }
        };

        MonthView.prototype.spansWeek = function(week) {
            return week >= this.startWeek && week <= this.endWeek;
        };

        MonthView.prototype.highlightRange = function(className, startDay, numDays, isRangeStart, isRangeEnd) {
            var index = startDay - 1,
                i;

            for (i = 0; i < numDays; i++) {
                if (index + i < this.dayCells.length) {
                    this.dayCells[index + i].classList.add(
                        'cal-day-range' +
                        (isRangeStart && i == 0 ? '-start' : '') +
                        (isRangeEnd && i == numDays-1 ? '-end' : ''));
                    this.dayCells[index + i].classList.add('range-' + className);
                }
            }
        };

        MonthView.prototype.unhighlightRanges = function() {
            var i, cl, cls, j;

            for (i = 0; i < this.dayCells.length; i++) {
                cl = [];
                for (j = 0; j < this.dayCells[i].classList.length; j++) {
                    cls = this.dayCells[i].classList[j];
                    if (cls.substr(0, 13) != 'cal-day-range' && cls.substr(0, 6) != 'range-') {
                        cl.push(cls);
                    }
                }
                this.dayCells[i].className = cl.join(' ');
                // this.dayCells[i].className = 'cal-day ' + (this.dayCells[i].classList.contains('today') ? ' today' : '');
                // this.dayCells[i].className = this.dayCells[i].className
                //     .replace(/\s+cal-day-range\b/, '')
                //     .replace(/\s+range-[^\s]+/, '');
            }
        };

        MonthView.prototype.clearMarkedDates = function() {
            var i, cl, cls, j;

            for (i = 0; i < this.dayCells.length; i++) {
                cl = [];
                for (j = 0; j < this.dayCells[i].classList.length; j++) {
                    cls = this.dayCells[i].classList[j];
                    if (cls.substr(0, 7) != 'marked-') {
                        cl.push(cls);
                    }
                }
                this.dayCells[i].className = cl.join(' ');
                // this.dayCells[i].className = 'cal-day ' + (this.dayCells[i].classList.contains('today') ? ' today' : '');
                // this.dayCells[i].className = this.dayCells[i].className
                //     .replace(/\s+marked-[^\s]+/, '');
            }
        };

        MonthView.prototype.setMarkedDates = function(dates) {
            var i, day;
            for (i = 0; i < dates.length; i++) {
                day = this.element.querySelector('#cal-day-' + dates[i].date);
                if (day) {
                    day.classList.add('marked-' + dates[i].className);
                }
            }
        };

    /**
     * Represents a date range in the DOM
     *
     * @param CalendarRange range The range to display
     * @param CalendarView calendarView The calendar display
     * @param String className The class name for the range
     */
    function DateRangeDisplay(range, calendarView, className) {
        this.range = range;
        this.calendarView = calendarView;
        this.className = className;

        this.spans = [];
        }

        DateRangeDisplay.prototype.render = function() {
            var year = this.range.start.year;
            var month = this.range.start.month;
            var day = this.range.start.day;
            var end = this.range.end.year * 100 + this.range.end.month;
            var numDays = 31;
            var monthView, isStart = true, isEnd = false;

            while (year * 100 + month <= end) {
                if (year == this.range.end.year && month == this.range.end.month) {
                    numDays = 1 + this.range.end.day - day;
                    isEnd = true;
                }

                monthView = this.calendarView.getMonthView(year, month);
                monthView.highlightRange(this.className, day, numDays, isStart, isEnd);

                if (++month >= 12) {
                    year++;
                    month = 1;
                }
                day = 1;
                isStart = false;
            }
        };

        DateRangeDisplay.prototype.unrender = function() {
            var year = this.range.start.year;
            var month = this.range.start.month;
            var day = this.range.start.day;
            var end = this.range.end.year * 100 + this.range.end.month;
            var monthView;

            while (year * 100 + month <= end) {
                monthView = this.calendarView.getMonthView(year, month);
                monthView.unhighlightRanges();
                if (++month >= 12) {
                    year++;
                    month = 1;
                }
            }
        };

    function SelectedDateView(calendarView, calendar) {
        this.calendarView = calendarView;
        this.calendar = calendar;

        this.element = document.createElement('div');
        this.element.className = 'cal-selected-date';
        calendarView.header.appendChild(this.element);
        }

        SelectedDateView.prototype.setDate = function(date) {
            var dateObj = date.getDateObj();

            this.element.innerHTML = '<div class="cal-selected-day">' + date.day + '</div>' +
                '<div class="cal-selected-date-content">' +
                    '<div class="cal-selected-date-month">' + this.calendar.monthSymbols[date.month] + ' ' + date.year + '</div>' +
                    '<div class="cal-selected-date-weekday">' + this.calendar.daySymbols[dateObj.getDay()] + '</div>' +
                '</div>';
        };


    /**
     * Represents an interactive calendar in the DOM
     *
     * @param Calendar calendar The calendar
     */
    function CalendarView(calendar, options) {
        options = options||{};

        this.calendar = calendar;
        this.element = document.createElement('div');
        this.element.className = 'cal-view';

        if (('inline' in options) && options.inline) {
            this.element.classList.add('cal-visible');
            this.element.classList.add('cal-inline');
            this.isInline = true;
        } else {
            this.dimmer = document.createElement('div');
            this.dimmer.className = 'cal-dimmer';
            this.isInline = false;
        }

        this.header = document.createElement('div');
        this.header.className = 'cal-header';
        this.element.appendChild(this.header);

        this.header.innerHTML = '<span class="cal-header-arrow"></span>'

        this.monthContainer = document.createElement('div');
        this.monthContainer.className = 'cal-months';
        this.element.appendChild(this.monthContainer);

        this.monthSpan = 2;
        this.forcedMonthSpan = null;
        this.monthViews = {};
        this.rangeSelectStart = null;
        this.currRange = null;
        this.currCell = null;
        this.currYear = null;
        this.currMonth = null;

        this.onSelectDate = null;
        this.onSelectRange = null;

        if ('ontouchstart' in window) {
            this.element.addEventListener('touchend', this.onClick.bind(this));
        } else {
            this.element.addEventListener('click', this.onClick.bind(this));
            this.element.addEventListener('mousemove', this.onMouseMove.bind(this));
            // this.dimmer = null;
        }

        this.tooltip = document.createElement('div');
        this.tooltip.className = 'cal-tooltip';
        this.tooltip.innerHTML = '42 dagen';
        this.element.appendChild(this.tooltip);

        this.selectedDateStart = new SelectedDateView(this, calendar);
        this.selectedDateEnd = new SelectedDateView(this, calendar);
        this.selectedDateStart.setDate(CalendarDate.today());

        var actions = document.createElement('div');
        actions.className = 'cal-actions';
        var button = document.createElement('button');
        button.type = 'button';
        button.className = 'cal-button';
        button.innerHTML = CalTranslate.current.cancel;
        actions.appendChild(button);
        this.element.appendChild(actions);
        button.addEventListener('click', this.close.bind(this));

        this.prevButton = document.createElement('button');
        this.prevButton.type = 'button';
        this.prevButton.className = 'cal-nav cal-prev';
        this.element.appendChild(this.prevButton);
        this.prevButton.addEventListener('click', this.showPrevMonth.bind(this));

        this.nextButton = document.createElement('button');
        this.nextButton.type = 'button';
        this.nextButton.className = 'cal-nav cal-next';
        this.element.appendChild(this.nextButton);
        this.nextButton.addEventListener('click', this.showNextMonth.bind(this));

        this.cancelClose = false;
        this.rangeDisplays = [];
        this.startDate = null;
        this.weekendsDisabled = false;
        this.isTooltipEnabled = true;
        this.showOtherMonths = false;

        this.closeTimeout = null;

        this.markedDates = {};
        }

        CalendarView.prototype.getMonthView = function(year, month) {
            var key = year + '-' + month;
            if (!(key in this.monthViews)) {
                this.monthViews[key] = new MonthView(year, month, this.calendar);
            }

            return this.monthViews[key];
        };

        CalendarView.prototype.showMonth = function(year, month) {
            var monthSpan = this.forcedMonthSpan ? this.forcedMonthSpan : (document.body.clientWidth < 640 ? 1 : 2),
                child, next, i;

            // this.monthContainer.innerHTML = '';
            // Gracefully remove
            child = this.monthContainer.firstChild;
            while (child) {
                next = child.nextSibling;
                child.parentElement.removeChild(child);
                child = next;
            }

            if (!this.isInline) {
                this.element.style.width = (320 * monthSpan) + 'px';
                if (document.body.clientWidth < 640) {
                    this.element.style.width = (document.body.clientWidth - 20) + 'px';
                } else if (monthSpan == 1) {
                    this.element.style.width = '500px';
                }
            }

            this.element.className = this.element.className.replace(/\scal-span-\d+/, '') + ' cal-span-' + monthSpan;

            this.currYear = year;
            this.currMonth = month;

            for (i = 0; i < monthSpan; i++) {
                md = this.getMonthView(year, month);
                this.monthContainer.appendChild(md.element);

                md.clearMarkedDates();
                if ((year in this.markedDates) && (month in this.markedDates[year])) {
                    md.setMarkedDates(this.markedDates[year][month]);
                }

                if (++month > 12) {
                    year++;
                    month = 1;
                }
            }

            this.respectStartDate();
        };

        CalendarView.prototype.showPrevMonth = function(event) {
            if (event) {
                event.preventDefault();
            }

            var month = this.currMonth - 1, year = this.currYear;

            if (month < 1) {
                year--;
                month = 12;
            }

            this.showMonth(year, month);
        };

        CalendarView.prototype.showNextMonth = function(event) {
            if (event) {
                event.preventDefault();
            }

            var month = this.currMonth + 1, year = this.currYear;

            if (month > 12) {
                year++;
                month = 1;
            }

            this.showMonth(year, month);
        };

        CalendarView.prototype.markDate = function(date, className, markCell) {
            var id = 'cal-day-' + date.year + '-' + (date.month < 10 ? '0' : '') + date.month + '-' + (date.day < 10 ? '0' : '') + date.day;
            var cell = this.monthContainer.querySelector('#' + id);
            if (cell) {
                (markCell ? cell.parentElement : cell).classList.add(className);
            }
        };

        CalendarView.prototype.markDateRange = function(dateRange, className, selectInHeader, dontAppendToRanges) {
            var rangeDisplay = new DateRangeDisplay(dateRange, this, className);
            rangeDisplay.render();
            if (selectInHeader) {
                this.selectedDateStart.setDate(dateRange.start);
                this.selectedDateEnd.setDate(dateRange.end);
            }
            if (!dontAppendToRanges) {
                this.rangeDisplays.push(rangeDisplay);
            }
            return rangeDisplay;
        };

        CalendarView.prototype.unmarkDate = function(date, className, unmarkCell) {
            if (date) {
                var id = 'cal-day-' + date.year + '-' + (date.month < 10 ? '0' : '') + date.month + '-' + (date.day < 10 ? '0' : '') + date.day;
                var cell = this.monthContainer.querySelector('#' + id);
                if (cell) {
                    (unmarkCell ? cell.parentElement : cell).classList.remove(className);
                }
            } else {
                var key;
                for (key in this.monthViews) {
                    this.monthViews[key].unmarkDates(className);
                }
            }
        };

        CalendarView.prototype.clearAllRanges = function() {
            var i;

            this.markDate(CalendarDate.today(), 'today');

            for (i = 0; i < this.rangeDisplays.length; i++) {
                this.rangeDisplays[i].unrender();
            }
            this.rangeDisplays = [];
        };

        CalendarView.prototype.startRangeSelect = function(date) {
            this.rangeSelectStart = date;
            this.currRange = new CalendarRange(date, date);
            this.rangeDisplay = this.markDateRange(this.currRange, 'highlight', true, true);
        };

        CalendarView.prototype.onClick = function(e) {
            if (e.target.classList.contains('cal-day')) {
                e.preventDefault();

                if (e.target.classList.contains('disabled')) {
                    return;
                }

                var m = e.target.getAttribute('id').match(/^cal-day-(\d{4})-(\d{1,2})-(\d{1,2})$/);
                var date = new CalendarDate(parseInt(m[1]), parseInt(m[2]), parseInt(m[3]));
                var endsRange = this.rangeSelectStart != null;

                if (this.onSelectDate) {
                    this.onSelectDate(date, endsRange);
                }

                if (endsRange) {
                    this.rangeDisplay.unrender();
                    this.tooltip.style.display = 'none';
                    this.currRange = new CalendarRange(this.rangeSelectStart, date);

                    if (this.onSelectRange) {
                        this.onSelectRange(this.currRange);
                    }

                    this.rangeSelectStart = null;
                    this.currRange = null;
                }
            }
        };

        CalendarView.prototype.onMouseMove = function(e) {
            if (e.target.classList.contains('cal-day') && e.target != this.currCell && this.rangeSelectStart) {
                if (e.target.classList.contains('disabled')) {
                    return;
                }

                var m = e.target.getAttribute('id').match(/^cal-day-(\d{4})-(\d{1,2})-(\d{1,2})$/);
                var date = new CalendarDate(parseInt(m[1]), parseInt(m[2]), parseInt(m[3]));

                this.currCell = e.target;

                if (this.currRange) {
                    this.rangeDisplay.unrender();
                }
                this.currRange = new CalendarRange(this.rangeSelectStart, date);
                this.rangeDisplay = this.markDateRange(this.currRange, 'highlight', true);
                this.showTooltip(e.target, this.currRange.getNumDays());
            }
        };

        CalendarView.prototype.showTooltip = function(cell, days) {
            if (this.isTooltipEnabled && cell) {
                var left = 0, top = 0, el = cell;
                while (el && el != this.element) {
                    left += el.offsetLeft;
                    top += el.offsetTop;
                    el = el.offsetParent;
                }

                this.tooltip.innerHTML = CalTranslate.current.numDays(days);
                this.tooltip.style.display = 'block';
                this.tooltip.style.left = (left + (cell.offsetWidth - this.tooltip.offsetWidth) / 2) + 'px';
                this.tooltip.style.top = (top - this.tooltip.offsetHeight - 5) + 'px';
            }
        };

        CalendarView.prototype.openAtPosition = function(offset, activator) {
            var monthSpan = this.forcedMonthSpan ? this.forcedMonthSpan : (document.body.clientWidth < 640 ? 1 : 2);
            var deisredWidth = monthSpan == 1 ? 500 : 640;

            activator._calendarView = true;
            if (this.closeTimeout) {
                clearTimeout(this.closeTimeout);
                this.closeTimeout = null;
            }
            if (document.body.clientWidth < 640) {
                this.element.style.position = 'fixed';
                this.element.style.left = '10px';
                this.element.style.top = ((Math.max(document.documentElement.clientHeight, window.innerHeight || 0) - 355) / 2) + 'px';
                if (this.dimmer) {
                    this.dimmer.style.display = 'block';
                }
            } else {
                var maxHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0) + (offset.fixed ? 0 : window.pageYOffset);
                if (offset.left + deisredWidth + 20 > document.body.clientWidth) {
                    offset.left = (offset.left + activator.offsetWidth) - deisredWidth;
                }
                if (maxHeight > 0 && offset.top + 300 > maxHeight) {
                    offset.top -= 300 + activator.offsetHeight;
                }
                this.element.style.position = offset.fixed ? 'fixed' : 'absolute';
                this.element.style.left = offset.left + 'px';
                this.element.style.top = (offset.top + (activator ? activator.offsetHeight : 0)) + 'px';
            }
            this.element.style.display = 'block';

            this.cancelClose = true;

            var el = this.element,
                dim = this.dimmer;

            setTimeout(function(){
                el.classList.add('cal-visible');
                if (dim && document.body.clientWidth < 640) {
                    dim.classList.add('cal-visible');
                }
            }, 100);


            setTimeout(function(){
                CalendarView.instance().cancelClose = false;
            }, 1000);
        };

        CalendarView.prototype.close = function(event) {
            if (event) {
                event.preventDefault();
            }

            if (!this.cancelClose) {
                this.element.classList.remove('cal-visible');
                if (this.dimmer) {
                    this.dimmer.classList.remove('cal-visible');
                }

                var el = this.element,
                    dim = this.dimmer;

                setTimeout(function(){
                    el.style.display = 'none';
                    if (dim) {
                        dim.style.display = 'none';
                    }
                }, 400);
            }
        };

        CalendarView.prototype.delayedClose = function () {
            if (this.closeTimeout) {
                clearTimeout(this.closeTimeout);
            }
            this.closeTimeout = setTimeout(function(){
                CalendarView.instance().close();
            }, 100);
        }

        CalendarView.prototype.setStartDate = function(startDate) {
            this.startDate = startDate;
            if (startDate) {
                if (this.currYear * 100 + this.currMonth < startDate.year * 100 + startDate.month) {
                    this.showMonth(startDate.year, startDate.month);
                } else {
                    this.respectStartDate();
                }
            } else {
                this.respectStartDate();
            }
        };

        CalendarView.prototype.disableWeekends = function(disabled) {
            this.weekendsDisabled = disabled;
            this.respectStartDate();
        };

        CalendarView.prototype.respectStartDate = function() {
            var days, day, i, check;

            days = this.monthContainer.querySelectorAll('.cal-day');

            if (this.startDate) {
                check = 'cal-day-' + this.startDate.year + '-' +
                    (this.startDate.month < 10 ? '0' : '') + this.startDate.month + '-' +
                    (this.startDate.day < 10 ? '0' : '') + this.startDate.day;

                for (i = 0; i < days.length; i++) {
                    day = days[i];
                    if (day.getAttribute('id') < check) {
                        day.classList.add('disabled');
                    } else {
                        if (this.weekendsDisabled && day.classList.contains('cal-day-weekend')) {
                            day.classList.add('disabled');
                        } else {
                            day.classList.remove('disabled');
                        }
                    }
                }

                if (this.currYear * 100 + this.currMonth == this.startDate.year * 100 + this.startDate.month) {
                    this.prevButton.style.display = 'none';
                } else {
                    this.prevButton.style.display = 'block';
                }
            } else {
                for (i = 0; i < days.length; i++) {
                    day = days[i];
                    if (this.weekendsDisabled && day.classList.contains('cal-day-weekend')) {
                        day.classList.add('disabled');
                    } else {
                        day.classList.remove('disabled');
                    }
                }

                this.prevButton.style.display = 'block';
            }
        };

        CalendarView.prototype.showHeader = function(show) {
            if (show) {
                this.element.classList.add('cal-with-header');
            } else {
                this.element.classList.remove('cal-with-header');
            }
        };

        CalendarView.prototype.enableTooltip = function(enable) {
            this.isTooltipEnabled = (typeof enable == 'undefined') ? true : enable;
        };

        CalendarView.prototype.enableOtherMonths = function(enable) {
            this.showOtherMonths = (typeof enable == 'undefined') ? true : enable;
            if (this.showOtherMonths) {
                this.element.classList.add('cal-with-othermonths');
            } else {
                this.element.classList.remove('cal-with-othermonths');
            }
        };

        CalendarView.prototype.forceMonthSpan = function(span) {
            this.forcedMonthSpan = span;
        };

        CalendarView.prototype.setMarkedDates = function(dates) {
            this.markedDates = dates;
        }

    CalendarView.instance = function () {
        if (!CalendarView._instance) {
            CalendarView._instance = new CalendarView(Calendar.instance());
            if (CalendarView._instance.dimmer) {
                document.body.appendChild(CalendarView._instance.dimmer);
            }
            document.body.appendChild(CalendarView._instance.element);
        }
        return CalendarView._instance;
    };

    function Calendar () {
        this.daySymbols = [];
        this.shortDaySymbols = [];
        for (day = 1; day <= 7; day++) {
            date = new Date(2018, 6, day);
            this.daySymbols.push(date.toLocaleDateString(locale, { weekday: 'long' }));
            this.shortDaySymbols.push(date.toLocaleDateString(locale, { weekday: 'short' }));
        }

        this.monthSymbols = [ '' ];
        for (month = 0; month < 12; month++) {
            this.monthSymbols.push((new Date(2018, month, 1)).toLocaleDateString(locale, { month: 'long' }));
        }

        this.startOfWeek = Picker.startOfWeek !== null ? Picker.startOfWeek : 0;

        if (!CalTranslate.current) {
            var lang = locale.replace(/-[^$]*$/, '').toLowerCase();
            if (!(lang in CalTranslate)) {
                lang = 'en';
            }
            CalTranslate.current = CalTranslate[lang];
        }
    }

    Calendar.getNumDays = function(year, month) {
        if (month == 2) {
            var isLeapYear = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
            return isLeapYear ? 29 : 28;
        } else if (month < 8) {
            return 30 + (month % 2);
        }
        return 31 - (month % 2);
    };

    Calendar.instance = function() {
        if (!Calendar._instance) {
            Calendar._instance = new Calendar();
        }
        return Calendar._instance;
    };

    Calendar.monthOffsets = [ 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ];
    Calendar.getWeekNumber = function(year, month, day) {
        var lyAdd = 0,
            yearDay,
            jan1Day = (new Date(year, 0, 1)).getDay(),
            week;

        if (month > 2) {
            lyAdd = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) ? 1 : 0;
        }

        yearDay = this.monthOffsets[month] + lyAdd + day;
        week = Math.ceil((yearDay + jan1Day) / 7);
        if (jan1Day == 0 || jan1Day >= 5) {
            week--;
            if (week < 1) week = 52;
        }

        return week;
    };

    function CalendarDate(year, month, day) {
        if (arguments.length == 1 && typeof year == 'string') {
            var m = year.match(/(\d{4})-(\d{1,2})-(\d{1,2})/);
            if (m) {
                this.year = parseInt(m[1]);
                this.month = parseInt(m[2]);
                this.day = parseInt(m[3]);
            } else {
                console.error('Invalid date: ' + year);
            }
        } else {
            this.year = parseInt(year);
            this.month = parseInt(month);
            this.day = parseInt(day);
        }
        this.key = this.year + this.lpad(month, 2) + this.lpad(day, 2);
        this.numDays = Calendar.getNumDays(year, month);
        }

        CalendarDate.prototype.lpad = function(num, length) {
            var str = ''+num;
            while (str.length < length) {
                str = '0' + str;
            }
            return str;
        };

        CalendarDate.prototype.decreaseWith = function(days) {
            var i;
            for (i = 0; i < days; i++) {
                if (--this.day < 1) {
                    if (--this.month < 1) {
                        this.year--;
                        this.month = 12;
                    }
                    this.numDays = Calendar.getNumDays(this.year, this.month);
                    this.day = this.numDays;
                }
            }
            this.key = this.year + this.lpad(this.month, 2) + this.lpad(this.day, 2);
        };

        CalendarDate.prototype.increaseWith = function(days) {
            var i;
            if (days < 0) this.decreaseWith(-days);
            for (i = 0; i < days; i++) {
                if (++this.day > this.numDays) {
                    if (++this.month > 12) {
                        this.year++;
                        this.month = 1;
                    }
                    this.day = 1;
                    this.numDays = Calendar.getNumDays(this.year, this.month);
                }
            }
            this.key = this.year + this.lpad(this.month, 2) + this.lpad(this.day, 2);
        };

        CalendarDate.prototype.clone = function() {
            return new CalendarDate(this.year, this.month, this.day);
        };

        CalendarDate.prototype.getUnixTime = function() {
            var date = new Date(this.year, this.month - 1, this.day);
            return date.getTime() / 1000;
        };

        CalendarDate.prototype.getDateObj = function() {
            return new Date(this.year, this.month - 1, this.day);
        };

        CalendarDate.prototype.toString = function() {
            return this.year + "-" + this.lpad(this.month, 2) + "-" + this.lpad(this.day, 2);
        };

    CalendarDate.today = function() {
        var date = new Date();
        return new CalendarDate(date.getFullYear(), date.getMonth()+1, date.getDate());
    };

    CalendarDate.fromString = function (string) {
        var match = string.match(/^(\d{4})-(\d{2})-(\d{2})/);
        if (match) {
            return new CalendarDate(parseInt(match[1]), parseInt(match[2]), parseInt(match[3]));
        }
        return null;
    }

    function CalendarRange(start, end) {
        this.start = start;
        this.end = end || start;
        if (this.end.key < this.start.key) {
            var swap = this.end;
            this.end = this.start;
            this.start = swap;
        }
        }

        CalendarRange.prototype.each = function(callback) {
            var date = this.start.clone(),
                index = 0;

            while (date.key <= this.end.key) {
                callback(date, index, date.key == this.end.key);
                date.increaseWith(1);
                index++;
            }
        };

        CalendarRange.prototype.getNumDays = function() {
            var diffSeconds = this.end.getUnixTime() - this.start.getUnixTime();
            return Math.round(1 + (diffSeconds / 86400));
        };

    /** THE PICKERS **/
    function Picker(element, options) {
        this.construct(element, options);
        }

        Picker.prototype.construct = function (element, options) {
            this.element = element;
            this.element.picker = this;
            this.date = null;
            this.range = null;
            this.changeCallback = null;
            this.startRangeCallback = null;
            this.startDate = null;
            this.weekendsDisabled = false;
            this.separator = ' t/m ';
            this.showHeader = true;
            this.showDayCount = true;
            this.showOtherMonths = false;
            this.monthSpan = null;
            this.markedDates = {};
            this.placeholder = "";

            if (options && ('showHeader' in options) && !options.showHeader) {
                this.showHeader = false;
            }
            if (options && ('showDayCount' in options) && !options.showDayCount) {
                this.showDayCount = false;
            }
            if (options && ('separator' in options)) {
                this.separator = options.separator;
            }
            if (options && ('placeholder' in options)) {
                this.placeholder = options.placeholder;
            }

            if (options && ('inline' in options) && options.inline) {
                this.calendarView = new CalendarView(Calendar.instance(), { inline: true });
                this.element.parentElement.insertBefore(this.calendarView.element, this.element.nextSibling);
                this.element.style.position = 'absolute';
                this.element.style.zIndex = -100;
                this.element.style.pointerEvents = 'none';
                this.element.style.opacity = 0;
                this.isInline = true;
            } else {
                this.calendarView = CalendarView.instance();
                this.isInline = false;
                this.element.type = "text";
            }


            var dateFormat;
            if (options && ('format' in options)) {
                dateFormat = options.format;
            } else {
                dateFormat = 'yyyy-mm-dd';
            }

            this.dateFormat = new Pattern(dateFormat.toLowerCase().replace('yyyy', '{year}').replace('mm', '{month}').replace('dd', '{day}'));

            this.displayInput;
            if (options && ('displayFormat' in options)) {
                this.displayFormat = options.displayFormat;
                this.displayInput = this.element.cloneNode();
                this.displayInput.name = "";
                this.element.id = "";
                this.element.type = "hidden";
                this.element.parentElement.insertBefore(this.displayInput, this.element.nextSibling);
                if (!this.isInline) {
                    this.displayInput.addEventListener('focus', this.onFocus.bind(this));
                }
            } else if (!this.isInline) {
                this.element.addEventListener('focus', this.onFocus.bind(this));
            }

            if (options && ('onChange' in options)) {
                this.changeCallback = options.onChange;
            }
            if (options && ('onStartRange' in options)) {
                this.startRangeCallback = options.onStartRange;
            }

            if (options && ('startDate' in options)) {
                var m;
                if (options.startDate == 'today') {
                    this.startDate = CalendarDate.today();
                } else if (m = options.startDate.match(/^today\s*(\+|-)\s*(\d+)/)) {
                    this.startDate = CalendarDate.today();
                    this.startDate.increaseWith(parseInt(m[1] + m[2]));
                } else {
                    var sd = this.dateFormat.match(options.startDate);
                    if (sd) {
                        this.startDate = new CalendarDate(sd.year, sd.month, sd.day);
                    }
                }
            }

            if (options && ('disableWeekends' in options)) {
                this.weekendsDisabled = options.disableWeekends;
            }
            if (options && ('showOtherMonths' in options)) {
                this.showOtherMonths = options.showOtherMonths;
            }
            if (options && ('monthSpan' in options)) {
                this.monthSpan = options.monthSpan;
            }
            if (options && ('markedDates' in options)) {
                var i, date;
                for (i = 0; i < options.markedDates.length; i++) {
                    date = new CalendarDate(options.markedDates[i].date);
                    if (!(date.year in this.markedDates)) {
                        this.markedDates[date.year] = {};
                    }
                    if (!(date.month in this.markedDates[date.year])) {
                        this.markedDates[date.year][date.month] = [];
                    }
                    this.markedDates[date.year][date.month].push(Object.create(options.markedDates[i]));
                }
            }

            if (element.hasAttribute("value")) {
                var value = element.getAttribute("value");
                this.setValue(value);

                if (this.range) {
                    element.value =
                        this.dateFormat.fillDate(this.range.start) +
                        this.separator +
                        this.dateFormat.fillDate(this.range.end);
                } else {
                    element.value = this.dateFormat.fillDate(this.date);
                }
                if (!this.date && this.displayInput) this.displayInput.value = value;
            }

            if (this.isInline) {
                this.prepareCalendarView();
            }
        }

        Picker.prototype.setValue = function(value) {}

        Picker.prototype.prepareCalendarView = function() {
            var date = this.date ? this.date : CalendarDate.today();
            this.calendarView.onSelectDate = this.onSelectDate.bind(this);
            if ('onSelectRange' in this) {
                this.calendarView.onSelectRange = this.onSelectRange.bind(this);
            }
            this.calendarView.enableTooltip(this.showDayCount);
            this.calendarView.showHeader(this.showHeader);
            this.calendarView.setMarkedDates(this.markedDates);
            this.calendarView.forceMonthSpan(this.monthSpan);
            this.calendarView.showMonth(date.year, date.month);
            this.calendarView.setStartDate(this.startDate);
            this.calendarView.disableWeekends(this.weekendsDisabled);
            this.calendarView.enableOtherMonths(this.showOtherMonths);
            this.calendarView.clearAllRanges();
            this.calendarView.unmarkDate(null, 'selected');
            if (this.date) {
                this.calendarView.markDate(this.date, 'selected');
            } else if (this.range) {
                this.calendarView.showMonth(this.range.start.year, this.range.start.month);
                this.calendarView.markDateRange(this.range, 'selected', true);
            }
        }

        Picker.prototype.onFocus = function(e) {
            var offset = getOffset(this.displayInput||this.element);

            this.prepareCalendarView();
            this.calendarView.openAtPosition(offset, this.displayInput||this.element);

            if (document.body.clientWidth < 640) {
                (this.displayInput||this.element).blur();
            }
        };

        Picker.prototype.onSelectDate = function(date, endsRange) {};

    Picker.startOfWeek = null;
    window.Picker = Picker;

    function DatePicker(element, options) {
        this.construct(element, options);
        }

        DatePicker.prototype = Object.create(Picker.prototype);
        DatePicker.prototype.setValue = function(value, trigger) {
            var match;

            if (!value) {
                if (this.displayInput) {
                    this.displayInput.value = this.placeholder;
                }

                this.element.value = "";
                this.date = null;
                this.calendarView.unmarkDate(null, 'selected');
            } else {
                match = this.dateFormat.match(value);

                if (match) {
                    this.date = new CalendarDate(match.year, match.month, match.day);

                    if (this.displayInput) {
                        this.displayInput.value = this.date.getDateObj().toLocaleDateString(locale, this.displayFormat);
                    }

                    if (trigger && this.onSelectDate) {
                        this.onSelectDate(this.date);
                    }
                }
            }
        };

        DatePicker.prototype.onSelectDate = function(date, endsRange) {
            this.date = date;

            this.element.value = this.dateFormat.fillDate(date);
            this.element.dispatchEvent(new Event("change"));
            if (this.displayInput) {
                this.displayInput.value = date.getDateObj().toLocaleDateString(locale, this.displayFormat);
            }
            if (this.isInline) {
                this.calendarView.unmarkDate(null, 'selected');
                if (this.date) {
                    this.calendarView.markDate(this.date, 'selected');
                }
            } else {
                this.calendarView.close();
            }

            if (this.changeCallback) {
                this.changeCallback.call(this.element, date, this.element.value);
            }
        };

    function DateRangePicker(element, options) {
        this.construct(element, options);
        }

        DateRangePicker.prototype = Object.create(Picker.prototype);
        DateRangePicker.prototype.setValue = function(value) {
            var pair = value ? value.split(this.separator) : null, start, end, match;

            if (!value) {
                if (this.displayInput) {
                    this.displayInput.value = this.placeholder;
                }

                this.element.value = "";
                this.range = null;
                this.calendarView.clearAllRanges();
            } else {
                if (pair[0]) {
                    match = this.dateFormat.match(pair[0]);

                    if (match) {
                        start = new CalendarDate(match.year, match.month, match.day);

                        if (pair.length > 1 && pair[1]) {
                            match = this.dateFormat.match(pair[1]);
                            if (match) {
                                end = new CalendarDate(match.year, match.month, match.day);
                            }
                        }

                        if (!end) {
                            end = start;
                        }

                        this.range = new CalendarRange(start, end);
                    }
                }
            }
        };

        DateRangePicker.prototype.onSelectDate = function(date, endsRange) {
            if (!endsRange) {
                if (this.range) {
                    this.range = null;
                }
                this.calendarView.clearAllRanges();
                this.calendarView.startRangeSelect(date);
                if (this.startRangeCallback) {
                    this.startRangeCallback.call(this.element, date);
                }
            }
        };

        DateRangePicker.prototype.onSelectRange = function(range) {
            this.range = range;
            this.calendarView.markDateRange(this.range, 'selected', true);

            this.element.value =
                this.dateFormat.fillDate(range.start) +
                this.separator +
                this.dateFormat.fillDate(range.end);
            this.element.dispatchEvent(new Event("change", { bubbles: true }));

            if (!this.isInline) {
                this.calendarView.close();
            }

            if (this.changeCallback) {
                this.changeCallback.call(this.element, range, this.element.value);
            }
        };

    window.RDatePicker = DatePicker;
    window.RDateRangePicker = DateRangePicker;
    window.RCalendarDate = CalendarDate;
    window.RCalendarRange = CalendarRange;

    window.addEventListener('click', function(e) {
        if (!e.target.datePicker) {
            var el = e.target;
            while (el && !el.classList.contains('cal-view') && !("_calendarView" in el)) {
                el = el.parentElement;
            }
            if (!el) {
                CalendarView.instance().delayedClose();
            }
        }
    });

})();

/**
 * GridBoard
 */
let Toggler = {
    activeVisible: null,

    makeVisible: function (element) {
        if (this.activeVisible) {
            this.makeInvisible(this.activeVisible);
        }
        let el = element;
        let top = 0;
        while (el) {
            top += el.offsetTop;
            el = el.offsetParent;
        }
        if (top + element.offsetHeight > window.pageYOffset + window.innerHeight) {
            let scrollTo = top - 75;
            let step = Math.abs(window.pageYOffset - scrollTo) / 10;
            let iv = setInterval(() => {
                if (window.pageYOffset + step >= scrollTo) {
                    window.scrollTo(window.pageXOffset, scrollTo);
                    clearInterval(iv);
                } else {
                    window.scrollTo(window.pageXOffset, window.pageYOffset + step);
                }
            }, 20);
        }
        element.classList.add("visible");
        this.activeVisible = element;
    },

    toggleVisible: function (element) {
        if (element.classList.contains("visible")) {
            this.makeInvisible(element);
        } else {
            this.makeVisible(element);
        }
    },

    makeInvisible: function (element) {
        element.classList.remove("visible");
        if (element === this.activeVisible) {
            this.activeVisible = null;
        }
    }
};

function GridBoard(element, baseUrl) {
    let top;

    if (!element) return;

    const em = parseInt(getComputedStyle(document.documentElement).fontSize);
    const columnWidth = 10 * em;

    baseUrl = baseUrl || location.pathname;

    let topScroll;
    let leftScroll;
    let bodyScroll;

    let colindicate = element.querySelector(".r-gb-colindicate");

    let mouseDown = false;
    let drag = { startX: 0, startY: 0, startLeft: 0, startTop: 0, moved: false };

    // let filters = element.querySelectorAll(".toolbar .filters .filter");
    let form = element.querySelector(".r-gb-toolbar form");

    let topPosition = 0;
    let topOffset = 0;
    let lastScrollTop = 0;

    let headerSizes = null;
    let watchHeader = null;

    let onMouseDown = event => {
        mouseDown = true;
        event.preventDefault();
        drag.startX = event.type == "touchstart" ? event.touches[0].pageX : event.pageX;
        drag.startY = event.type == "touchstart" ? event.touches[0].pageY : event.pageY;
        drag.startLeft = bodyScroll.scrollLeft;
        drag.startTop = bodyScroll.scrollTop;
        drag.moved = false;
    };
    let onMouseMove = event => {
        if (mouseDown) {
            event.preventDefault();
            let x = event.type == "touchmove" ? event.touches[0].pageX : event.pageX;
            let y = event.type == "touchmove" ? event.touches[0].pageY : event.pageY;
            let dx = x - drag.startX;
            let dy = y - drag.startY;
            bodyScroll.scrollLeft = topScroll.scrollLeft = drag.startLeft - dx;
            bodyScroll.scrollTop = leftScroll.scrollTop = drag.startTop - dy;
            drag.moved = true;
        }
    };
    let onMouseUp = event => {
        mouseDown = false;
        if (drag.moved) {
            event.preventDefault();
        }
    };

    let rearrange = () => {
        top = element.querySelector(".r-gb-top");

        if (!top) return;

        let numColumns = top.querySelectorAll(".r-gb-col").length;
        let scrollRows = element.querySelectorAll(".r-gb-body .r-gb-scroll .r-inner > *");
        let fixRows = element.querySelectorAll(".r-gb-body .r-gb-fix > *");

        topScroll = element.querySelector(".r-gb-top .r-gb-scroll");
        leftScroll = element.querySelector(".r-gb-body .r-gb-fix");
        bodyScroll = element.querySelector(".r-gb-body .r-gb-scroll");

        let content = element.querySelector(".r-gb-content");

        {
            topPosition = 0;
            let el = content;
            while (el) {
                topPosition += el.offsetTop;
                el = el.offsetParent;
            }
        }

        element.querySelector(".r-gb-top .r-gb-scroll .r-inner").style.width = `${Math.round(numColumns * columnWidth + .7 * em)}px`;
        element.querySelector(".r-gb-body .r-gb-scroll .r-inner").style.width = `${Math.round(numColumns * columnWidth)}px`;

        if (content) {
            // content.style.height = `calc(100% - ${toolbar ? toolbar.offsetHeight : 0}px)`;
            // element.querySelector(".r-gb-body").style.height = `calc(100% - ${top.offsetHeight}px)`;
        } else {
            // element.querySelector(".r-gb-body").style.height = `calc(100% - ${top.offsetHeight + (toolbar ? toolbar.offsetHeight : 0)}px)`;
        }

        for (let index = 0; index < fixRows.length; index++) {
            if (index < scrollRows.length) {
                let height = Math.round(fixRows[index].offsetHeight);
                fixRows[index].style.height = `${height}px`;
                scrollRows[index].style.height = `${height}px`;
            }
        }

        if (localStorage.gbScroll) {
            let scroll = localStorage.gbScroll.split(',');
            topScroll.scrollLeft = bodyScroll.scrollLeft = parseInt(scroll[0]);
            leftScroll.scrollTop = bodyScroll.scrollTop = parseInt(scroll[1]);
            localStorage.gbScroll = null;
        }

        bodyScroll.addEventListener("wheel", (event) => {
            if (mouseDown) {
                event.preventDefault();
            }
        });

        bodyScroll.addEventListener("scroll", (event) => {
            if (!mouseDown) {
                topScroll.scrollLeft = bodyScroll.scrollLeft;
                leftScroll.scrollTop = bodyScroll.scrollTop;
            }
        });

        bodyScroll.addEventListener("mousedown", onMouseDown);
        bodyScroll.addEventListener("mousemove", onMouseMove);
        bodyScroll.addEventListener("mouseup", onMouseUp);
    }

    rearrange();
    if (element.classList.contains("r-gb-hoverindicate") && colindicate) {
        bodyScroll.querySelector(".r-inner").addEventListener("mouseenter", function(event) {
            if (event.target.classList.contains("r-gb-col")) {
                colindicate.style.left = `${event.target.offsetLeft}px`;
            }
        }, true);
    }

    if (form) {
        form.addEventListener("change", event => {
            let labels = [];

            if (event.target.type == "checkbox") {
                let checkboxes = document.querySelectorAll(`input[name="${event.target.name}"]`);
                for (checkbox of checkboxes) {
                    if (!checkbox.checked) continue;
                    let label = document.querySelector(`label[for="${checkbox.id}"]`);
                    if (label) {
                        labels.push(label.innerHTML);
                    }
                }
            } else {
                let item = event.target.parentElement.querySelector(`.r-item[data-value="${event.target.value}"]`);
                if (item) {
                    labels.push(item.innerHTML);
                }
            }

            let fcontainer = event.target;
            while (fcontainer && !fcontainer.hasAttribute("data-filter")) {
                fcontainer = fcontainer.parentElement;
            }

            if (fcontainer) {
                let valueLabel = document.querySelector(`[data-filter="${fcontainer.getAttribute('data-filter')}"] .r-gb-value`);
                if (valueLabel) {
                    valueLabel.innerHTML = labels.length > 2
                        ? `${labels.length} geselecteerd`
                        : (labels.length == 0 ? 'Alles' : labels.join(', '));
                }
            }
        });
    }

    let storeScroll = () => {
        if (bodyScroll) {
            localStorage.gbScroll = `${bodyScroll.scrollLeft},${bodyScroll.scrollTop}`;
        }
    }

    let onScroll = () => {
        if (!top) return;

        let scrollTop = window.pageYOffset;
        if (scrollTop + topOffset > topPosition && lastScrollTop + topOffset <= topPosition) {
            top.classList.add("r-fixed");
            top.style.top = topOffset + 'px';
        } else if (scrollTop + topOffset <= topPosition && lastScrollTop + topOffset > topPosition) {
            top.classList.remove("r-fixed");
            top.style.top = null;
        }
        lastScrollTop = scrollTop;
    }

    let onResize = () => {
        if (watchHeader) {
            let header = document.querySelector(watchHeader);
            if (header) {
                topOffset = header.offsetHeight;
            } else {
                topOffset = 0;
            }
        } else if (headerSizes) {
            let lastValue = 0;
            let found = false;
            for (let width in headerSizes) {
                if (width == 'max' || window.innerWidth < parseInt(width)) {
                    topOffset = headerSizes[width];
                    found = true;
                    break;
                }
                lastValue = headerSizes[width];
            }
            if (!found) {
                topOffset = lastValue;
            }
        } else {
            topOffset = 0;
        }

        let topRow = element.querySelector('.r-gb-toprow');
        if (topRow) {
            topRow.style.maxWidth = element.offsetWidth + 'px';
        }
    }

    let loadWithValues = (values) => {
        element.classList.add('r-loading');
        storeScroll();
        let xhr = new XMLHttpRequest();
        xhr.addEventListener("readystatechange", event => {
            if (xhr.readyState == XMLHttpRequest.DONE) {
                if (Math.floor(xhr.status / 100) == 2) {
                    // let response = JSON.parse(xhr.responseText);
                    let fragment = document.createElement('div');
                    fragment.innerHTML = xhr.responseText;
                    element.querySelector('.r-gb-content').innerHTML = fragment.querySelector('.r-gb-content').innerHTML;
                    rearrange();
                    element.classList.remove('r-loading');
                } else {
                    console.error(xhr.status + ' ' + xhr.statusText);
                }
            }
        });

        let url = baseUrl;
        url += (url.indexOf('?') == -1) ? '?' : '&';
        url += 'locale=' + Rental.locale;

        xhr.open('post', url, true);
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
        xhr.send(values);
    }

    let config = (key, value) => {
        if (key == 'headerSizes' && (value === null || typeof(value) == 'object')) {
            headerSizes = value;
            onResize();
        } else if (key == 'watchHeader' && typeof(value) == 'string') {
            watchHeader = value;
            onResize();
        }
    }


    let onSelectionClick = (event) => {
        event.preventDefault();
        let target = event.target;
        if (target.classList.contains("r-item")) {
            let activeItem = target.parentElement.querySelector(".r-item.active");
            let input = target.parentElement.querySelector("input");
            if (activeItem != target) {
                if (activeItem) activeItem.classList.remove("active");
                target.classList.add("active");
                if (target.hasAttribute("data-value")) {
                    input.value = target.getAttribute("data-value");
                    input.dispatchEvent(new Event("change", { bubbles: true, cancelable: false }));
                }
            }
        }
    }

    let selections = element.querySelectorAll(".r-selection");
    for (selection of selections) {
        selection.addEventListener("click", onSelectionClick);
    }

    window.addEventListener("resize", onResize);
    window.addEventListener("scroll", onScroll);
    onResize();
    onScroll();

    return {
        loadWithValues: loadWithValues,
        config: config
    };
}

{
    let selections = document.querySelectorAll(".r-selection");

    function onSelectionClick(event) {
        event.preventDefault();
        let target = event.target;
        if (target.classList.contains("r-item")) {
            let activeItem = this.querySelector(".r-item.active");
            let input = this.querySelector("input");
            if (activeItem != target) {
                if (activeItem) activeItem.classList.remove("active");
                target.classList.add("active");
                if (target.hasAttribute("data-value")) {
                    input.value = target.getAttribute("data-value");
                    input.dispatchEvent(new Event("change", { bubbles: true, cancelable: false }));
                }
            }
        }
    }

    for (selection of selections) {
        selection.addEventListener("click", onSelectionClick);
    }
}

window.addEventListener("click", event => {
    let target = event.target;
    while (target && !target.classList.contains("r-gb-context")) {
        target = target.parentElement;
    }

    if (target) {
        event.preventDefault();
        let popupSelector = target.getAttribute("data-popup");
        if (popupSelector) {
            let popup = document.querySelector(popupSelector);
            if (popup) {
                let style = getComputedStyle(target);
                popup.style.left = `${Math.max(10,(target.offsetLeft + target.offsetWidth - parseInt(style.paddingRight)) - popup.offsetWidth + 32)}px`;
                popup.style.top = `${target.offsetTop + target.offsetHeight}px`;
                Toggler.makeVisible(popup);
            }
        }
    } else {
        target = event.target;
        while (target && !target.classList.contains("visible")) {
            target = target.parentElement;
        }

        if (!target && Toggler.activeVisible) {
            event.preventDefault();
            Toggler.makeInvisible(Toggler.activeVisible);
        }
    }
});

/**
 * Rental
 */
const Rental = {
    Translations: {},

    locale: (document.documentElement.getAttribute('lang') || 'nl').substring(0, 2).toLowerCase(),
    currUrl: null,
    bookingUrl: null,

    translate: function(message, replacements) {
        let result = message;
        if ((this.locale in this.Translations) && (message in this.Translations[this.locale])) {
            result = this.Translations[this.locale][message];
        }
        if (replacements) {
            result = result.replace(/%([^%]+)%/g, (m0, m1) => {
                return (m1 in replacements) ? replacements[m1] : "";
            });
        }
        return result;
    },

    renderTable: function(data, range, limit) {
        let fmtOptions = { day: "numeric", month: "long", year: "numeric", hour: "numeric", minute: "2-digit", timeZone: 'UTC' };
        let html = "";
        let lastPeriod = null;

        let days = [];
        for (let d = 6; d < 13; d++) {
            let date = new Date(2019, 0, 6);
            days.push(date.toLocaleDateString(Rental.locale, { weekday: "long" }));
        }

        let count = 0;
        for (let index = 0; index < data.length; index++) {
            let option = data[index];

            if (range && (option.period_from.substring(0, 10) != range[0] || option.period_to.substring(0, 10) != range[1])) continue;

            let fromDate = (typeof option.period_from == "number") ? new Date(option.period_from * 1000) : new Date(option.period_from.replace(' ', 'T') + 'Z');
            let toDate = (typeof option.period_to == "number") ? new Date(option.period_to * 1000) : new Date(option.period_to.replace(' ', 'T') + 'Z');
            let numDays = (toDate.getTime() - fromDate.getTime()) / 86400000;
            let notification = "";

            html += `<tr>
                <td nowrap="nowrap"><span class="booking-label hide-for-large">${Rental.translate('Periode')}</span>${option.period_name != lastPeriod ? Rental.translate(option.period_name) : ''}</td>
                <td nowrap="nowrap"><span class="booking-label hide-for-large"></span>${fromDate.toLocaleString(Rental.locale, { weekday: "long"})}${numDays > 1 ? ' - ' + toDate.toLocaleString(Rental.locale, { weekday: "long"}) : ''}</td>

                <td nowrap="nowrap"><span class="booking-label hide-for-large">${Rental.translate('Van')}</span>${fromDate.toLocaleString(Rental.locale, fmtOptions)}</td>
                <td nowrap="nowrap"><span class="booking-label hide-for-large">${Rental.translate('Tot')}</span>${toDate.toLocaleString(Rental.locale, fmtOptions)}</td>

                <td nowrap="nowrap"><span class="booking-label hide-for-large">${Rental.translate('Prijs')}</span>
                    ${option.price_old && option.price_old != option.price_from ? '<s>' + parseFloat(option.price_from).toLocaleString(Rental.locale, { style: "currency", currency: "EUR", currencyDisplay: "symbol" }) + '</s>' : ''}
                    <strong class="c-dark">${parseFloat(option.price_from).toLocaleString(Rental.locale, { style: "currency", currency: "EUR", currencyDisplay: "symbol" })}</strong>
                    ${notification}
                </td>

                <td class="text-right table-buttons">
                    <a href="${Rental.locale == 'de' ? '/angebot-anfragen' : '/boot-huren-friesland/boeken'}?act=add&periodkey=${option.period_key}" class="r-button r-button-1" rel="nofollow" style="padding-left: 0.7rem;"><i class="icon-calendar-check"></i> ${Rental.translate('Boeken')}</a>
                </td>
            </tr>`;
            lastPeriod = option.period_name;
            count++;
            if (limit && count >= limit) break;
        }

        if (count == 0) {
            html = '<tr><td colspan="6" style="padding: 50px 10px; text-align: center;">';
            html += Rental.translate('Er zijn geen beschikbare periodes gevonden.<br/>Probeer een andere datum of periode te kiezen.');
            html += '</td></tr>';
        }

        return html;
    },

    renderPriceTable: function(data, startDate) {

        let matrix = {};

        let smallScreen = document.body.clientWidth < 768;
        let numDates = smallScreen ? 3 : 5;

        let firstDate = (startDate instanceof Date) ? startDate.getTime() : (new Date(startDate)).getTime();
        let dates = [];
        for (let i = 0; i < numDates; i++) {
            dates.push(new Date(firstDate + i * 86400000));
        }

        let maxDate = dates[dates.length - 1];
        let maxDateString = `${maxDate.getFullYear()}-${(maxDate.getMonth()+1).toString().padStart(2,'0')}-${maxDate.getDate().toString().padStart(2,'0')} 23:59:59`;

        let numPeriods = 0;

        /*
        matrix = {
            "period_id" => {
                label: "Period label",
                options: [ availability_row, ... ]
            }
        }
        Elke entry in de matrix is een rij in de tabel.
        Elke optie in de entry is een kolom in de tabel.

        notitie: JS sorteerd numrieke keys automatisch! Er word nu p_ aan de keys toegevoegd om dit te voorkomen
        */
        for (let i = 0; i < data.length; i++) {
            let option = data[i];

            if (option.period_from < maxDateString) {
                if (!('p_'+option.period_id in matrix)) {
                    matrix['p_'+option.period_id] = {
                        label: Rental.translate(option.period_name),
                        subLabel: option.period_addition ? Rental.translate(option.period_addition) : null,
                        options: [],
                        prevOption: null
                    };
                }

                option.day = parseInt(option.period_from.substring(8, 10));
                if (option.period_from.substring(0, 10) < startDate) {
                    if (option.period_to.substring(0, 10) >= startDate) {
                        matrix['p_'+option.period_id].prevOption = option;
                    }
                } else {
                    matrix['p_'+option.period_id].options.push(option);
                }
            }
        }

        let html = '<thead><tr><th>&nbsp;</th>';
        if (!smallScreen) html += '<th class="r-pt-nav"><a href="#" data-pt-previous><svg class="r-icon"><use href="#icon_chevron_left" x="0" y="0"/></svg></a></th>';
        for (let i = 0; i < dates.length; i++) {
            html += '<th>' + dates[i].toLocaleDateString(Rental.locale, { weekday: "short", day: "numeric", month: "short" }) + '</th>';
        }
        if (!smallScreen) html += '<th class="r-pt-nav"><a href="#" data-pt-next><svg class="r-icon"><use href="#icon_chevron_right" x="0" y="0"/></svg></a></th>';
        html += '</tr></thead><tbody>';
        for (let key in matrix) {
            if (matrix[key].options.length == 0 && !matrix[key].prevOption) continue;
            html += '<tr data-period="' + key.replace('p_', '') + '"><td class="r-pt-lbl">' + matrix[key].label + (matrix[key].subLabel ? '<br/><span class="r-pt-sublbl">' + matrix[key].subLabel + '</span>' : '') + '</td>';
            if (!smallScreen) html += '<td class="r-pt-spc">&nbsp;</td>';
            let index = 0;
            for (let i = 0; i < dates.length; i++) {
                if (index < matrix[key].options.length && matrix[key].options[index].day == dates[i].getDate()) {
                    let option = matrix[key].options[index];
                    let price = option.price_for ? (option.price_for) : (option.price_from);
                    let priceFrom = (option.price_from);
                    html += `<td class="r-pt-selectable" data-date="${option.period_from.substring(0,10)}" data-info="['${option.period_key}','${option.period_from.substring(0,10)}','${option.period_to.substring(0,10)}',${price},${priceFrom}]">`;
                    if (priceFrom && priceFrom != price) {
                        html += '<span class="r-pt-price-from">' + priceFrom.toLocaleString(Rental.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '</span>';
                        html += '<span class="r-pt-price r-pt-price-discount">' + price.toLocaleString(Rental.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '</span>';
                    } else {
                        html += '<span class="r-pt-price">' + price.toLocaleString(Rental.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '</span>';
                    }
                    html += '</td>';
                    index++;
                } else if (i == 0 && matrix[key].prevOption) {
                    let option = matrix[key].prevOption;
                    let price = option.price_for ? (option.price_for) : (option.price_from);
                    let priceFrom = (option.price_from);
                    html += `<td class="r-pt-selectable" data-date="${option.period_from.substring(0,10)}" data-info="['${option.period_key}','${option.period_from.substring(0,10)}','${option.period_to.substring(0,10)}',${price},${priceFrom}]">`;
                    html += '<span class="r-pt-price-date">' + (new Date(option.period_from.substring(0,10))).toLocaleDateString(Rental.locale, { weekday: 'short', day: 'numeric', month: 'short' }) + '</span>';
                    if (priceFrom && priceFrom != price) {
                        html += '<span class="r-pt-price-from">' + priceFrom.toLocaleString(Rental.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '</span>';
                        html += '<span class="r-pt-price pt-price-discount">' + price.toLocaleString(Rental.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '</span>';
                    } else {
                        html += '<span class="r-pt-price">' + price.toLocaleString(Rental.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '</span>';
                    }
                    html += '</td>';
                } else {
                    html += '<td>-</td>';
                }
            }
            if (!smallScreen) html += '<td class="r-pt-spc">&nbsp;</td>';
            html += '</tr>';
            numPeriods++;
        }

        if (numPeriods == 0) {
            html += '<tr><td colspan="' + (smallScreen ? '6' : '8') + '"><p style="text-align:center">' + Rental.translate("Geen beschikbare opties gevonden") + '</p></td></tr>';
        }

        html += '</tbody>';
        if (smallScreen) {
            html += `<tfoot><tr><th colspan="${numDates + 1}">
                <div class="r-pt-foot">
                    <a href="#" data-pt-previous><svg class="r-icon"><use href="#icon_chevron_left" x="0" y="0"/></svg> &nbsp;${Rental.translate('Eerder')}</a>
                    <a href="#" data-pt-next>${Rental.translate('Later')}&nbsp; <svg class="r-icon"><use href="#icon_chevron_right" x="0" y="0"/></svg></i></a>
                </div>
            </th></tfoot>`;
        }

        return html;
    },

    selectPriceOption(td) {
        if (!td) {
            let detailPanels = document.querySelectorAll(".r-pricetable .r-pt-details");
            for (let dp of detailPanels) {
                dp.innerHTML = `
                    <div class="headline">${Rental.translate('Uw vakantie')}</div>
                    <div class="info">${Rental.translate('Kies uw gewenste periode')}</div>`;
            }
            return;
        }

        const dateFormat = { year: "numeric", month: "short", day: "numeric", weekday: "short" };
        const dateFormatNoYear = { month: "short", day: "numeric", weekday: "short" };

        let info = JSON.parse(td.getAttribute("data-info").replace(/'/g, '"'));

        let periodKey = info[0];
        let startDate = new Date(info[1]);
        let endDate = new Date(info[2]);
        let price = parseFloat(info[3]);
        let priceFrom = info.length > 4 ? info[4] : null;

        let numDays = Math.ceil((endDate.getTime() - startDate.getTime()) / 86400000) + 1;

        let bookingUrl = this.bookingUrl;
        let inlineBooking = false;
        if (!bookingUrl) {
            let bp = td;
            while (bp && !bp.hasAttribute("data-rental-periods")) bp = bp.parentElement;
            if (bp && bp.hasAttribute("data-booking-url")) {
                bookingUrl = bp.getAttribute("data-booking-url");
                inlineBooking = true;
            } else {
                bookingUrl = '/boeken';
            }
        }

        let days = Rental.translate('1 dag(deel)');
        if (numDays > 1) {
            days = numDays == 2 ? Rental.translate('1 nacht') : `${numDays - 1} ${Rental.translate('nachten')}` + ' / ' +
                numDays == 1 ? Rental.translate('1 dag') : `${numDays} ${Rental.translate('dagen')}`;
        }

        let html = `
            <div class="headline">${Rental.translate('Uw vakantie')}</div>
            <div class="info">
                ${startDate.toLocaleDateString(Rental.locale, startDate.getFullYear() != endDate.getFullYear() ? dateFormat : dateFormatNoYear)}
                -
                ${endDate.toLocaleDateString(Rental.locale, dateFormat)}<br/>
                ${days}
            </div>
            <div class="prices">
                ${priceFrom && priceFrom != price ? `<span class="r-pt-price-from">&euro; ${priceFrom.toLocaleString(Rental.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span><br/>${Rental.translate('Nu v.a.')}` : ''}
                <span class="r-pt-price${priceFrom && priceFrom != price ? ' r-pt-price-discount' : ''}">&euro;&nbsp;${price.toLocaleString(Rental.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span><br/>
            </div>
            <a href="${bookingUrl}${bookingUrl.indexOf('?')==-1?'?':'&'}act=add&periodkey=${periodKey}" class="r-button r-button-1"${inlineBooking ? ' rel="booking"' : ''} ${bookingUrl.match(/^https?:\/\//) ? ' target="_blank"' : ''}>${Rental.translate('Boek nu')} <i class="fa fa-shopping-cart"></i></a>`;

        let detailPanels = document.querySelectorAll(".r-pricetable .r-pt-details");
        for (let dp of detailPanels) {
            dp.innerHTML = html;
        }

        let selectedTd = document.querySelector(".r-pricetable .r-pt-selected");
        if (selectedTd) selectedTd.classList.remove("r-pt-selected");
        td.classList.add("r-pt-selected");
    },

    loadPeriods: function(element, listingId, options) {
        if (!element.hasAttribute("data-rental-periods")) {
            element.setAttribute("data-rental-periods", "true");
            element.addEventListener("click", event => {
                event.preventDefault();
                let el = event.target;
                while (el && el.tagName != "TD") {
                    el = el.parentElement;
                }
                if (el && el.classList.contains("r-pt-selectable")) {
                    Rental.selectPriceOption(el);
                }
            });
        }


        let priceTable = element;
        while (priceTable && !priceTable.classList.contains("r-pricetable")) priceTable = priceTable.parentElement;
        if (priceTable) priceTable.classList.add("loading");

        if (!listingId) {
            listingId = element.getAttribute("data-listing");
        }

        if (!listingId) return;
        let now = new Date();
        let range = null;

        if (!options && element.lastOptions) {
            options = element.lastOptions;
        } else {
            element.lastOptions = options;
        }

        let monthId = 1554069600;
        if (now.getMonth >= 3) {
            let monthStart = new Date(now.getFullYear(), now.getMonth());
            monthId = monthStart.getTime() / 1000;
        }
        let limit = ("limit" in options) ? options.limit : null;

        let url = `${('baseUri' in options) ? options.baseUri : ''}/rentaladmin/api/options/listing/${listingId}`;
        if (("date" in options) && options.date) {
            let date = new Date(options.date);
            let toDate = new Date(options.date);
            date.setDate(date.getDate() - 7);
            toDate.setDate(toDate.getDate() + 7);
            let dateString = `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2,'0')}-${date.getDate().toString().padStart(2,'0')}`;
            let toDateString = `${toDate.getFullYear()}-${(toDate.getMonth()+1).toString().padStart(2,'0')}-${toDate.getDate().toString().padStart(2,'0')}`;
            url += `/start/${dateString}/end/${toDateString}`;
        } else if (("dateRange" in options) && options.dateRange) {
            range = [ options.dateRange.start.toString(), options.dateRange.end.toString() ];
            url += `/start/${range[0]}${options.dateRange.end.key != options.dateRange.start.key ? `/end/${range[1]}` : ''}`;
        } else {
            url += `/month/${monthId}`;
        }
        if (("periods" in options) && options.periods && options.periods.length){
            url += `/periods/${options.periods.join(',')}`;
            url = url.replace(/\/month\/\d+/g, '');
        }

        let xhr = new XMLHttpRequest();
        xhr.addEventListener("readystatechange", event => {
            if (xhr.readyState == XMLHttpRequest.DONE) {
                if (Math.floor(xhr.status / 100) == 2) {
                    let result = xhr.response;
                    if (typeof result == "string") {
                        result = JSON.parse(result);
                    }
                    if (("priceTable" in options) && options.priceTable) {
                        element.innerHTML = Rental.renderPriceTable(result, range ? options.dateRange.start : options.date);
                        Rental.selectPriceOption(null);
                        if (priceTable) priceTable.classList.remove("loading");
                        element.dispatchEvent(new CustomEvent("rental.ready", { bubbles: false, cancelable: false }));
                    } else {
                        element.innerHTML = Rental.renderTable(result, range, limit);
                    }

                    if (Rental.Widgets.page.scrollToContent && ("scrollTo" in options) && options.scrollTo) {
                        let top = 0;
                        let el = element;
                        while (el) {
                            top += el.offsetTop;
                            el = el.offsetParent;
                        }

                        if (top < window.pageYOffset + 100 || top > window.pageYOffset + window.innerHeight) {
                            window.scrollTo(window.pageXOffset, top - 100);
                        }
                    }
                } else {
                    console.log(xhr.status);
                }
            }
        });

        xhr.open("get", url, true);
        xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
        xhr.send();
    },

    parseScripts: function(html) {
        while (true) {
            let match = html.match(/<script\b([^>]*)>/);

            if (!match) break;
            let offset = match.index + match[0].length;
            let pos = html.indexOf('</script>', offset);
            if (pos == -1) {
                console.error('Unexpected end of script');
                break;
            }
            let js = html.substring(offset, pos);
            let mt = match[1].match(/\btype="([^"]+)"/);
            if (!mt || mt[1] == 'text/javascript') {
                if (js) {
                    eval(js);
                }
                html = html.substring(0, match.index) + html.substring(pos + 9);
            } else {
                html = html.substring(0, match.index) +
                    match[0].replace(/</g, '&amp;lt;').replace(/>/g, '&amp;gt;') +
                    js +
                    '&amp;lt;/script&amp;gt;' +
                    html.substring(pos + 9);
            }
        }

        return html.replace(/&amp;lt;/g, '<').replace(/&amp;gt;/g, '>');
    },

    loadUrl: function(widget, url, target, selector, replacements, data, keepCurrUrl, scroll) {
        return new Promise((resolve, reject) => {
            let subtarget = selector ? target.querySelector(selector) : null;
            let loadTarget = subtarget || target;
            loadTarget.classList.add("r-loading");

            let hashPos = url.indexOf('#');
            let hash = hashPos > -1 ? url.substring(hashPos) : null;
            if (hash) {
                url = url.substring(0, hashPos);
            }

            if (Rental.locale != 'nl') {
                url += (url.indexOf('?') > -1 ? '&' : '?') + 'locale=' + Rental.locale;
            }

            let sessionKey = 'rentalSession_' + Rental.Widgets.page.namespace;
            console.log(sessionKey, localStorage.getItem(sessionKey));
            if (localStorage && localStorage.getItem(sessionKey)) {
                url += (url.indexOf('?') > -1 ? '&' : '?') + 'rentalsession=' + localStorage.getItem(sessionKey);
            }

            if (!url.match(/\bwidget=/)) {
                url += (url.indexOf('?') > -1 ? '&' : '?') + 'widget=' + Rental.widgetKey;
            }
            if (Rental.Widgets.page.embedId && !url.match(/\bembed=/)) {
                url += (url.indexOf('?') > -1 ? '&' : '?') + 'embed=' + Rental.Widgets.page.embedId;
            }

            if (hash) {
                url += hash;
            }

            let xhr = new XMLHttpRequest();
            xhr.addEventListener("readystatechange", event => {
                if (xhr.readyState == XMLHttpRequest.DONE) {
                    loadTarget.classList.remove("r-loading");

                    let top = 0, el = loadTarget;
                    while (el) {
                        top += el.offsetTop;
                        el = el.offsetParent;
                    }
                    if (scroll !== false && Rental.Widgets.page.scrollToContent && (top < window.pageYOffset + 100 || top > window.pageYOffset + window.innerHeight)) {
                        window.scrollTo(window.pageXOffset, top - 100);
                    }

                    if (Math.floor(xhr.status / 100) == 2) {
                        let rs = xhr.getResponseHeader('X-Rental-Session');
                        if (rs && localStorage) {
                            localStorage.setItem(sessionKey, rs);
                        }

                        if (!keepCurrUrl) {
                            this.currUrl = xhr.responseURL || url;
                        }

                        let html = Rental.cleanupHtml(xhr.responseText);
                        if (html && html[0] == '{') {
                            let json = JSON.parse(html);
                            html = json.blockhtml;
                        }

                        let fragment;
                        if (replacements) {
                            for (let key in replacements) {
                                html = html.replace(new RegExp(`#${key}#`, 'g'), replacements[key]);
                            }
                        }

                        html = this.parseScripts(html);

                        if (selector) {
                            fragment = document.createElement("div");
                            fragment.innerHTML = html;
                            let subfragment = fragment.querySelector(selector);
                            let subtarget = target.querySelector(selector);
                            if (subfragment && subtarget) {
                                subtarget.innerHTML = subfragment.innerHTML;
                            }
                        } else {
                            target.innerHTML = html;
                        }
                        resolve({ widget, target, fragment });
                    } else {
                        target.innerHTML = xhr.status + ' ' + xhr.statusText;
                        reject(xhr.status);
                    }
                }
            });
            xhr.open(data ? 'post' : 'get', url, true);
            xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
            if (data) {
                xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            }
            xhr.send(data);
        });
    },

    serialize: function(form, allowEmptyValues) {
        let pairs = [];

        for (let element of form.elements) {
            if (element.name) {
                if (element.tagName.toLowerCase() == "select") {
                    if (element.selectedIndex > -1) {
                        pairs.push(`${element.name}=${encodeURIComponent(element.options[element.selectedIndex].value)}`);
                    }
                } else if (element.type == "radio" || element.type == "checkbox") {
                    if (element.checked) {
                        pairs.push(`${element.name}=${encodeURIComponent(element.value)}`);
                    }
                } else if ((element.value && element.value != '0') || allowEmptyValues) {
                    pairs.push(`${element.name}=${encodeURIComponent(element.value)}`);
                }
            }
        }

        return pairs.join('&');
    },

    cleanupHtml: function (html) {
        if (html.indexOf('<' + '!DOCTYPE') > -1 || html.indexOf('<' + '!doctype') > -1) {
            let bodyPos = html.indexOf('<body');
            if (bodyPos == -1) { console.log('No BODY tag found'); return ""; }
            let bodyEndPos = html.indexOf('</body>', bodyPos);
            if (bodyEndPos == -1) { console.log('No BODY end tag found'); return ""; }
            html = '<div' + html.substring(bodyPos + 5, bodyEndPos) + '</div>';
        }

        return html;
    },

    require: function(url, type) {
        return new Promise((resolve, reject) => {
            let m = url.match(/\.([a-zA-Z0-9]+)$/);
            let extension = m ? m[1] : null;
            if (!extension) {
                // reject("Require failed: No extension found on " + url);
                // return;
                extension = type || 'js';
            }
            if (extension != 'css' && extension != 'js') {
                reject("Require failed: Invalid extension found on " + url);
                return;
            }

            if (extension == 'css') {
                let link = document.createElement("link");
                link.setAttribute("rel", "stylesheet");
                link.setAttribute("href", url);
                document.body.appendChild(link);
                resolve();
            } else if (extension == 'js') {
                let xhr = new XMLHttpRequest();
                xhr.addEventListener("readystatechange", event => {
                    if (xhr.readyState == XMLHttpRequest.DONE) {
                        if (Math.floor(xhr.status / 100) == 2) {
                            let js = xhr.responseText;
                            eval(js);
                            resolve();
                        } else {
                            reject(xhr.status + ' ' + xhr.statusText);
                        }
                    }
                });
                xhr.open('get', url, true);
                xhr.send();
            }
        });
    },

    postcodeApi: function(context) {
        let nr = context.querySelector('[data-postcode-api="nr"]');
        let toevoeging = context.querySelector('[data-postcode-api="toevoeging"]');
        let postal = context.querySelector('[data-postcode-api="postal"]');
        let loading = context.querySelector('[data-postcode-api="loading"]');

        let rentalAddress = context.getElementById('form-c_address1');
        let rentalPostal  = context.getElementById('form-c_zip_code');
        let rentalCity    = context.getElementById('form-c_city');
        let rentalCountry = context.getElementById('form-c_country');

        let apiResultAddress = context.querySelector('[data-postcode-api-result="address"]');
        let apiResultCity    = context.querySelector('[data-postcode-api-result="city"]');
        let apiResultCountry = context.querySelector('[data-postcode-api-result="country"]');

        let autofill = context.querySelector('[data-postcode-api-autofill]');
        let fields = context.querySelector('[data-postcode-api-fields]');
        let result = context.querySelector('[data-postcode-api-result]');
        let error = context.querySelector('[data-postcode-api-error]');

        if (rentalAddress.value == '') {
            autofill.style.display = 'none';
        } else {
            fields.style.display = 'none';
        }

        result.style.display = 'none';
        error.style.display = 'none';

        // Handmatige edit
        context.querySelector('[data-postcode-api-edit]').addEventListener('click', function() {
            result.style.display = 'none';
            fields.style.display = 'none';
            error.style.display = 'none';
            autofill.style.display = 'block';
        });

        // Postcode API
        function onChange() {
            if (this.getAttribute('data-postcode-api') == 'toevoeging' && this.value == '') {
                return false;
            }

            if (nr.value != '' && postal.value) {
                loading.style.display = 'block';

                error.style.display = 'none';
                result.style.display = 'none';

                let xhr = new XMLHttpRequest();
                xhr.addEventListener("readystatechange", event => {
                    if (xhr.readyState == XMLHttpRequest.DONE) {
                        if (Math.floor(xhr.status / 100) == 2) {
                            let result = xhr.response;
                            if (typeof result == 'string') result = JSON.parse(result);

                            if(!result) {
                                error.display = 'block';
                            } else {
                                if (toevoeging.value != '') {
                                    rentalAddress.value = result.address + ' ' + result.address_nr + toevoeging.value;
                                    apiResultAddress.innerHTML = result.address + ' ' + result.address_nr + toevoeging.value;
                                } else {
                                    rentalAddress.value = result.address + ' ' + result.address_nr;
                                    apiResultAddress.innerHTML = result.address + ' ' + result.address_nr;
                                }

                                rentalPostal.value = result.postal;
                                rentalCity.value = result.city;
                                rentalCountry.value = result.country;

                                apiResultCity.innerHTML = result.postal + ' ' + result.city;
                                apiResultCountry.innerHTML = result.country;

                                result.style.display = 'block';
                                loading.style.display = 'none';
                            }
                        } else {
                            console.log(xhr.status + ' ' + xhr.statusText);
                        }
                    }
                });
                xhr.open('get', '/rentaladmin/postcode/search/postal/' + encodeURIComponent(postal.value) + '/nr/' + encodeURIComponent(nr.value), true);
                xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
                xhr.send();
            }
        }

        postal.addEventListener("change", onChange);
        nr.addEventListener("change", onChange);
        toevoeging.addEventListener("change", onChange);
    }
};

// Rental.Translations.de = {
//     "dag": "Tag",
//     "Dag": "Tag",
//     "Midweek": "Kurzwoche",
//     "Midweek scholen": "Kurzwoche für Schule",
//     "Week": "Woche",
//     "Weekend": "Wochenende",
//     "midweek": "Kurzwoche",
//     "week": "Woche",
//     "weekend": "Wochenende",
//     "week + midweek": "Woche + Kurzwoche",
//     "week + weekend": "Woche + Wochenende",
//     "2 weken": "2 Wochen",
//     "3 weken": "3 Wochen",
//     "Periode": "Zeit",
//     "Van": "Von",
//     "Tot": "Bis",
//     "Prijs": "Preise",
//     "Boeken": "Buchen",
//     "Boek nu": "Jetzt buchen",
//     "Uw vakantie": "Ihr Urlaub",
//     "1 nacht": "1 Nacht",
//     "nachten": "Nächte",
//     "1 dag": "1 Tag",
//     "1 dag(deel)": "1 Tag / Tagesteil",
//     "dagen": "Tage",
//     "Eerder": "Früher",
//     "Later": "Später",
//     "Nu v.a.": "Jetzt von",
//     "Let op! Deze prijs geldt alleen<br/>als u voor %persons% personen boekt!": "Achtung! Dieser Preis gilt nur,<br/>wenn Sie für %persons% Personen buchen!",
//     "Er zijn geen beschikbare periodes gevonden.<br/>Probeer een andere datum of periode te kiezen.": "Keine verfugbare Perioden gefunden.<br/>Versuchen Sie, ein anderes Datum oder einen anderen Zeitraum auszuw&auml;hlen.",
//     "Voornaam": "Vorname",
//     "Tussenvoegsel": "Namenzusatz",
//     "Achternaam": "Nachname",
//     "Straatnaam en huisnummer": "Straßenname und Hausnummer",
//     "Straatnaam": "Straßenname",
//     "Postcode": "Postleitzahl",
//     "Plaats": "Wohnort",
//     "Mobiel": "Handy",
//     "Telefoonnummer": "Telefonnummer",
//     "E-mailadres": "E-mail",
//     "t/m donderdag": "bis Tonnerstag",
//     "t/m vrijdag": "bis Freitag",
//     "vanaf vrijdag": "ab Freitag",
//     "vanaf zaterdag": "ab Samstag"
// };

// Rental.Translations.en = {
//     "dag": "day",
//     "Dag": "Day",
//     "Midweek": "Midweek",
//     "Midweek scholen": "Midweek for schools",
//     "Week": "Week",
//     "Weekend": "Weekend",
//     "midweek": "midweek",
//     "week": "week",
//     "weekend": "weekend",
//     "week + midweek": "week + midweek",
//     "week + weekend": "week + weekend",
//     "2 weken": "2 weeks",
//     "3 weken": "3 weeks",
//     "Periode": "Period",
//     "Van": "From",
//     "Tot": "Until",
//     "Prijs": "Price",
//     "Boeken": "Book",
//     "Boek nu": "Book now",
//     "Uw vakantie": "Your vacation",
//     "1 nacht": "1 night",
//     "nachten": "nights",
//     "1 dag": "1 day",
//     "1 dag(deel)": "1 day(part)",
//     "dagen": "days",
//     "Eerder": "Earlier",
//     "Later": "Later",
//     "Nu v.a.": "Now from",
//     "Let op! Deze prijs geldt alleen<br/>als u voor %persons% personen boekt!": "Notice! This price applies only<br/>when you book for %persons% persons!",
//     "Er zijn geen beschikbare periodes gevonden.<br/>Probeer een andere datum of periode te kiezen.": "There are no periods available for this date.<br/>Try and select another date.",
//     "Voornaam": "First name",
//     "Tussenvoegsel": "Middle name",
//     "Achternaam": "Last name",
//     "Straatnaam en huisnummer": "Street name and house number",
//     "Straatnaam": "Street name",
//     "Postcode": "Postal code",
//     "Plaats": "City",
//     "Mobiel": "Mobile",
//     "Telefoonnummer": "Phone number",
//     "E-mailadres": "Email address",
//     "t/m donderdag": "until thursday",
//     "t/m vrijdag": "until friday",
//     "vanaf vrijdag": "from friday",
//     "vanaf zaterdag": "from saturday"
// };

window.addEventListener("click", function(event){
    let target = event.target;
    while (target && !(target.hasAttribute("data-pt-previous") || target.hasAttribute("data-pt-next"))) {
        target = target.parentElement;
    }

    if (target) {
        event.preventDefault();
        let periods = target;
        while (periods && !periods.hasAttribute("data-rental-periods")) periods = periods.parentElement;
        if (!periods || !periods.lastOptions) return;
        let date = (("dateRange" in periods.lastOptions) && periods.lastOptions.dateRange) ? periods.lastOptions.dateRange.start : periods.lastOptions.date;
        let newDate = target.hasAttribute('data-pt-previous')
            ? new Date((new Date(date)).getTime() - 345600000)
            : new Date((new Date(date)).getTime() + 345600000);
        let ev = new CustomEvent("rental.changedate", { bubbles: false, cancelable: false });
        ev.data = { date: `${newDate.getFullYear()}-${(newDate.getMonth()+1).toString().padStart(2,'0')}-${newDate.getDate().toString().padStart(2,'0')}` };
        periods.dispatchEvent(ev);
    }
});

/**
 * Filter bar
 */
{
    class FilterItem {
        constructor(element) {
            this.element = element;
            this.cachedValues = {};
        }

        getValue(name) {
            if (!(name in this.cachedValues)) {
                let input = this.element.querySelector(`[name="${name}"]`);
                if (!input) input = this.element.querySelector(`#${name}`);
                if (!input) return null;
                this.cachedValues[name] = input.value || 0;
            }
            return this.cachedValues[name];
        }

        getSystemValue(name) {
            let sname = '.' + name;

            if (!(sname in this.cachedValues)) {
                switch (name) {
                    case "numChecked":
                        this.cachedValues[sname] = this.element.querySelectorAll('input:checked').length;
                        break;

                    case "implodedCheckedLabels":
                        let checked = this.element.querySelectorAll('input:checked');
                        let array = [];
                        for (let i = 0; i < checked.length; i++) {
                            let label = this.element.querySelector(`label[for="${checked[i].id}"]`);
                            if (label) array.push(label.innerHTML);
                        }
                        this.cachedValues[sname] = `"${array.join(', ').replace(/"/g, '\\"')}"`;
                        break;
                }
            }
            return this.cachedValues[sname];
        }
    }

    window.FilterBar = function FilterBar(filterbar, options) {
        if (!filterbar) return;

        let form = filterbar;
        let public;

        options = options||{};
        if (!('submitOnClear' in options)) options.submitOnClear = true;
        if (!('submitOnBlur' in options)) options.submitOnBlur = false;

        while (form && form.tagName.toLowerCase() != 'form') {
            form = form.parentElement;
        }

        function submitForm() {
            let submitButton = filterbar.querySelector('[type="submit"]');
            if (submitButton) {
                if ('$' in window && typeof '$' === 'function') {
                    $(submitButton).click();
                } else {
                    submitButton.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
                }
            } else if (form) {
                form.submit();
            }
        }

        function parseTemplateExpression(expression, item) {
            let expr = expression.replace(/['"\.]?[a-zA-Z][a-zA-Z0-9\-]*/g, mm0 => {
                if (mm0[0] == '\'' || mm0 == '"') {
                    return mm0;
                } else if (mm0[0] == '.') {
                    return item.getSystemValue(mm0.substring(1));
                } else {
                    return item.getValue(mm0);
                }
            });
            return eval(expr);
        }

        function fillTemplate(template, item) {
            let result = template.replace(/\{([^\}]+)\}/g, (m0, m1) => {
                return parseTemplateExpression(m1, item);
            });
            return result;
        }

        function updateItem(itemEl) {
            let label = itemEl.querySelector(".r-fb-label");

            if (label) {
                if ("fbDelegate" in itemEl) {

                    itemEl.fbDelegate.updateLabel.call(itemEl, label);

                } else {

                    if (!label.oldContent) label.oldContent = label.innerHTML;
                    if (label.hasAttribute("data-template")) {
                        let update = true;
                        let item = new FilterItem(itemEl);
                        if (label.hasAttribute("data-template-if")) {
                            update = parseTemplateExpression(label.getAttribute("data-template-if"), item);
                        }
                        if (update) {
                            let content = fillTemplate(label.getAttribute("data-template"), item);
                            if (content) {
                                label.innerHTML = content;
                                itemEl.classList.add("set");
                            } else {
                                label.innerHTML = label.oldContent;
                                itemEl.classList.remove("set");
                            }
                        } else {
                            label.innerHTML = label.oldContent;
                            itemEl.classList.remove("set");
                        }
                    }

                }
            }
        }

        function updateAllItems() {
            let items = filterbar.querySelectorAll(".r-fb-item");
            for (let i = 0; i < items.length; i++) {
                updateItem(items[i]);
            }
        }

        function clearFilters(itemEl, submit) {
            if ("fbDelegate" in itemEl) {
                itemEl.fbDelegate.clearValues.call(itemEl);
            } else {
                let inputs = itemEl.querySelectorAll("input");
                for (let i = 0; i < inputs.length; i++) {
                    if (inputs[i].type == "checkbox" || inputs[i].type == "radio") {
                        inputs[i].checked = false;
                    } else if (inputs[i].hasAttribute("min")) {
                        inputs[i].value = inputs[i].getAttribute("min");
                    } else {
                        inputs[i].value = "";
                    }
                }
            }
            updateItem(itemEl);
            if (submit && options.submitOnClear) {
                submitForm();
            }
        }

        function clearAllFilters() {
            let items = filterbar.querySelectorAll(".r-fb-item");
            for (let i = 0; i < items.length; i++) {
                clearFilters(items[i]);
            }
            if (options.submitOnClear) {
                submitForm();
            }
        }

        filterbar.addEventListener("click", event => {
            if (event.target.classList.contains("r-fb-label") || (event.target.parentElement && event.target.parentElement.classList.contains("r-fb-label"))) {
                event.preventDefault();

                let item = event.target;
                while (item && !item.classList.contains("r-fb-item")) item = item.parentElement;

                if (item.classList.contains("open")) {
                    item.classList.remove("open");
                    if (options.submitOnBlur) {
                        submitForm();
                    }
                } else {
                    // let currItem = item.parentElement.querySelector(".r-fb-item.open");
                    let currItem = document.querySelector(".r-fb-item.open");
                    if (currItem) {
                        currItem.classList.remove("open");
                    }
                    item.classList.add("open");
                    FilterBar.current = public;

                    let panel = item.querySelector(".r-fb-panel");
                    if (panel.getAttribute("data-position") == "right") {
                        panel.style.left = (item.offsetWidth - panel.offsetWidth) + 'px';
                    } else if (!item.parentElement.classList.contains("r-fb-compact") && item.offsetLeft + panel.offsetWidth > item.parentElement.clientWidth) {
                        let offset = Math.min(item.offsetLeft, item.offsetLeft - (item.parentElement.clientWidth - panel.offsetWidth));
                        panel.style.left = '-' + (offset) + 'px';
                    } else {
                        panel.style.left = 0;
                    }
                }
            } else {
                if (event.target.tagName.toLowerCase() == "button") {
                    if (event.target.classList.contains("r-clear-button")) {
                        if (event.target.parentElement.classList.contains("r-fb-foot")) {
                            let item = event.target.parentElement.parentElement.parentElement;
                            clearFilters(item, true);
                        } else {
                            clearAllFilters();
                        }
                    } else if (event.target.classList.contains("r-next-button")) {
                        if (event.target.parentElement.classList.contains("r-fb-foot")) {
                            let item = event.target.parentElement.parentElement.parentElement;

                            item.classList.remove("open");

                            let nextItem = item.nextElementSibling;
                            if (!nextItem.classList.contains("r-fb-item")) return;

                            nextItem.classList.add("open");
                            let panel = nextItem.querySelector(".r-fb-panel");
                            if (panel.getAttribute("data-postition") == "right") {
                                panel.style.left = (nextItem.offsetWidth - panel.offsetWidth) + 'px';
                            } else if (!nextItem.parentElement.classList.contains("r-fb-compact") && nextItem.offsetLeft + panel.offsetWidth > nextItem.parentElement.clientWidth) {
                                panel.style.left = '-' + (nextItem.offsetLeft - (nextItem.parentElement.clientWidth - panel.offsetWidth)) + 'px';
                            }
                        }
                    }
                }
            }
        });

        filterbar.addEventListener("change", event => {
            if (event.target.tagName.toLowerCase() == "input" && event.target.classList.contains("r-input-number")) {
                let input = event.target;
                let value = input.value ? parseInt(input.value) : 0;
                let min = input.getAttribute("min") || 0;
                let max = input.getAttribute("max") || 65536;
                if (value < min) {
                    input.value = min;
                } else if (value > max) {
                    input.value = max;
                }
            }

            let item = event.target;
            while (item && !item.classList.contains("r-fb-item")) {
                item = item.parentElement;
            }

            if (item) {
                updateItem(item);
            }
        });

        updateAllItems();

        public = {
            closePanel: function() {
                let currItem = filterbar.querySelector(".r-fb-item.open");
                if (currItem) {
                    currItem.classList.remove("open");
                }
            },
            submit: function() {
                submitForm();
            },
            clearAllFilters: clearAllFilters,
            submitOnBlur: options.submitOnBlur
        };

        return public;
    }
}

window.addEventListener("click", event => {
    let el = event.target;

    while (el && !el.classList.contains("r-fb-item")) el = el.parentElement;
    if (!el) {
        el = document.querySelector(".r-fb-item.open");
        if (el) {
            el.classList.remove("open");
            if (FilterBar.current && FilterBar.current.submitOnBlur) {
                FilterBar.current.submit();
            }
        }
    }

    if (event.target.classList.contains("r-stepper-down")) {
        event.preventDefault();
        let input = event.target.parentElement.querySelector("input");
        let value = input.value ? parseInt(input.value) : 0;
        let min = parseInt(input.getAttribute("min") || '0');
        if (value > min) {
            value--;
        }
        input.value = value;
        input.dispatchEvent(new Event("change", { bubbles: true }));
    } else if (event.target.classList.contains("r-stepper-up")) {
        event.preventDefault();
        let input = event.target.parentElement.querySelector("input");
        let value = input.value ? parseInt(input.value) : 0;
        let max = parseInt(input.getAttribute("max") || '65536');
        if (value < max) {
            value++;
        }
        input.value = value;
        input.dispatchEvent(new Event("change", { bubbles: true }));
    }
});


/**
 * Gallery
 */
class RGallery {
    constructor(anchors) {
        this.element = document.createElement("div");
        this.element.className = "r-gallery";
        this.element.style.display = "none";
        document.body.appendChild(this.element);

        this.element.innerHTML = '\
            <div class="r-gallery-close"><svg class="r-icon" viewbox="0 0 14 14"><use href="#icon_close" x="0" y="0"></svg></div>\
            <div class="r-gallery-prev"><svg class="r-icon" viewbox="0 0 14 14"><use href="#icon_chevron_left" x="0" y="0"></svg></div>\
            <div class="r-gallery-next"><svg class="r-icon" viewbox="0 0 14 14"><use href="#icon_chevron_right" x="0" y="0"></svg></div>\
            <div class="r-gallery-count"><span class="curr"></span> / <span class="total"></span></div>\
            <div class="r-gallery-rail"><div class="r-gallery-slides"></div></div>\
            '

        this.slideIndex = 0;

        this.slidesEl = this.element.querySelector(".r-gallery-slides");
        this.prevButton = this.element.querySelector(".r-gallery-prev");
        this.nextButton = this.element.querySelector(".r-gallery-next");
        this.closeButton = this.element.querySelector(".r-gallery-close");

        let html = "";
        let numSlides = 0;
        for (let anchor of anchors) {
            if (anchor.hasAttribute("href")) {
                anchor.setAttribute("data-r-gallery-index", numSlides);
                html += `<div class="r-gallery-slide"><div class="r-image" style="background-image:url(${anchor.href})"></div></div>`;
                numSlides++;
            }
        }
        this.slidesEl.innerHTML = html;

        this.slides = this.slidesEl.querySelectorAll(".r-gallery-slide");
        this.element.querySelector(".r-gallery-count .total").innerHTML = numSlides;

        this.onResize();
        this.update();

        this.nextButton.addEventListener("click", event => {
            event.preventDefault();
            if (this.slideIndex < this.slides.length - 1) {
                this.slideIndex++;
                this.update();
            }
        });

        this.prevButton.addEventListener("click", event => {
            event.preventDefault();
            if (this.slideIndex > 0) {
                this.slideIndex--;
                this.update();
            }
        });

        this.closeButton.addEventListener("click", event => {
            event.preventDefault();
            this.hide();
        });

        window.addEventListener("resize", this.onWindowResize);
    }

    onResize(event) {
        this.slidesEl.style.width = `${document.body.clientWidth * this.slides.length}px`;
    }

    update() {
        this.slidesEl.style.transform = `translateX(-${document.body.clientWidth * this.slideIndex}px)`;
        this.element.querySelector(".r-gallery-count .curr").innerHTML = this.slideIndex + 1;
        if (this.slideIndex == 0) {
            this.prevButton.classList.add("r-disabled");
        } else {
            this.prevButton.classList.remove("r-disabled");
        }
        if (this.slideIndex == this.slides.length - 1) {
            this.nextButton.classList.add("r-disabled");
        } else {
            this.nextButton.classList.remove("r-disabled");
        }
    }

    onWindowResize() {
        this.onResize();
        this.update();
    }

    show() {
        this.element.style.display = 'block';
        this.onResize();
        this.update();
    }

    hide() {
        this.element.style.display = 'none';
    }

    destroy() {
        window.removeEventListener("resize", this.onWindowResize);
        this.element.parentElement.removeChild(this.element);
    }
}

/**
 * Widgets
 */
{
    let baseUri = 'https://rental.frieslandboating.vakantievaren.nl';
    if (baseUri.match(/^#baseuri#$/)) {
        let scripts = document.getElementsByTagName('script');
        let me = scripts[scripts.length - 1];
        let m = me.src.match(/^https?:\/\/[^\/]+/);
        baseUri = m ? m[0] : '';
    }

    // Rental.require(baseUri + '/themes/extern/css/rental.css');
    // Rental.require(baseUri + '/addthemes/extern/css/rental.css');
    Rental.require(baseUri + '/rentaladmin/widget/styles/theme/', 'css');
    if (Rental.locale != 'nl' && Rental.locale != 'nl-NL') {
        Rental.require(baseUri + '/rentaladmin/widget/translations/' + Rental.locale);
    }

    Rental.widgetKey = 'cd0e57233a8d09db80cc50cf01eaffac';

    let initialized = false;
    let urlHistory = [];
    let datePickers = [];
    let pickingADate = false;

    let navigate = (widget, url, target, selector, replacements, data) => {
        if (urlHistory.length > 0) {
            let widget = urlHistory[urlHistory.length-1].widget;
            if ((widget in Rental.Widgets) && ('deinit' in Rental.Widgets[widget])) {
                Rental.Widgets[widget].deinit(target);
            }
        }
        urlHistory.push({ widget, url, target, selector, replacements });
        return Rental.loadUrl(widget, url, target, selector, replacements, data);
    }

    let navigateBack = () => {
        urlHistory.pop();
        let h = urlHistory[urlHistory.length - 1];
        return Rental.loadUrl(h.widget, h.url, h.target, null, h.replacements);
    }

    let currNav = () => {
        if (urlHistory.length == 0) return null;
        return urlHistory[urlHistory.length - 1];
    }

    let initFilterbar = (fbElement, markedDates, options) => {
        let filterbar = FilterBar(fbElement, options);

        const lang = 'nl';
        const dateFormat = { month: "short", day: "numeric", year: "numeric" };
        let item = fbElement.querySelector(".r-dateitem");
        if (item) {
            const itemLabel = item.querySelector(".r-fb-label");
            const itemPanel = item.querySelector(".r-fb-panel");

            let datePicker;
            const delegate = {
                updateLabel: function(label) {},
                clearValues: function() {
                    itemLabel.innerHTML = Rental.translate("Wanneer");
                    this.classList.remove("set");
                    datePicker.setValue(null);
                    let input = fbElement.querySelector('[name="filters[date]"]');
                    if (input) input.value = "";
                }
            };

            item.fbDelegate = delegate;

            let input = item.querySelector('input[type="date"]');
            datePicker = new RDatePicker(input, {
                inline: true,
                showHeader: false,
                startDate: "today",
                monthSpan: document.body.clientWidth < 768 || !itemPanel.classList.contains("r-fb-large") ? 1 : 2,
                onChange: function(date, value) {
                    let dt = new Date(date.year, date.month - 1, date.day);
                    itemLabel.innerHTML = dt.toLocaleDateString(Rental.locale, dateFormat);
                    item.classList.add("set");

                    if (!pickingADate) {
                        pickingADate = true;
                        for (let dp of datePickers) {
                            if (dp !== datePicker) {
                                dp.setValue(date.toString(), true);
                            }
                        }
                        pickingADate = false;
                    }
                },
                markedDates: markedDates||[]
            });

            datePickers.push(datePicker);
        }

        return filterbar;
    }

    let initLinks = (element) => {
        element.addEventListener("click", event => {
            let target = event.target;
            while (target && !(target.tagName == "A" && target.hasAttribute("href"))) {
                target = target.parentElement;
            }
            if (target) {
                let rel = target.getAttribute("rel");
                switch (rel) {
                    case "object-detail":
                        event.preventDefault();
                        navigate("detail", target.href, element)
                            .then(result => {
                                Rental.Widgets.detail.init(result.target);
                            });
                        break;

                    case "booking":
                        event.preventDefault();
                        let url = target.href + (Rental.Widgets.page.namespace ? '&ns=' + Rental.Widgets.page.namespace : '');
                        navigate("booking", url, element)
                            .then(result => {
                                Rental.Widgets.booking.init(result.target);
                            });
                        break;

                    case "back":
                        event.preventDefault();
                        navigateBack()
                            .then(result => {
                                switch (result.widget) {
                                    case "objects":
                                        Rental.Widgets.objects.init(result.target);
                                        break;
                                    case "detail":
                                        Rental.Widgets.detail.init(result.target);
                                        break;
                                    case "search":
                                        Rental.Widgets.search.init(result.target);
                                        break;
                                    case "booking":
                                        Rental.Widgets.booking.init(result.target);
                                        break;
                                }
                            });
                }
            }
        });

        element.addEventListener("submit", event => {
            if (event.target.hasAttribute("rel")) {
                event.preventDefault();
                let data = Rental.serialize(event.target);
                let rel = event.target.getAttribute("rel");

                switch (rel) {
                    case "object-detail":
                        event.preventDefault();
                        navigate("detail", event.target.action, element, null, null, data)
                            .then(result => {
                                Rental.Widgets.detail.init(result.target);
                            });
                        break;

                    case "booking":
                        event.preventDefault();
                        navigate("booking", event.target.action, element, null, null, data)
                            .then(result => {
                                Rental.Widgets.booking.init(result.target);
                            });
                        break;
                }
            }
        });
    }

    Rental.Widgets = {
        page: {
            beschikbareperiodes: null,
            scrollToContent: false,
            namespace: 'rental',
            embedId: null,
            udpateNamespace: element => {
                if (element.hasAttribute('data-rental-namespace')) {
                    Rental.Widgets.page.namespace = element.getAttribute('data-rental-namespace');
                }
            },
            updateCarts: () => {
                let carts = document.querySelectorAll('[data-rental-cart]');
                if (carts.length) {
                    let url = new URL('/rentaladmin/widget/shoppingcart?format=json', baseUri);
                    let sessionKey = 'rentalSession_' + Rental.Widgets.page.namespace;
                    if (localStorage && localStorage.getItem(sessionKey)) {
                        url.searchParams.set('rentalsession', localStorage.getItem(sessionKey));
                    }
                    let xhr = new XMLHttpRequest();
                    xhr.addEventListener("readystatechange", event => {
                        if (xhr.readyState == XMLHttpRequest.DONE) {
                            if (Math.floor(xhr.status / 100) == 2) {
                                let response = xhr.response;
                                if (typeof response == "string") {
                                    response = JSON.parse(response);
                                }
                                for (let cart of carts) {
                                    cart.classList.add('rental-filled');
                                    cart.innerHTML = response.quantity;
                                }
                            }
                        }
                    });
                    xhr.open("get", url.toString(), true);
                    xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
                    xhr.send();
                }
            }
        },
        search: {
            init: function(element) {
                Rental.Widgets.page.udpateNamespace(element);
                let fbElements = element.querySelectorAll('.r-filterbar');
                datePickers = [];
                for (let fbElement of fbElements) {
                    initFilterbar(fbElement, null, { submitOnClear: false });
                }
            }
        },
        objects: {
            init: function(element, customDetail, pricegroup, objectType, path) {
                let fbElement;
                let filterbar;
                let lastValues;

                Rental.Widgets.page.udpateNamespace(element);

                path = path || '/rentaladmin/widget/objects';

                let onSubmit = (event) => {
                    event.preventDefault();
                    if (filterbar) {
                        filterbar.closePanel();
                    }

                    if (location.search && ("pushState" in history)) {
                        history.pushState({}, document.title, location.protocol+'//'+location.hostname+location.pathname);
                    }

                    let values = Rental.serialize(event.target);
                    let liveFilters = element.querySelectorAll('[data-live-filter]');

                    if (!values) {
                        values = 'filters=clear';
                    }

                    if (values == lastValues) return;

                    let url = baseUri ? new URL(path + '?' + values, baseUri) :  new URL(path + '?' + values);
                    if (customDetail) url.searchParams.set('customdetail', '1');
                    if (pricegroup) url.searchParams.set('pricegroup', pricegroup);
                    if (objectType) url.searchParams.set('type', objectType);
                    url.searchParams.set('widget', Rental.widgetKey);

                    navigate('objects', url.toString(), element, '.r-objectlist')
                        .then(result => {
                            lastValues = values;
                            for (let el of liveFilters) {
                                let domfilter = result.fragment.querySelector(`[data-live-filter="${el.getAttribute('data-live-filter')}"]`);
                                if (domfilter) {
                                    let checkedIds = [];
                                    for (let input of el.querySelectorAll("input")) {
                                        if (input.checked) checkedIds.push(input.id);
                                    }
                                    el.innerHTML = domfilter.innerHTML;
                                    for (let id of checkedIds) {
                                        let input = el.querySelector('#'+id);
                                        if (input) input.cheked = true;
                                    }
                                } else {
                                    console.log("Could not update live filter " + el.getAttribute('data-live-filter'));
                                }
                            }

                            if ('jQuery' in window) {
                                jQuery(element).trigger("rental.contentloaded");
                            }
                        });
                }

                datePickers = [];
                fbElement = element.querySelector('.r-filterbar');
                filterbar = initFilterbar(fbElement, null, { submitOnBlur: true });
                fbElement.addEventListener("submit", onSubmit);

                element.addEventListener("click", event => {
                    let target = event.target;
                    if (target && target.hasAttribute('data-clear-all') && filterbar) {
                        event.preventDefault();
                        filterbar.clearAllFilters();
                    }
                });
            },
        },
        detail: {
            init: function(element, keepDatepickers) {
                Rental.Widgets.page.udpateNamespace(element);

                datePickers = [];
                Rental.Widgets.page.beschikbareperiodes = element.querySelector('table[data-listing]');



                function onSubmit(event){
                    event.preventDefault();

                    let currItem = document.querySelector(".r-fb-item.open");
                    if (currItem) {
                        currItem.classList.remove("open");
                    }

                    let date = this.querySelector('input[name="filters[date]"]').value;
                    currDate = date;
                    Rental.loadPeriods(Rental.Widgets.page.beschikbareperiodes, null, {
                        limit: 6,
                        date: date,
                        priceTable: true,
                        baseUri: baseUri,
                        scrollTo: true
                    });
                }

                Rental.Widgets.page.beschikbareperiodes.addEventListener("rental.changedate", function(event) {
                    for (let datePicker of datePickers) {
                        datePicker.setValue(event.data.date, true);
                    }
                    currDate = event.data.date;
                    Rental.loadPeriods(Rental.Widgets.page.beschikbareperiodes, null, {
                        limit: 6,
                        date: event.data.date,
                        priceTable: true,
                        baseUri: baseUri
                    });
                });
                Rental.Widgets.page.beschikbareperiodes.addEventListener("rental.ready", function(event) {
                    var m1, m2, td;
                    m1 = location.search.match(/(\?|&)date_from=([^&]+)/);
                    if (m1) {
                        m2 = location.search.match(/(\?|&)period=([^&]+)/);
                        if (m2) {
                            td = this.querySelector('[data-period="' + m2[2] + '"] [data-date="' + m1[2] + '"]');
                            if (!td) td = this.querySelector('[data-period="' + m2[2] + '"] .pt-selectable');
                        } else {
                            td = this.querySelector('[data-date="' + m1[2] + '"]');
                            if (!td) td = this.querySelector('.pt-selectable');
                        }
                        Rental.selectPriceOption(td);
                    }
                });

                Rental.loadPeriods(Rental.Widgets.page.beschikbareperiodes, null, {
                    limit: 6,
                    date: element.querySelector('input[name="filters[date]"]').value,
                    periods: [],
                    priceTable: true,
                    baseUri: baseUri
                });

                let mdScript = element.querySelector('script.r-available-dates');
                let markedDates = null;
                if (mdScript) {
                    markedDates = JSON.parse(mdScript.innerText.replace(/,\]/g, ']'));
                }

                let fbElements = element.querySelectorAll('.r-filterbar');
                if (!keepDatepickers) datePickers = [];
                for (let fbElement of fbElements) {
                    initFilterbar(fbElement, markedDates, { submitOnBlur: true });
                    fbElement.addEventListener("submit", onSubmit);
                }

                let readmore = element.querySelector('.r-readmore');
                if (readmore && readmore.scrollHeight > readmore.clientHeight) {
                    readmore.classList.add("r-active");
                    let button = readmore.querySelector(".r-button");
                    button.addEventListener("click", event => {
                        event.preventDefault();
                        if (readmore.classList.contains("open")) {
                            button.innerHTML = "Lees meer";
                            readmore.classList.remove("open");
                        } else {
                            button.innerHTML = "Minder tekst";
                            readmore.classList.add("open");
                        }
                    });
                }

                this.gallery = null;

                let links = element.querySelectorAll("[data-r-gallery]");
                let onClick = event => {
                    event.preventDefault();
                    let anchor = event.target;
                    while (anchor && !anchor.hasAttribute('data-r-gallery')) {
                        anchor = anchor.parentElement;
                    }
                    if (anchor) {
                        if (!this.gallery) {
                            this.gallery = new RGallery(links);
                        }
                        this.gallery.slideIndex = parseInt(anchor.getAttribute('data-r-gallery-index'));
                        this.gallery.show();
                    }
                }

                for (let link of links) {
                    link.addEventListener("click", onClick);
                }

                let nav = currNav();
                let  m = nav ? nav.url.match(/#(.+)$/) : null;
                if (!m) {
                    m = location.hash.match(/#(.+)$/);
                }
                if (m && m[1] == 'boeken') {
                    let bookingbox = element.querySelector('.r-bookingbox');
                    if (bookingbox) {
                        let top = 0;
                        let el = bookingbox;
                        while (el) {
                            top += el.offsetTop;
                            el = el.offsetParent;
                        }
                        window.scrollTo(window.pageXOffset, top - 100);
                    }
                }
            },
            deinit: function(element) {
                if (this.gallery) {
                    this.gallery.destroy();
                    this.gallery = null;
                }
            }
        },
        availability: {
            init: function(element) {
                Rental.Widgets.page.udpateNamespace(element);
                Rental.Widgets.detail.init(element, true);
            }
        },
        detailFilter: {
            init: function(element) {
                Rental.Widgets.page.udpateNamespace(element);

                function onSubmit(event){
                    event.preventDefault();

                    let currItem = document.querySelector(".r-fb-item.open");
                    if (currItem) {
                        currItem.classList.remove("open");
                    }

                    if (Rental.Widgets.page.beschikbareperiodes) {
                        let date = this.querySelector('input[name="filters[date]"]').value;
                        currDate = date;
                        Rental.loadPeriods(Rental.Widgets.page.beschikbareperiodes, null, {
                            limit: 6,
                            date: date,
                            priceTable: true,
                            baseUri: baseUri,
                            scrollTo: true
                        });
                    }
                }

                let mdScript = element.querySelector('script.r-available-dates');
                let markedDates = null;
                if (mdScript) {
                    markedDates = JSON.parse(mdScript.innerText.replace(/,\]/g, ']'));
                }

                let fbElements = element.querySelectorAll('.r-filterbar');
                for (let fbElement of fbElements) {
                    initFilterbar(fbElement, markedDates);
                    fbElement.addEventListener("submit", onSubmit);
                }
            }
        },
        booking: {
            init: function(element) {
                Rental.Widgets.page.udpateNamespace(element);

                if (location.search && location.search.indexOf('periodkey=') > -1 && ('pushState' in history)) {
                    history.pushState({}, document.title, location.pathname);
                }
                let m = Rental.currUrl.match(/(\?|&)act=([a-z0-9]+)/);
                let action = m ? m[2] : "list";

                Rental.Widgets.page.updateCarts();

                switch (action) {
                    case "list":
                        this.initList(element);
                        break;
                    case "details":
                        this.initDetails(element);
                        break;
                    case "form":
                        this.initForm(element);
                        break;
                }
            },
            initList: function(element) {
                let form = element.querySelector('form');

                onInputChange = () => {
                    let values = Rental.serialize(form, true);
                    let containerSelector = "#sideSelection";
                    let container = element.querySelector(containerSelector);
                    container.style.opacity = 0.5;
                    Rental.loadUrl("booking", form.action, element, containerSelector, null, values, true, false)
                        .then(result => {
                            container.style.opacity = 1;
                        });
                }

                form.addEventListener("change", onInputChange);

                form.addEventListener("click", event => {
                    let el = event.target;
                    while (el && !(el.hasAttribute('data-rental-confirm-delete') || el.hasAttribute('data-rental-reset-cart'))) {
                        el = el.parentElement;
                    }

                    if (el) {
                        event.preventDefault();
                        if (el.hasAttribute('data-rental-confirm-delete')) {
                            let id = el.getAttribute('data-rental-confirm-delete');
                            let objectEl = document.querySelector(`[data-rental-id="${id}"]`);
                            if (objectEl) {
                                let headlineEl = objectEl.querySelector(`[data-rental-headline]`);
                                if (headlineEl) {
                                    let headline = headlineEl.innerHTML;
                                    if (confirm("Weet u zeker dat u " + headline + " wilt verwijderen?")) {
                                        let input = document.querySelector(`input[name="qt[${id}]"]`);
                                        if (input) {
                                            input.value = 0;
                                            onInputChange.call(document.querySelector('form'));
                                            objectEl.parentElement.removeChild(objectEl);
                                            setTimeout(() => {
                                                Rental.Widgets.page.updateCarts();
                                            }, 2000);
                                        }
                                    }
                                }
                            }
                        } else {
                            if (confirm("Weet u zeker dat u alle objecten wilt verwijderen?")) {
                                let inputs = document.querySelectorAll(`input[name^="qt["]`);
                                for (input of inputs) {
                                    input.value = 0;
                                }
                                onInputChange.call(document.querySelector('form'));
                                document.getElementById("objects").innerHTML = "";
                            }
                        }
                    }
                });
            },
            initDetails: function(element) {
                let form = element.querySelector('form');

                onInputChange = () => {
                    let values = Rental.serialize(form).replace(/&bewaren=[^&]*/, '');
                    let containerSelector = "#sideSelection";
                    let container = element.querySelector(containerSelector);
                    container.style.opacity = 0.5;
                    Rental.loadUrl("booking", form.action, element, containerSelector, null, values, true, false)
                        .then(result => {
                            container.style.opacity = 1;
                        });
                }

                form.addEventListener("change", onInputChange);
            },
            initForm: function(element) {
                function onSelectChange() {
                    let rSelect = this.parentElement;
                    let label = "&nbsp;";
                    if (this.selectedIndex > -1) {
                        label = this.options[this.selectedIndex].innerHTML;
                    }
                    rSelect.querySelector('.r-select-label').innerHTML = label;
                }

                let selects = element.querySelectorAll(".r-form select");
                for (let select of selects) {
                    let rSelect = document.createElement('div');
                    rSelect.className = 'r-select';
                    rSelect.innerHTML = '<span class="r-select-label"></span>';
                    select.parentElement.insertBefore(rSelect, select);
                    rSelect.appendChild(select);
                    onSelectChange.call(select);
                    select.addEventListener("change", onSelectChange);
                }

                element.querySelector('#form-c_first_name').setAttribute('placeholder', Rental.translate('Voornaam'));
                element.querySelector('#form-c_middle_name').setAttribute('placeholder', Rental.translate('Tussenvoegsel'));
                element.querySelector('#form-c_last_name').setAttribute('placeholder', Rental.translate('Achternaam'));

                element.querySelector('#form-c_address1').setAttribute('placeholder', Rental.translate('Straatnaam'));
                element.querySelector('#form-c_housenumber').setAttribute('placeholder', Rental.translate('Nr'));
                element.querySelector('#form-c_zip_code').setAttribute('placeholder', Rental.translate('Postcode'));
                element.querySelector('#form-c_city').setAttribute('placeholder', Rental.translate('Plaats'));

                element.querySelector('#form-c_phone').setAttribute('placeholder', Rental.translate('Telefoonnummer'));
                element.querySelector('#form-c_mobile').setAttribute('placeholder', Rental.translate('Mobiel'));
                element.querySelector('#form-c_email').setAttribute('placeholder', Rental.translate('E-mailadres'));
            }
        },
        grid: {
            init: function (element, options) {
                let gridBoard = GridBoard(
                    element.querySelector(".r-gridboard"),
                    options && ('baseUrl' in options) ? options.baseUrl : null
                );
                let lastValues = null;

                if (element.hasAttribute("data-rental-watch-header")) {
                    gridBoard.config('watchHeader', element.getAttribute("data-rental-watch-header"));
                }
                if (options && ('headerSizes' in options)) {
                    gridBoard.config('headerSizes', options.headerSizes);
                }

                element.querySelector("form").addEventListener("change", function (event) {
                    let values = Rental.serialize(this);
                    if (values != lastValues) {
                        lastValues = values;
                        gridBoard.loadWithValues(values);
                    }
                });

            }
        }
    };

    function generateCalClasses() {
        let htmlStyle = getComputedStyle(document.documentElement);

        let availableColor = `rgba(${htmlStyle.getPropertyValue('--r-color-success-rgb')}, 0.2)`;
        let unavailableColor = `rgba(${htmlStyle.getPropertyValue('--r-text-color-rgb')}, 0.05)`;

        let svg1 = `<svg version="1.1" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24l-24 24z" fill="${availableColor}"/><path d="M24 0v24h-24z" fill="${unavailableColor}"/></svg>`;
        let svg2 = `<svg version="1.1" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24l-24 24z" fill="${unavailableColor}"/><path d="M24 0v24h-24z" fill="${availableColor}"/></svg>`;

        let css = `
            .cal-view .marked-available-morning, .cal-legend .marked-available-morning {
                background: center / 100% no-repeat url("data:image/svg+xml;base64,${btoa(svg1)}");
            }
            .cal-view .marked-available-evening, .cal-legend .marked-available-evening {
                background: center / 100% no-repeat url("data:image/svg+xml;base64,${btoa(svg2)}");
            }`;

        let head = document.head || document.getElementsByTagName('head')[0],
            style = document.createElement('style');

        head.appendChild(style);

        style.type = 'text/css';
        if (style.styleSheet){
            // This is required for IE8 and below.
            style.styleSheet.cssText = css;
        } else {
            style.appendChild(document.createTextNode(css));
        }
    }

    window.addEventListener("load", event => {
        if (initialized) return;
        initialized = true;

        generateCalClasses();

        let containers = document.querySelectorAll('[data-rental-widget]');
        for (container of containers) {
            container.classList.add("r-container");
            container.classList.add("r-init");

            initLinks(container);
            let widget = container.getAttribute('data-rental-widget');
            let embedId = container.getAttribute('data-rental-embed');
            let replacements;
            let objectId;
            let customDetail = false;
            let url;

            if (embedId) {
                Rental.Widgets.page.embedId = embedId;
            }

            if (container.hasAttribute('data-rental-booking-url')) {
                Rental.bookingUrl = container.getAttribute('data-rental-booking-url');
            }

            switch (widget) {
                case 'search':
                    replacements = {
                        target_url: container.getAttribute('data-rental-target-url') || location.toString()
                    };
                    navigate('search', baseUri + '/rentaladmin/widget/search', container, null, replacements)
                        .then(result => {
                            Rental.Widgets.search.init(result.target);
                            result.target.classList.remove("r-init");
                        });
                    break;
                case 'objects':
                    customDetail = container.hasAttribute("data-rental-custom-detail");
                    pricegroup = container.getAttribute("data-rental-group");
                    objectType = container.getAttribute("data-rental-type");
                    url = new URL('/rentaladmin/widget/objects' + location.search, baseUri);
                    if (customDetail) url.searchParams.append('customdetail', '1');
                    if (pricegroup) url.searchParams.append('pricegroup', pricegroup);
                    if (objectType) url.searchParams.append('type', objectType);
                    navigate('objects', url.toString(), container)
                        .then(result => {
                            Rental.Widgets.objects.init(result.target, customDetail, pricegroup, objectType);
                            result.target.classList.remove("r-init");
                        });
                    break;
                case 'availability':
                    objectId = container.getAttribute("data-rental-object");
                    if (!objectId) {
                        console.error("No object ID");
                        return;
                    }
                    navigate('availability', baseUri + '/rentaladmin/widget/availability?id=' + objectId + location.search.replace(/^\?/, '&'), container)
                        .then(result => {
                            Rental.Widgets.availability.init(result.target);
                            result.target.classList.remove("r-init");
                        });
                    break;
                case 'booking':
                    navigate('booking', baseUri + '/rentaladmin/widget/booking' + location.search, container)
                        .then(result => {
                            Rental.Widgets.booking.init(result.target);
                            result.target.classList.remove("r-init");
                        });
                    break;
                case 'detail-filter':
                    objectId = container.getAttribute("data-rental-object");
                    if (!objectId) {
                        console.error("No object ID");
                        return;
                    }
                    url = new URL('/rentaladmin/widget/detail-filter' + location.search, baseUri);
                    url.searchParams.set('id', objectId);
                    navigate('detailFilter', url.toString(), container)
                        .then(result => {
                            Rental.Widgets.detailFilter.init(result.target);
                            result.target.classList.remove("r-init");
                        });
                    break;
                case 'grid':
                    customDetail = container.hasAttribute("data-rental-custom-detail");
                    pricegroup = container.getAttribute("data-rental-group");
                    objectType = container.getAttribute("data-rental-type");
                    bookingUrl = container.getAttribute("data-rental-booking-url");
                    url = new URL('/rentaladmin/widget/grid' + location.search, baseUri);
                    if (customDetail) url.searchParams.append('customdetail', '1');
                    if (pricegroup) url.searchParams.append('pricegroup', pricegroup);
                    if (objectType) url.searchParams.append('type', objectType);
                    if (bookingUrl) url.searchParams.append('booking-url', bookingUrl);
                    navigate('grid', url.toString(), container)
                        .then(result => {
                            Rental.Widgets.grid.init(result.target, { baseUrl: baseUri + '/rentaladmin/widget/grid' });
                            result.target.classList.remove("r-init");
                        });
                    break;
            }

            setTimeout(() => Rental.Widgets.page.scrollToContent = true, 2000);
        }

        if (containers.length == 0) {
            console.error("No containers detected");
        }

        Rental.Widgets.page.updateCarts();
    });
}
