blob: acea4ed86fbe0fa2c05b8a8c55d0b99b7969c5b1 [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
let datepicker_spawner = null
let calendarpicker_spawner = null
const DATE_UNITS = {
w: 'week',
d: 'day',
M: 'month',
y: 'year'
}
function fixupPicker(obj) {
obj.addEventListener("focus", function(event) {
$('html').on('hide.bs.dropdown', function(e) {
return false;
});
});
obj.addEventListener("blur", function(event) {
$('html').unbind('hide.bs.dropdown')
});
}
// makeSelect: Creates a <select> object with options
function makeSelect(options, id, selval) {
let sel = document.createElement('select')
sel.addEventListener("focus", function(event) {
$('html').on('hide.bs.dropdown', function(e) {
return false;
});
});
sel.addEventListener("blur", function(event) {
$('html').unbind('hide.bs.dropdown')
});
sel.setAttribute("name", id)
sel.setAttribute("id", id)
// For each options element, create it in the DOM
for (let key in options) {
let opt = document.createElement('option')
// Hash or array?
if (typeof key == "string") {
opt.setAttribute("value", key)
// Option is selected by default?
if (key == selval) {
opt.setAttribute("selected", "selected")
}
} else {
// Option is selected by default?
if (options[key] == selval) {
opt.setAttribute("selected", "selected")
}
}
opt.text = options[key]
sel.appendChild(opt)
}
return sel
}
// splitDiv: Makes a split div with 2 elements,
// and puts div2 into the right column,
// and 'name' as text in the left one.
function splitDiv(id, name, div2) {
let div = document.createElement('div')
let subdiv = document.createElement('div')
let radio = document.createElement('input')
radio.setAttribute("type", "radio")
radio.setAttribute("name", "datepicker_radio")
radio.setAttribute("value", name)
radio.setAttribute("id", "datepicker_radio_" + id)
radio.setAttribute("onclick", "calcTimespan('" + id + "')")
let label = document.createElement('label')
label.innerHTML = "&nbsp; " + name + ": "
label.setAttribute("for", "datepicker_radio_" + id)
subdiv.appendChild(radio)
subdiv.appendChild(label)
subdiv.style.float = "left"
div2.style.float = "left"
subdiv.style.width = "120px"
subdiv.style.height = "48px"
div2.style.height = "48px"
div2.style.width = "250px"
div.appendChild(subdiv)
div.appendChild(div2)
return div
}
// calcTimespan: Calculates the value and representational text
// for the datepicker choice and puts it in the datepicker's
// spawning input/select element.
function calcTimespan(what) {
let wat = ""
let tval = ""
// Less than N units ago?
if (what == 'lt') {
// Get unit and how many units
let N = document.getElementById('datepicker_lti').value
let unit = document.getElementById('datepicker_lts').value
let unitt = DATE_UNITS[unit]
if (parseInt(N) != 1) {
unitt += "s"
}
// If this makes sense, construct a humanly readable and a computer version
// of the timespan
if (N.length > 0) {
wat = "Less than " + N + " " + unitt + " ago"
tval = "lte=" + N + unit
}
}
// More than N units ago?
if (what == 'mt') {
// As above, get unit and no of units.
let N = document.getElementById('datepicker_mti').value
let unit = document.getElementById('datepicker_mts').value
let unitt = DATE_UNITS[unit]
if (parseInt(N) != 1) {
unitt += "s"
}
// construct timespan val + description
if (N.length > 0) {
wat = "More than " + N + " " + unitt + " ago"
tval = "gte=" + N + unit
}
}
// Date range?
if (what == 'cd') {
// Get From and To values
let f = document.getElementById('datepicker_cfrom').value
let t = document.getElementById('datepicker_cto').value
// construct timespan val + description if both from and to are valid
if (f.length > 0 && t.length > 0) {
wat = "From " + f + " to " + t
tval = "dfr=" + f + "|dto=" + t
}
}
// If we calc'ed a value and spawner exists, update its key/val
if (datepicker_spawner && what && wat.length > 0) {
document.getElementById('datepicker_radio_' + what).checked = true
if (datepicker_spawner.options) {
datepicker_spawner.options[0].value = tval
datepicker_spawner.options[0].text = wat
} else if (datepicker_spawner.value) {
datepicker_spawner.value = wat
datepicker_spawner.setAttribute("data", tval)
}
}
}
// datePicker: spawns a date picker with letious
// timespan options right next to the parent caller.
function datePicker(parent, seedPeriod) {
datepicker_spawner = parent
let div = document.getElementById('datepicker_popup')
// If the datepicker object doesn't exist, spawn it
if (!div) {
div = document.createElement('div')
div.setAttribute("id", "datepicker_popup")
div.setAttribute("class", "datepicker")
}
// Reset the contents of the datepicker object
div.innerHTML = ""
div.style.display = "block"
// Position the datepicker next to whatever called it
let bb = parent.getBoundingClientRect()
div.style.top = (bb.bottom + 8) + "px"
div.style.left = (bb.left + 32) + "px"
// -- Less than N $units ago
let ltdiv = document.createElement('div')
let lti = document.createElement('input')
lti.setAttribute("id", "datepicker_lti")
lti.style.width = "48px"
lti.setAttribute("onkeyup", "calcTimespan('lt')")
lti.setAttribute("onblur", "calcTimespan('lt')")
ltdiv.appendChild(lti)
let lts = makeSelect({
'd': "Day(s)",
'w': 'Week(s)',
'M': "Month(s)",
'y': "Year(s)"
}, 'datepicker_lts', 'm')
lts.setAttribute("onchange", "calcTimespan('lt')")
ltdiv.appendChild(lts)
ltdiv.appendChild(document.createTextNode(' ago'))
div.appendChild(splitDiv('lt', 'Less than', ltdiv))
// -- More than N $units ago
let mtdiv = document.createElement('div')
let mti = document.createElement('input')
mti.style.width = "48px"
mti.setAttribute("id", "datepicker_mti")
mti.setAttribute("onkeyup", "calcTimespan('mt')")
mti.setAttribute("onblur", "calcTimespan('mt')")
mtdiv.appendChild(mti)
let mts = makeSelect({
'd': "Day(s)",
'w': 'Week(s)',
'M': "Month(s)",
'y': "Year(s)"
}, 'datepicker_mts', 'm')
mtdiv.appendChild(mts)
mts.setAttribute("onchange", "calcTimespan('mt')")
mtdiv.appendChild(document.createTextNode(' ago'))
div.appendChild(splitDiv('mt', 'More than', mtdiv))
// -- Calendar timespan
// This is just two text fields, the calendarPicker sub-plugin populates them
let cdiv = document.createElement('div')
let cfrom = document.createElement('input')
cfrom.style.width = "90px"
cfrom.setAttribute("id", "datepicker_cfrom")
cfrom.setAttribute("onfocus", "showCalendarPicker(this)")
cfrom.setAttribute("onchange", "calcTimespan('cd')")
cdiv.appendChild(document.createTextNode('From: '))
cdiv.appendChild(cfrom)
let cto = document.createElement('input')
cto.style.width = "90px"
cto.setAttribute("id", "datepicker_cto")
cto.setAttribute("onfocus", "showCalendarPicker(this)")
cto.setAttribute("onchange", "calcTimespan('cd')")
cdiv.appendChild(document.createTextNode('To: '))
cdiv.appendChild(cto)
div.appendChild(splitDiv('cd', 'Date range', cdiv))
// -- Magic button that sends the timespan back to the caller
let okay = document.createElement('input')
okay.setAttribute("type", "button")
okay.setAttribute("value", "Okay")
okay.setAttribute("onclick", "setDatepickerDate()")
div.appendChild(okay)
parent.parentNode.appendChild(div)
document.body.setAttribute("onclick", "")
window.setTimeout(function() {
document.body.setAttribute("onclick", "blurDatePicker(event)")
}, 200)
lti.focus()
// This is for recalcing the set options if spawned from a
// select/input box with an existing value derived from an
// earlier call to datePicker
let ptype = ""
let pvalue = parent.hasAttribute("data") ? parent.getAttribute("data") : parent.value
if (pvalue.search(/=|-/) != -1) {
// Less than N units ago?
if (pvalue.match(/lte/)) {
let m = pvalue.match(/lte=(\d+)([dMyw])/)
ptype = 'lt'
if (m) {
document.getElementById('datepicker_lti').value = m[1]
let sel = document.getElementById('datepicker_lts')
for (let i in sel.options) {
if (parseInt(i) >= 0) {
if (sel.options[i].value == m[2]) {
sel.options[i].selected = "selected"
} else {
sel.options[i].selected = null
}
}
}
}
}
// More than N units ago?
if (pvalue.match(/gte/)) {
ptype = 'mt'
let m = pvalue.match(/gte=(\d+)([dMyw])/)
if (m) {
document.getElementById('datepicker_mti').value = m[1]
let sel = document.getElementById('datepicker_mts')
// Go through the unit values, select the one we use
for (let i in sel.options) {
if (parseInt(i) >= 0) {
if (sel.options[i].value == m[2]) {
sel.options[i].selected = "selected"
} else {
sel.options[i].selected = null
}
}
}
}
}
// Date range?
if (pvalue.match(/dfr/)) {
ptype = 'cd'
// Make sure we have both a dfr and a dto here, catch them
let mf = pvalue.match(/dfr=(\d+-\d+-\d+)/)
let mt = pvalue.match(/dto=(\d+-\d+-\d+)/)
if (mf && mt) {
// easy peasy, just set two text fields!
document.getElementById('datepicker_cfrom').value = mf[1]
document.getElementById('datepicker_cto').value = mt[1]
}
}
// Month??
if (pvalue.match(/(\d{4})-(\d+)/)) {
ptype = 'cd'
// Make sure we have both a dfr and a dto here, catch them
let m = pvalue.match(/(\d{4})-(\d+)/)
if (m.length == 3) {
// easy peasy, just set two text fields!
let dfrom = new Date(parseInt(m[1]), parseInt(m[2]) - 1, 1, 0, 0, 0)
let dto = new Date(parseInt(m[1]), parseInt(m[2]), 0, 23, 59, 59)
document.getElementById('datepicker_cfrom').value = m[0] + "-" + dfrom.getDate()
document.getElementById('datepicker_cto').value = m[0] + "-" + dto.getDate()
}
}
calcTimespan(ptype)
}
}
function datePickerValue(seedPeriod) {
// This is for recalcing the set options if spawned from a
// select/input box with an existing value derived from an
// earlier call to datePicker
let rv = seedPeriod
if (seedPeriod && seedPeriod.search && seedPeriod.search(/=|-/) != -1) {
// Less than N units ago?
if (seedPeriod.match(/lte/)) {
let m = seedPeriod.match(/lte=(\d+)([dMyw])/)
let unitt = DATE_UNITS[m[2]]
if (parseInt(m[1]) != 1) {
unitt += "s"
}
rv = "Less than " + m[1] + " " + unitt + " ago"
}
// More than N units ago?
if (seedPeriod.match(/gte/)) {
let m = seedPeriod.match(/gte=(\d+)([dMyw])/)
let unitt = DATE_UNITS[m[2]]
if (parseInt(m[1]) != 1) {
unitt += "s"
}
rv = "More than " + m[1] + " " + unitt + " ago"
}
// Date range?
if (seedPeriod.match(/dfr/)) {
let mf = seedPeriod.match(/dfr=(\d+-\d+-\d+)/)
let mt = seedPeriod.match(/dto=(\d+-\d+-\d+)/)
if (mf && mt) {
rv = "From " + mf[1] + " to " + mt[1]
}
}
// Month??
if (seedPeriod.match(/^(\d+)-(\d+)$/)) {
let mr = seedPeriod.match(/(\d+)-(\d+)/)
if (mr) {
let dfrom = new Date(parseInt(mr[1]), parseInt(mr[2]) - 1, 1, 0, 0, 0)
rv = MONTHS[dfrom.getMonth()] + ', ' + mr[1]
}
}
}
return rv
}
function datePickerDouble(seedPeriod) {
// This basically takes a date-arg and doubles it backwards
// so >=3M becomes =>6M etc. Also returns the cutoff for
// the original date and the span in days of the original
let dbl = seedPeriod
let tspan = 1
let dfrom = new Date()
let dto = new Date()
// datepicker range?
if (seedPeriod && seedPeriod.search && seedPeriod.search(/=/) != -1) {
// Less than N units ago?
if (seedPeriod.match(/lte/)) {
let m = seedPeriod.match(/lte=(\d+)([dMyw])/)
dbl = "lte=" + (parseInt(m[1]) * 2) + m[2]
// N months ago
if (m[2] == "M") {
dfrom.setMonth(dfrom.getMonth() - parseInt(m[1]), dfrom.getDate())
}
// N days ago
if (m[2] == "d") {
dfrom.setDate(dfrom.getDate() - parseInt(m[1]))
}
// N years ago
if (m[2] == "y") {
dfrom.setYear(dfrom.getFullYear() - parseInt(m[1]))
}
// N weeks ago
if (m[2] == "w") {
dfrom.setDate(dfrom.getDate() - (parseInt(m[1]) * 7))
}
// Calc total duration in days for this time span
tspan = parseInt((dto.getTime() - dfrom.getTime() + 5000) / (1000 * 86400))
}
// More than N units ago?
if (seedPeriod.match(/gte/)) {
let m = seedPeriod.match(/gte=(\d+)([dMyw])/)
dbl = "gte=" + (parseInt(m[1]) * 2) + m[2]
// Can't really figure out a timespan for this, so...null!
// This also sort of invalidates use on the trend page, but meh..
tspan = null
dfrom = null
// Months
if (m[2] == "M") {
dto.setMonth(dto.getMonth() - parseInt(m[1]), dto.getDate())
}
// Days
if (m[2] == "d") {
dto.setDate(dto.getDate() - parseInt(m[1]))
}
// Years
if (m[2] == "y") {
dto.setYear(dto.getFullYear() - parseInt(m[1]))
}
// Weeks
if (m[2] == "w") {
dto.setDate(dto.getDate() - (parseInt(m[1]) * 7))
}
}
// Date range?
if (seedPeriod.match(/dfr/)) {
// Find from and to
let mf = seedPeriod.match(/dfr=(\d+)-(\d+)-(\d+)/)
let mt = seedPeriod.match(/dto=(\d+)-(\d+)-(\d+)/)
if (mf && mt) {
// Starts at 00:00:00 on from date
dfrom = new Date(parseInt(mf[1]), parseInt(mf[2]) - 1, parseInt(mf[3]), 0, 0, 0)
// Ends at 23:59:59 on to date
dto = new Date(parseInt(mt[1]), parseInt(mt[2]) - 1, parseInt(mt[3]), 23, 59, 59)
// Get duration in days, add 5 seconds to we can floor the value and get an integer
tspan = parseInt((dto.getTime() - dfrom.getTime() + 5000) / (1000 * 86400))
// double the distance
let dpast = new Date(dfrom)
dpast.setDate(dpast.getDate() - tspan)
dbl = seedPeriod.replace(/dfr=[^|]+/, "dfr=" + (dpast.getFullYear()) + '-' + (dpast.getMonth() + 1) + '-' + dpast.getDate())
} else {
tspan = 0
}
}
}
// just N days?
else if (parseInt(seedPeriod).toString() == seedPeriod.toString()) {
tspan = parseInt(seedPeriod)
dfrom.setDate(dfrom.getDate() - tspan)
dbl = "lte=" + (tspan * 2) + "d"
}
// Specific month?
else if (seedPeriod.match(/^(\d+)-(\d+)$/)) {
// just a made up thing...(month range)
let mr = seedPeriod.match(/(\d+)-(\d+)/)
if (mr) {
// Same as before, start at 00:00:00
dfrom = new Date(parseInt(mr[1]), parseInt(mr[2]) - 1, 1, 0, 0, 0)
// end at 23:59:59
dto = new Date(parseInt(mr[1]), parseInt(mr[2]), 0, 23, 59, 59)
// B-A, add 5 seconds so we can floor the no. of days into an integer neatly
tspan = parseInt((dto.getTime() - dfrom.getTime() + 5000) / (1000 * 86400))
// Double timespan
let dpast = new Date(dfrom)
dpast.setDate(dpast.getDate() - tspan)
dbl = "dfr=" + (dpast.getFullYear()) + '-' + (dpast.getMonth() + 1) + '-' + dpast.getDate() + "|dto=" + (dto.getFullYear()) + '-' + (dto.getMonth() + 1) + '-' + dto.getDate()
} else {
tspan = 0
}
}
return [dbl, dfrom, dto, tspan]
}
// set date in caller and hide datepicker again.
function setDatepickerDate() {
calcTimespan()
blurDatePicker()
}
// findParent: traverse DOM and see if we can find a parent to 'el'
// called 'name'. This is used for figuring out whether 'el' has
// lost focus or not.
function findParent(el, name) {
if (el.getAttribute && el.getAttribute("id") == name) {
return true
}
if (el.parentNode && el.parentNode.getAttribute) {
if (el.parentNode.getAttribute("id") != name) {
return findParent(el.parentNode, name)
} else {
return true
}
} else {
return false;
}
}
// function for hiding the date picker
function blurDatePicker(evt) {
let es = evt ? (evt.target || evt.srcElement) : null;
if ((!es || !es.parentNode || (!findParent(es, "datepicker_popup") && !findParent(es, "calendarpicker_popup"))) && !(es ? es : "null").toString().match(/javascript:void/)) {
document.getElementById('datepicker_popup').style.display = "none"
$('html').trigger('hide.bs.dropdown')
}
}
// draws the actual calendar inside a calendarPicker object
function drawCalendarPicker(obj, date) {
obj.focus()
// Default to NOW for calendar.
let now = new Date()
// if called with an existing date (YYYY-MM-DD),
// convert it to a JS date object and use that for
// rendering the calendar
if (date) {
let ar = date.split(/-/)
now = new Date(ar[0], parseInt(ar[1]) - 1, ar[2])
}
let mat = now
// Go to first day of the month
mat.setDate(1)
obj.innerHTML = "<h3>" + MONTHS[mat.getMonth()] + ", " + mat.getFullYear() + ":</h3>"
let tm = mat.getMonth()
// -- Nav buttons --
// back-a-year button
let a = document.createElement('a')
fixupPicker(a)
a.setAttribute("onclick", "drawCalendarPicker(this.parentNode, '" + (mat.getFullYear() - 1) + '-' + (mat.getMonth() + 1) + '-' + mat.getDate() + "');")
a.setAttribute("href", "javascript:void(0);")
a.setAttribute("class", "glyphicon glyphicon-fast-backward");
obj.appendChild(a)
// back-a-month button
a = document.createElement('a')
fixupPicker(a)
a.setAttribute("onclick", "drawCalendarPicker(this.parentNode, '" + mat.getFullYear() + '-' + (mat.getMonth()) + '-' + mat.getDate() + "');")
a.setAttribute("href", "javascript:void(0);")
a.setAttribute("class", "glyphicon glyphicon-step-backward");
obj.appendChild(a)
// forward-a-month button
a = document.createElement('a')
fixupPicker(a)
a.setAttribute("onclick", "drawCalendarPicker(this.parentNode, '" + mat.getFullYear() + '-' + (mat.getMonth() + 2) + '-' + mat.getDate() + "');")
a.setAttribute("href", "javascript:void(0);")
a.setAttribute("class", "glyphicon glyphicon-step-forward");
obj.appendChild(a)
// forward-a-year button
a = document.createElement('a')
fixupPicker(a)
a.setAttribute("onclick", "drawCalendarPicker(this.parentNode, '" + (mat.getFullYear() + 1) + '-' + (mat.getMonth() + 1) + '-' + mat.getDate() + "');")
a.setAttribute("href", "javascript:void(0);")
a.setAttribute("class", "glyphicon glyphicon-fast-forward");
obj.appendChild(a)
obj.appendChild(document.createElement('br'))
// Table containing the dates of the selected month
let table = document.createElement('table')
table.setAttribute("border", "1")
table.style.margin = "0 auto"
// Add header day names
let tr = document.createElement('tr');
for (let m = 0; m < 7; m++) {
let td = document.createElement('th')
td.innerHTML = DAYS_SHORTENED[m]
tr.appendChild(td)
}
table.appendChild(tr)
// Until we hit the first day in a month, add blank days
tr = document.createElement('tr');
let weekday = mat.getDay()
if (weekday == 0) {
weekday = 7
}
weekday--;
for (let i = 0; i < weekday; i++) {
let td = document.createElement('td')
tr.appendChild(td)
}
// While still in this month, add day then increment date by 1 day.
while (mat.getMonth() == tm) {
weekday = mat.getDay()
if (weekday == 0) {
weekday = 7
}
weekday--;
if (weekday == 0) {
table.appendChild(tr)
tr = document.createElement('tr');
}
let td = document.createElement('td')
// onclick for setting the calendarPicker's parent to this val.
td.setAttribute("onclick", "setCalendarDate('" + mat.getFullYear() + '-' + (mat.getMonth() + 1) + '-' + mat.getDate() + "');")
td.innerHTML = mat.getDate()
mat.setDate(mat.getDate() + 1)
tr.appendChild(td)
}
table.appendChild(tr)
obj.appendChild(table)
}
// callback for datePicker; sets the cd value to what date was picked
function setCalendarDate(what) {
$('html').on('hide.bs.dropdown', function(e) {
return false;
});
setTimeout(function() {
$('html').unbind('hide.bs.dropdown');
}, 250);
calendarpicker_spawner.value = what
let div = document.getElementById('calendarpicker_popup')
div.parentNode.focus()
div.style.display = "none"
calcTimespan('cd')
}
// caller for when someone clicks on a calendarPicker enabled field
function showCalendarPicker(parent, seedDate) {
calendarpicker_spawner = parent
// If supplied with a YYYY-MM-DD date, use this to seed the calendar
if (!seedDate) {
let m = parent.value.match(/(\d+-\d+(-\d+)?)/)
if (m) {
seedDate = m[1]
}
}
// Show or create the calendar object
let div = document.getElementById('calendarpicker_popup')
if (!div) {
div = document.createElement('div')
div.setAttribute("id", "calendarpicker_popup")
div.setAttribute("class", "calendarpicker")
document.getElementById('datepicker_popup').appendChild(div)
div.innerHTML = "Calendar goes here..."
}
div.style.display = "block"
let bb = parent.getBoundingClientRect()
// Align with the calling object, slightly below
div.style.top = (bb.bottom + 8) + "px"
div.style.left = (bb.right - 32) + "px"
drawCalendarPicker(div, seedDate)
}