You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

716 lines
21 KiB

// JavaScript Document
/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
var CLNDR = null,
calendar = function (timeobject, trigger_elmnt, date_time_format) {
if (!this.checkOuterObj(timeobject)) {
throw 'Calendar: constructor expects second parameter to be input form field DOM node.';
}
this.id = jQuery(trigger_elmnt).attr('id');
this.trigger_elmnt = trigger_elmnt;
this.date_time_format = date_time_format;
this.sdt = new CDate();
};
function toggleCalendar(trigger_elmnt, time_input, date_time_format) {
if (CLNDR && jQuery(trigger_elmnt).is(CLNDR.trigger_elmnt) && CLNDR.is_visible) {
CLNDR.clndrhide();
}
else {
CLNDR && CLNDR.clndrhide();
CLNDR = new calendar(time_input, trigger_elmnt, date_time_format);
CLNDR.clndrshow();
}
}
calendar.prototype = {
id: null, // Calendar ID. Should be unique on page.
sdt: null, // Date object of a selected date.
month: 0, // Represents currently opened month number.
year: 2008, // Represents currently opened year.
day: 1, // Represents currently opened day.
clndr_calendar: null, // html obj of calendar
clndr_month_div: null, // html obj
clndr_year_div: null, // html obj
clndr_days: null, // html obj
clndr_month: null, // html obj
clndr_year: null, // html obj
clndr_year_wrap: null, // html obj
clndr_month_wrap: null, // html obj
clndr_monthup: null, // html bttn obj
clndr_monthdown: null, // html bttn obj
clndr_yearup: null, // html bttn obj
clndr_yeardown: null, // html bttn obj
timeobject: null, // Input field with selected time. Source and destination of selected date.
is_visible: false, // State of calendar visibility.
has_user_time: false, // Confirms, if time was selected from input field.
hl_month: null, // highlighted month number
hl_year: null, // highlighted year number
hl_day: null, // highlighted days number
active_section: null, // Active calendar section. See 'sections' array. Default value set in method clndrshow.
monthname: new Array(t('S_JANUARY'), t('S_FEBRUARY'), t('S_MARCH'), t('S_APRIL'), t('S_MAY'), t('S_JUNE'),
t('S_JULY'), t('S_AUGUST'), t('S_SEPTEMBER'), t('S_OCTOBER'), t('S_NOVEMBER'), t('S_DECEMBER')
),
dayname: new Array(t('S_SUNDAY'), t('S_MONDAY'), t('S_TUESDAY'), t('S_WEDNESDAY'), t('S_THURSDAY'), t('S_FRIDAY'),
t('S_SATURDAY')
),
sections: new Array('.calendar-year', '.calendar-month', '.calendar-date'),
date_time_format: PHP_ZBX_FULL_DATE_TIME,
trigger_elmnt: null, // Calendar visibility trigger element.
ondateselected: function() {
this.setDateToOuterObj();
this.clndrhide();
},
clndrhide: function() {
if (this.is_visible) {
this.calendardelete();
this.is_visible = false;
jQuery(window).off('resize', this.calendarPositionHandler);
jQuery(document)
.off('click', this.calendarDocumentClickHandler)
.off('keydown', this.calendarKeyDownHandler)
.off('keyup', this.calendarKeyUpHandler);
removeFromOverlaysStack(this.id);
}
},
clndrshow: function() {
this.calendarcreate();
this.setSDateFromOuterObj();
this.syncBSDateBySDT();
this.syncHlDate();
this.setCDate();
this.calendarPositionHandler();
this.clndr_calendar.style.display = (this.clndr_calendar.tagName === 'span') ? 'inline' : 'block';
this.is_visible = true;
jQuery(window).on('resize', jQuery.proxy(this.calendarPositionHandler, this));
jQuery(this.trigger_elmnt).on('remove', jQuery.proxy(this.clndrhide, this));
jQuery(document)
.on('keydown', jQuery.proxy(this.calendarKeyDownHandler, this))
.on('keyup', jQuery.proxy(this.calendarKeyUpHandler, this))
.on('click', jQuery.proxy(this.calendarDocumentClickHandler, this));
addToOverlaysStack(this.id, this.trigger_elmnt, 'clndr');
this.active_section = this.sections.indexOf('.calendar-date');
this.focusSection();
},
setPosition: function(top, left) {
jQuery(this.clndr_calendar).css({
top: Math.min(top, jQuery(window).height() - jQuery(this.clndr_calendar).outerHeight()) + 'px',
left: Math.min(left, jQuery(window).width() - jQuery(this.clndr_calendar).outerWidth()) + 'px'
});
},
calendarDocumentClickHandler: function(e) {
var $target = jQuery(e.target);
if (!$target.is(this.trigger_elmnt) && !$target.closest('.overlay-dialogue.calendar').length) {
this.clndrhide();
}
},
calendarPositionHandler: function () {
var $anchor = jQuery(this.trigger_elmnt),
offset = $anchor.offset();
this.setPosition(offset.top + $anchor.height(), offset.left + $anchor.width());
},
/**
* This function is workaround for Firefox bug.
*
* When triggering keydown event on [space] button, event is called for both, the actual element as well as calendar
* icon elemnet, so the calendar is first closed (by handler of actually focused element) and immediately opened
* again (by calendar icon element's handler).
*
* Workaround works as follow - it separates [enter] and [space] button in 2 handlers with similar functionality
* (since pressing [space] and [enter] does the same thing in calendar). Keyup handles the [space] click event,
* while other keyboard events are handled by keydown event.
*/
calendarKeyUpHandler: function(event) {
if (event.which == 32) { // Space
// Enter has special meaning for each Calendar section.
var active_section = this.sections[this.active_section];
if (active_section === '.calendar-year' || active_section === '.calendar-month') {
this.active_section++;
this.focusSection();
}
else if (active_section === '.calendar-date') {
this.setday(event, this.hl_day, this.hl_month, this.hl_year);
}
return false; // Prevent page scrolling when pressing Space.
}
},
calendarKeyDownHandler: function(event) {
var hl_date;
if (this.active_section < 0 || this.active_section > this.sections.length) {
this.active_section = 0;
}
switch (event.which) {
case 37: // arrow left
case 38: // arrow up
case 39: // arrow right
case 40: // arrow down
switch (this.sections[this.active_section]) {
case '.calendar-date':
hl_date = new Date(this.hl_year, this.hl_month, this.hl_day, 0, 0, 0, 0);
switch (event.which) {
case 37: // arrow left
hl_date.setDate(hl_date.getDate() - 1);
break;
case 38: // arrow up
hl_date.setDate(hl_date.getDate() - 7);
break;
case 39: // arrow right
hl_date.setDate(hl_date.getDate() + 1);
break;
case 40: // arrow down
hl_date.setDate(hl_date.getDate() + 7);
break;
}
this.hl_year = hl_date.getFullYear();
this.hl_month = hl_date.getMonth();
this.hl_day = hl_date.getDate();
jQuery('td.highlighted', this.clndr_calendar)
.removeClass('highlighted')
.attr('tabindex', '-1');
if (this.hl_year != this.year || this.hl_month != this.month) {
this.year = this.hl_year;
this.month = this.hl_month;
this.day = this.hl_day;
this.setCDate();
}
jQuery('td[data-date='+this.hl_day+']', this.clndr_calendar)
.addClass('highlighted')
.attr('tabindex', '0')
.focus();
break;
case '.calendar-year':
// Arrow left or arrow down.
if (event.which == 37 || event.which == 40) {
this.yeardown();
}
// Arrow right or arrow up.
else if (event.which == 38 || event.which == 39) {
this.yearup();
}
break;
case '.calendar-month':
// Arrow left or arrow down.
if (event.which == 37 || event.which == 40) {
this.monthdown();
}
// Arrow right or arrow up.
else if (event.which == 38 || event.which == 39) {
this.monthup();
}
break;
}
// Prevent page scrolling.
event.preventDefault();
break;
case 9: // Tab
event.preventDefault();
if (event.shiftKey) {
this.active_section--;
if (this.active_section < 0) {
this.active_section = this.sections.length - 1;
}
}
else {
this.active_section++;
if (this.active_section >= this.sections.length) {
this.active_section = 0;
}
}
this.focusSection();
break;
case 13: // Enter
// Enter has special meaning for each Calendar section.
var active_section = this.sections[this.active_section];
if (active_section === '.calendar-year' || active_section === '.calendar-month') {
this.active_section++;
this.focusSection();
}
else if (active_section === '.calendar-date') {
this.setday(event, this.hl_day, this.hl_month, this.hl_year);
}
return false;
case 32: // Prevent page scrolling when pressing Space.
return false;
}
},
focusSection: function() {
var section_to_focus = this.sections[this.active_section];
jQuery('.highlighted', this.clndr_calendar).removeClass('highlighted').blur();
if (section_to_focus === '.calendar-year' || section_to_focus === '.calendar-month') {
jQuery(section_to_focus, this.clndr_calendar).addClass('highlighted').focus();
}
else if (section_to_focus === '.calendar-date') {
/**
* Switching between months and years, date picker will highlight previously selected date. If
* selected date is in different year or month, the first date of displayed year is highlighted.
* Same happens also if the number of dates in selected month is smaller than selected date in different
* month.
*/
if (this.hl_year != this.year || this.hl_month != this.month
|| new Date(this.year, this.month + 1, 0).getDate() < this.hl_day) {
this.hl_day = 1;
}
jQuery('td[data-date='+this.hl_day+']', this.clndr_calendar)
.addClass('highlighted')
.attr('tabindex', '0')
.focus();
}
},
checkOuterObj: function(timeobject) {
if (typeof(timeobject) === 'undefined' || empty(timeobject)) {
return false;
}
this.timeobject = document.getElementById(timeobject);
if (this.timeobject === null || this.timeobject.tagName.toLowerCase() !== 'input') {
return false;
}
return true;
},
setSDateFromOuterObj: function() {
var val = this.timeobject.value,
/**
* Date and time format separators must be synced with ZBX_FULL_DATE_TIME, ZBX_DATE_TIME and ZBX_DATE in
* defines.inc.php.
*/
datetime = val.split(' '),
date = datetime[0].split('-'),
time = (datetime.length > 1) ? datetime[1].split(':') : new Array();
// Open calendar with current time by default.
this.sdt = new CDate();
this.has_user_time = false;
if (date.length === 3 && this.setSDateDMY(date[2], date[1], date[0])) {
if (!time.length) {
return;
}
this.sdt.setTimeObject(null, null, null, 0, 0, 0);
// Set time to calendar, so time doesn't change when selecting different date.
if ((time.length === 2 || time.length === 3)
&& time[0] > -1 && time[0] < 24
&& time[1] > -1 && time[1] < 60) {
this.has_user_time = true;
this.sdt.setHours(time[0]);
this.sdt.setMinutes(time[1]);
if (time.length === 3 && time[2] > -1 && time[2] < 60) {
this.sdt.setSeconds(time[2]);
}
}
}
},
setSDateDMY: function(d, m, y) {
var dateHolder = new Date(y, m - 1, d, 0, 0, 0);
if (y >= 1970 && dateHolder.getFullYear() == y && dateHolder.getMonth() == m - 1 && dateHolder.getDate() == d) {
this.sdt.setTimeObject(y, m - 1, d);
return true;
}
return false;
},
setDateToOuterObj: function() {
var $input = jQuery(this.timeobject),
new_val = this.sdt.format(this.date_time_format);
if ($input.val() != new_val) {
$input.val(new_val).trigger('change');
}
},
setday: function(e, day, month, year) {
this.day = day;
this.month = month;
this.year = year;
this.syncSDT();
this.syncBSDateBySDT();
this.ondateselected();
},
monthup: function() {
this.month++;
if (this.month > 11) {
// prevent months from running in loop in year 2038
if (this.year < 2038) {
this.month = 0;
this.yearup();
}
else {
this.month = 11;
}
}
else {
this.setCDate();
}
this.hl_month = this.month;
this.hl_year = this.year;
},
monthdown: function() {
this.month--;
if (this.month < 0) {
// prevent months from running in loop in year 1970
if (this.year > 1970) {
this.month = 11;
this.yeardown();
}
else {
this.month = 0;
}
}
else {
this.setCDate();
}
this.hl_month = this.month;
this.hl_year = this.year;
},
yearup: function() {
if (this.year >= 2038) {
return ;
}
this.year++;
this.setCDate();
this.hl_year = this.year;
},
yeardown: function() {
if (this.year <= 1970) {
return ;
}
this.year--;
this.setCDate();
this.hl_year = this.year;
},
syncBSDateBySDT: function() {
this.day = this.sdt.getDate();
this.month = this.sdt.getMonth();
this.year = this.sdt.getFullYear();
},
syncHlDate: function() {
this.hl_day = this.day;
this.hl_month = this.month;
this.hl_year = this.year;
},
syncSDT: function() {
var hours = 0,
minutes = 0,
seconds = 0;
if (this.has_user_time) {
// If time was present in input field, use it.
hours = this.sdt.getHours();
minutes = this.sdt.getMinutes();
seconds = this.sdt.getSeconds();
}
else {
var cdt = new CDate();
// If today was selected, use current time. Otherwise use 00:00:00.
if (cdt.getFullYear() === this.year && cdt.getMonth() === this.month && cdt.getDate() === this.day) {
hours = cdt.getHours();
minutes = cdt.getMinutes();
seconds = cdt.getSeconds();
}
}
this.sdt.setTimeObject(this.year, this.month, this.day, hours, minutes, seconds);
},
setCDate: function() {
this.clndr_month.textContent = this.monthname[this.month];
this.clndr_year.textContent = this.year;
this.createDaysTab();
},
createDaysTab: function() {
var tbody = this.clndr_days;
tbody.innerHTML = '';
var cdt = new CDate();
// Start drawing days from first week of the month.
cdt.setTimeObject(this.year, this.month, 1);
// make 0 - Monday, not Sunday (as default)
var prev_days = cdt.getDay() - 1;
if (prev_days < 0) {
prev_days = 6;
}
// Set to first day of the week.
if (prev_days > 0) {
cdt.setTime(cdt.getTime() - prev_days * 86400000);
}
for (var y = 0; y < 6; y++) {
var tr = document.createElement('tr');
tr.setAttribute('role', 'presentation');
tbody.appendChild(tr);
for (var x = 0; x < 7; x++) {
var td = document.createElement('td');
tr.appendChild(td);
if (this.month != cdt.getMonth()) {
$(td).addClass('grey');
}
else {
td.setAttribute('data-date', cdt.getDate());
}
if (this.sdt.getFullYear() == cdt.getFullYear()
&& this.sdt.getMonth() == cdt.getMonth()
&& this.sdt.getDate() == cdt.getDate()) {
$(td).addClass('selected');
}
td.setAttribute('aria-label', this.calendarGetReadableDate(cdt));
td.setAttribute('tabindex', '-1');
td.setAttribute('role', 'button');
var span = document.createElement('span');
span.setAttribute('aria-hidden', 'true');
span.appendChild(document.createTextNode(cdt.getDate()));
td.appendChild(span);
addListener(td, 'click', this.setday.bindAsEventListener(
this, cdt.getDate(), cdt.getMonth(), cdt.getFullYear()
));
cdt.setTime(cdt.getTime() + 86400000); // + 1day
}
}
},
calendarGetReadableDate: function(cdt) {
return cdt.getDate() + ', ' + this.dayname[cdt.getDay()] + ' ' + this.monthname[cdt.getMonth()] + ' ' +
cdt.getFullYear();
},
/**
* Create and append calendar DOM element to body.
*/
calendarcreate: function() {
this.clndr_calendar = document.createElement('div');
this.clndr_calendar.className = 'overlay-dialogue calendar';
this.clndr_calendar.setAttribute('aria-label', t('S_CALENDAR'));
this.clndr_calendar.setAttribute('role', 'application');
this.clndr_calendar.setAttribute('tabindex', '0');
this.clndr_calendar.style.display = 'none';
document.body.appendChild(this.clndr_calendar);
/*
* Calendar header
*/
var header = document.createElement('div');
this.clndr_calendar.appendChild(header);
header.className = 'calendar-header';
// year
this.clndr_year_div = document.createElement('div');
this.clndr_year_div.setAttribute('role', 'presentation');
this.clndr_year_div.className = 'calendar-year';
header.appendChild(this.clndr_year_div);
var arrow_left = document.createElement('span');
arrow_left.className = 'arrow-left';
var arrow_right = document.createElement('span');
arrow_right.className = 'arrow-right';
this.clndr_yeardown = document.createElement('button');
this.clndr_yeardown.setAttribute('type', 'button');
this.clndr_yeardown.setAttribute('tabindex', '-1');
this.clndr_yeardown.className = 'btn-grey';
this.clndr_yeardown.appendChild(arrow_left);
this.clndr_year_div.appendChild(this.clndr_yeardown);
this.clndr_year = document.createTextNode('');
this.clndr_year_wrap = document.createElement('span');
this.clndr_year_wrap.appendChild(this.clndr_year);
this.clndr_year_wrap.setAttribute('aria-live', 'assertive');
this.clndr_year_wrap.setAttribute('id', 'current-year'+this.id);
this.clndr_year_wrap.setAttribute('aria-atomic', 'true');
this.clndr_year_div.appendChild(this.clndr_year_wrap);
this.clndr_year_div.setAttribute('aria-labelledby', this.clndr_year_wrap.id);
this.clndr_yearup = document.createElement('button');
this.clndr_yearup.setAttribute('type', 'button');
this.clndr_yearup.setAttribute('tabindex', '-1');
this.clndr_yearup.className = 'btn-grey';
this.clndr_yearup.appendChild(arrow_right);
this.clndr_year_div.appendChild(this.clndr_yearup);
// month
this.clndr_month_div = document.createElement('div');
this.clndr_month_div.className = 'calendar-month';
this.clndr_month_div.setAttribute('role', 'presentation');
header.appendChild(this.clndr_month_div);
var arrow_left = document.createElement('span');
arrow_left.className = 'arrow-left';
var arrow_right = document.createElement('span');
arrow_right.className = 'arrow-right';
this.clndr_monthdown = document.createElement('button');
this.clndr_monthdown.setAttribute('type', 'button');
this.clndr_monthdown.setAttribute('tabindex', '-1');
this.clndr_monthdown.className = 'btn-grey';
this.clndr_monthdown.appendChild(arrow_left);
this.clndr_month_div.appendChild(this.clndr_monthdown);
this.clndr_month = document.createTextNode('');
this.clndr_month_wrap = document.createElement('span');
this.clndr_month_wrap.setAttribute('aria-live', 'assertive');
this.clndr_month_wrap.setAttribute('aria-atomic', 'true');
this.clndr_month_wrap.setAttribute('id', 'current-month'+this.id);
this.clndr_month_wrap.appendChild(this.clndr_month);
this.clndr_month_div.appendChild(this.clndr_month_wrap);
this.clndr_month_div.setAttribute('aria-labelledby', this.clndr_month_wrap.id);
this.clndr_monthup = document.createElement('button');
this.clndr_monthup.setAttribute('type', 'button');
this.clndr_monthup.setAttribute('tabindex', '-1');
this.clndr_monthup.className = 'btn-grey';
this.clndr_monthup.appendChild(arrow_right);
this.clndr_month_div.appendChild(this.clndr_monthup);
// days heading
var table = document.createElement('table');
this.clndr_calendar.appendChild(table);
var thead = document.createElement('thead');
thead.setAttribute('role', 'presentation');
table.appendChild(thead);
var tr = document.createElement('tr');
thead.appendChild(tr);
[
t('S_MONDAY_SHORT_BIG'),
t('S_TUESDAY_SHORT_BIG'),
t('S_WEDNESDAY_SHORT_BIG'),
t('S_THURSDAY_SHORT_BIG'),
t('S_FRIDAY_SHORT_BIG'),
t('S_SATURDAY_SHORT_BIG'),
t('S_SUNDAY_SHORT_BIG')
].forEach(function(str) {
var td = document.createElement('th');
td.appendChild(document.createTextNode(str));
tr.appendChild(td);
});
/*
* Days calendar
*/
this.clndr_days = document.createElement('tbody');
this.clndr_days.setAttribute('class', 'calendar-date');
table.appendChild(this.clndr_days);
addListener(this.clndr_monthdown, 'click', this.monthdown.bindAsEventListener(this));
addListener(this.clndr_monthup, 'click', this.monthup.bindAsEventListener(this));
addListener(this.clndr_yeardown, 'click', this.yeardown.bindAsEventListener(this));
addListener(this.clndr_yearup, 'click', this.yearup.bindAsEventListener(this));
// Active section setter.
var cal_obj = this;
jQuery(this.sections).each(function(index, item) {
jQuery(item, cal_obj.clndr_calendar)
.attr({'tabindex': '0'})
.on('click', function() {
cal_obj.active_section = index;
cal_obj.focusSection();
});
});
},
/**
* Remove calendar DOM element. Detach event listeners.
*/
calendardelete: function() {
removeListener(this.clndr_monthdown, 'click', this.monthdown);
removeListener(this.clndr_monthup, 'click', this.monthup);
removeListener(this.clndr_yeardown, 'click', this.yeardown);
removeListener(this.clndr_yearup, 'click', this.yearup);
jQuery(this.clndr_calendar).remove();
}
};