calendar/calendar-preact.js

182 lines
4.6 KiB
JavaScript

/**
* Calendar Script
* Creates a calendar widget which can be used to select the date more easily than using just a text box
*
* (c) Vitaliy Filippov 2018+
* Repository: http://yourcmc.ru/git/vitalif-js/calendar
* Version: 2021-09-19
* License: Dual-license MPL 2.0+ or GNU LGPL 3.0+
*
* Example:
* <input type="text" name="date" id="date" />
* <script type="text/javascript">
* Calendar.set("date");
* </script>
*/
import ReactCalendar from './calendar-react.js';
import preact from 'preact';
/** @jsx preact.h */
export class Calendar extends ReactCalendar
{
componentDidMount()
{
this.componentDidUpdate();
}
componentDidUpdate()
{
// Position the div in the correct location...
var input = this.props.input;
var div = Calendar.box;
div.style.position = "absolute";
var xy = getOffset(input);
var height = input.clientHeight||input.offsetHeight;
var ww = document.body.clientWidth||document.documentElement.clientWidth;
var wh = document.body.clientHeight||document.documentElement.clientHeight;
if (xy.left-1+div.offsetWidth > ww)
div.style.left = (ww-div.offsetWidth-1)+"px";
else
div.style.left = (xy.left-1)+"px";
if (div.offsetHeight + xy.top+height-1 >= wh &&
xy.top-div.offsetHeight >= 0)
div.style.top = (xy.top-div.offsetHeight)+'px';
else
div.style.top = (xy.top+height-1)+"px";
}
static onBlur()
{
if (!Calendar.stopBlur || Calendar.stopBlur < Date.now()-200)
Calendar.hideCalendar();
}
/// Called when the user clicks on a date in the calendar.
static onChange(i, date)
{
i.value = date;
if ("Event" in window)
{
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
i.dispatchEvent(evt);
}
else
i.fireEvent("onchange");
Calendar.hideCalendar();
}
/// Display the calendar - if a date exists in the input box, that will be selected in the calendar.
static showCalendar(input, options)
{
// Show the calendar with the date in the input as the selected date
const props = {
...options,
input,
value: input.value,
hide: Calendar.hideCalendar,
onChange: date => Calendar.onChange(input, date),
};
Calendar.init();
Calendar.box.style.display = "block";
Calendar.stopBlur = Date.now();
preact.render(<Calendar {...props} />, Calendar.box.parentNode, Calendar.box);
}
/// Hides the currently shown calendar.
static hideCalendar()
{
if (!Calendar.box)
return;
Calendar.box.style.display = "none";
}
/// Setup a text input box to be a calendar box.
static set(input_or_id, options)
{
if (typeof input_or_id == 'string')
{
input_or_id = document.getElementById(input_or_id);
}
if (!input_or_id)
{
return; // If the input field is not there, exit.
}
input_or_id.addEventListener('blur', Calendar.onBlur);
input_or_id.addEventListener('focus', function(ev)
{
Calendar.showCalendar(input_or_id, options);
});
// FIXME: Add change listener to enable interactive date selection in calendar while typing
}
// Will be called once when the first input is set.
static init()
{
if (!Calendar.box || !Calendar.box.parentNode)
{
var div = document.createElement('div');
if (!Calendar.box)
Calendar.box = div;
div.className = "calendar-box";
div.addEventListener("mousedown", function(ev)
{
ev = ev || window.event;
if (ev.stopPropagation)
ev.stopPropagation();
else
ev.cancelBubble = true;
Calendar.stopBlur = Date.now();
return true;
});
document.getElementsByTagName("body")[0].insertBefore(div, document.getElementsByTagName("body")[0].firstChild);
if (!Calendar.addedListener)
{
document.addEventListener("mousedown", function() { Calendar.hideCalendar(); });
Calendar.addedListener = true;
}
}
}
}
window.Calendar = Calendar;
function getOffsetRect(elem)
{
var box = elem.getBoundingClientRect();
var body = document.body;
var docElem = document.documentElement;
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
var clientTop = docElem.clientTop || body.clientTop || 0;
var clientLeft = docElem.clientLeft || body.clientLeft || 0;
var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;
return { top: Math.round(top), left: Math.round(left) };
}
function getOffsetSum(elem)
{
var top = 0, left = 0;
while(elem)
{
top = top + parseInt(elem.offsetTop);
left = left + parseInt(elem.offsetLeft);
elem = elem.offsetParent;
}
return { top: top, left: left };
}
function getOffset(elem)
{
if (elem.getBoundingClientRect)
return getOffsetRect(elem);
else
return getOffsetSum(elem);
}