2021-08-13 23:26:18 +02:00
|
|
|
/**
|
|
|
|
* EGroupware eTemplate2 - Date widget (WebComponent)
|
|
|
|
*
|
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
|
|
* @package etemplate
|
|
|
|
* @subpackage api
|
|
|
|
* @link https://www.egroupware.org
|
|
|
|
* @author Nathan Gray
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2021-08-27 08:13:36 +02:00
|
|
|
import {css, html} from "@lion/core";
|
|
|
|
import {LionInputDatepicker} from "@lion/input-datepicker";
|
|
|
|
import {Unparseable} from "@lion/form-core";
|
2021-08-26 17:20:41 +02:00
|
|
|
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
|
2021-08-13 23:26:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* To parse a date into the right format
|
|
|
|
*
|
|
|
|
* @param {string} dateString
|
|
|
|
* @returns {Date | undefined}
|
|
|
|
*/
|
|
|
|
export function parseDate(dateString)
|
|
|
|
{
|
2021-08-19 23:09:00 +02:00
|
|
|
// First try the server format
|
2021-09-23 22:12:15 +02:00
|
|
|
if(dateString.substr(-1) === "Z")
|
2021-08-19 23:09:00 +02:00
|
|
|
{
|
2021-09-23 22:12:15 +02:00
|
|
|
try
|
2021-08-19 23:09:00 +02:00
|
|
|
{
|
2021-09-23 22:12:15 +02:00
|
|
|
let date = new Date(dateString);
|
|
|
|
if(date instanceof Date)
|
|
|
|
{
|
|
|
|
return date;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch(e)
|
|
|
|
{
|
|
|
|
// Nope, that didn't parse directly
|
2021-08-19 23:09:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-13 23:26:18 +02:00
|
|
|
let formatString = <string>(egw.preference("dateformat") || 'Y-m-d');
|
2021-09-23 22:12:15 +02:00
|
|
|
formatString = formatString.replaceAll(new RegExp('[-/\.]', 'ig'), '-');
|
2021-08-13 23:26:18 +02:00
|
|
|
let parsedString = "";
|
2021-08-19 23:09:00 +02:00
|
|
|
switch(formatString)
|
2021-08-13 23:26:18 +02:00
|
|
|
{
|
|
|
|
case 'd-m-Y':
|
|
|
|
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
|
|
|
|
3,
|
|
|
|
5,
|
|
|
|
)}/${dateString.slice(0, 2)}`;
|
|
|
|
break;
|
|
|
|
case 'm-d-Y':
|
|
|
|
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
|
|
|
|
0,
|
|
|
|
2,
|
|
|
|
)}/${dateString.slice(3, 5)}`;
|
|
|
|
break;
|
|
|
|
case 'Y-m-d':
|
|
|
|
parsedString = `${dateString.slice(0, 4)}/${dateString.slice(
|
|
|
|
5,
|
|
|
|
7,
|
|
|
|
)}/${dateString.slice(8, 10)}`;
|
|
|
|
break;
|
|
|
|
case 'd-M-Y':
|
|
|
|
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
|
|
|
|
3,
|
|
|
|
5,
|
|
|
|
)}/${dateString.slice(0, 2)}`;
|
|
|
|
default:
|
|
|
|
parsedString = '0000/00/00';
|
|
|
|
}
|
|
|
|
|
|
|
|
const [year, month, day] = parsedString.split('/').map(Number);
|
2021-09-23 22:12:15 +02:00
|
|
|
const parsedDate = new Date(year, month - 1, day);
|
2021-08-13 23:26:18 +02:00
|
|
|
|
|
|
|
// Check if parsedDate is not `Invalid Date` or that the date has changed (e.g. the not existing 31.02.2020)
|
|
|
|
if (
|
|
|
|
year > 0 &&
|
|
|
|
month > 0 &&
|
|
|
|
day > 0 &&
|
|
|
|
parsedDate.getDate() === day &&
|
|
|
|
parsedDate.getMonth() === month - 1
|
|
|
|
)
|
|
|
|
{
|
|
|
|
return parsedDate;
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Format dates according to user preference
|
|
|
|
*
|
|
|
|
* @param {Date} date
|
|
|
|
* @param {import('@lion/localize/types/LocalizeMixinTypes').FormatDateOptions} [options] Intl options are available
|
|
|
|
* set 'dateFormat': "Y-m-d" to specify a particular format
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
|
|
|
export function formatDate(date: Date, options): string
|
|
|
|
{
|
|
|
|
if (!date || !(date instanceof Date))
|
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
let _value = '';
|
|
|
|
// Add timezone offset back in, or formatDate will lose those hours
|
2021-10-19 00:03:05 +02:00
|
|
|
let formatDate = new Date(date.valueOf() - date.getTimezoneOffset() * 60 * 1000);
|
2021-08-13 23:26:18 +02:00
|
|
|
|
|
|
|
let dateformat = options.dateFormat || <string>egw.preference("dateformat") || 'Y-m-d';
|
|
|
|
|
|
|
|
var replace_map = {
|
2021-08-19 23:09:00 +02:00
|
|
|
d: (date.getUTCDate() < 10 ? "0" : "") + date.getUTCDate(),
|
|
|
|
m: (date.getUTCMonth() < 9 ? "0" : "") + (date.getUTCMonth() + 1),
|
2021-08-13 23:26:18 +02:00
|
|
|
Y: "" + date.getUTCFullYear()
|
|
|
|
}
|
|
|
|
var re = new RegExp(Object.keys(replace_map).join("|"), "gi");
|
|
|
|
_value = dateformat.replace(re, function (matched)
|
|
|
|
{
|
|
|
|
return replace_map[matched];
|
|
|
|
});
|
|
|
|
return _value;
|
|
|
|
}
|
|
|
|
|
2021-08-26 20:59:13 +02:00
|
|
|
export class Et2Date extends Et2InputWidget(LionInputDatepicker)
|
2021-08-13 23:26:18 +02:00
|
|
|
{
|
|
|
|
static get styles()
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
...super.styles,
|
|
|
|
css`
|
2021-09-24 19:13:14 +02:00
|
|
|
:host([focused]) ::slotted(button), :host(:hover) ::slotted(button) {
|
|
|
|
display: inline-block;
|
|
|
|
}
|
|
|
|
::slotted(.calendar_button) {
|
|
|
|
border: none;
|
|
|
|
background: transparent;
|
|
|
|
margin-left: -20px;
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
`,
|
2021-08-13 23:26:18 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
static get properties()
|
|
|
|
{
|
|
|
|
return {
|
2021-08-19 23:09:00 +02:00
|
|
|
...super.properties
|
2021-08-13 23:26:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor()
|
|
|
|
{
|
|
|
|
super();
|
|
|
|
this.parser = parseDate;
|
|
|
|
this.formatter = formatDate;
|
|
|
|
}
|
|
|
|
|
|
|
|
connectedCallback()
|
|
|
|
{
|
|
|
|
super.connectedCallback();
|
|
|
|
}
|
|
|
|
|
2021-08-19 23:09:00 +02:00
|
|
|
/**
|
|
|
|
* @param {Date} modelValue
|
|
|
|
*/
|
|
|
|
// eslint-disable-next-line class-methods-use-this
|
|
|
|
serializer(modelValue : Date)
|
|
|
|
{
|
|
|
|
// isValidDate() is hidden inside LionInputDate, and not exported
|
2021-08-27 19:21:40 +02:00
|
|
|
// @ts-ignore Can't call isNan(Date), but we're just checking
|
2021-08-19 23:09:00 +02:00
|
|
|
if(!(modelValue instanceof Date) || isNaN(modelValue))
|
|
|
|
{
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
// modelValue is localized, so we take the timezone offset in milliseconds and subtract it
|
|
|
|
// before converting it to ISO string.
|
|
|
|
const offset = modelValue.getTimezoneOffset() * 60000;
|
|
|
|
return new Date(modelValue.getTime() - offset).toJSON().replace(/\.\d{3}Z$/, 'Z');
|
|
|
|
}
|
2021-08-13 23:26:18 +02:00
|
|
|
|
2021-10-19 00:03:05 +02:00
|
|
|
set_value(value)
|
|
|
|
{
|
|
|
|
this.modelValue = this.parser(value);
|
|
|
|
}
|
|
|
|
|
2021-08-13 23:26:18 +02:00
|
|
|
getValue()
|
|
|
|
{
|
2021-11-01 17:21:08 +01:00
|
|
|
if(this.readOnly)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-08-19 23:09:00 +02:00
|
|
|
// The supplied value was not understandable, return null
|
2021-10-19 00:03:05 +02:00
|
|
|
if(this.modelValue instanceof Unparseable || !this.modelValue)
|
2021-08-19 23:09:00 +02:00
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
2021-09-23 22:12:15 +02:00
|
|
|
return this.modelValue.toJSON();
|
2021-08-13 23:26:18 +02:00
|
|
|
}
|
2021-09-23 22:12:15 +02:00
|
|
|
|
2021-09-24 19:13:14 +02:00
|
|
|
get _overlayReferenceNode()
|
|
|
|
{
|
|
|
|
return this.getInputNode();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @override Configures OverlayMixin
|
|
|
|
* @desc overrides default configuration options for this component
|
|
|
|
* @returns {Object}
|
|
|
|
*/
|
|
|
|
_defineOverlayConfig()
|
|
|
|
{
|
|
|
|
this.hasArrow = false;
|
|
|
|
if(window.innerWidth >= 600)
|
|
|
|
{
|
|
|
|
return {
|
|
|
|
hidesOnOutsideClick: true,
|
|
|
|
placementMode: 'local',
|
|
|
|
popperConfig: {
|
|
|
|
placement: 'bottom-end',
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return super.withBottomSheetConfig();
|
|
|
|
}
|
|
|
|
|
2021-10-19 00:03:05 +02:00
|
|
|
/**
|
|
|
|
* The LionCalendar shouldn't know anything about the modelValue;
|
|
|
|
* it can't handle Unparseable dates, but does handle 'undefined'
|
|
|
|
* @param {?} modelValue
|
|
|
|
* @returns {Date|undefined} a 'guarded' modelValue
|
|
|
|
*/
|
|
|
|
static __getSyncDownValue(modelValue)
|
|
|
|
{
|
|
|
|
if(!(modelValue instanceof Date))
|
|
|
|
{
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
const offset = modelValue.getTimezoneOffset() * 60000;
|
|
|
|
return new Date(modelValue.getTime() + offset);
|
|
|
|
}
|
|
|
|
|
2021-11-01 17:21:08 +01:00
|
|
|
/**
|
|
|
|
* Overriding from parent for read-only
|
|
|
|
*
|
|
|
|
* @return {TemplateResult}
|
|
|
|
* @protected
|
|
|
|
*/
|
|
|
|
// eslint-disable-next-line class-methods-use-this
|
|
|
|
_inputGroupInputTemplate()
|
|
|
|
{
|
|
|
|
if(this.readOnly)
|
|
|
|
{
|
|
|
|
return this.formattedValue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return super._inputGroupInputTemplate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-24 19:13:14 +02:00
|
|
|
/**
|
|
|
|
* Overriding parent to add class to button, and use an image instead of unicode emoji
|
|
|
|
*/
|
|
|
|
// eslint-disable-next-line class-methods-use-this
|
|
|
|
_invokerTemplate()
|
|
|
|
{
|
2021-11-01 17:21:08 +01:00
|
|
|
if(this.readOnly)
|
|
|
|
{
|
|
|
|
return '';
|
|
|
|
}
|
2021-10-19 00:03:05 +02:00
|
|
|
let img = this.egw() ? this.egw().image("calendar") || '' : '';
|
2021-09-24 19:13:14 +02:00
|
|
|
return html`
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class="calendar_button"
|
|
|
|
@click="${this.__openCalendarOverlay}"
|
|
|
|
id="${this.__invokerId}"
|
|
|
|
aria-label="${this.msgLit('lion-input-datepicker:openDatepickerLabel')}"
|
|
|
|
title="${this.msgLit('lion-input-datepicker:openDatepickerLabel')}"
|
|
|
|
>
|
2021-10-19 00:03:05 +02:00
|
|
|
<img src="${img}" style="width:16px"/>
|
2021-09-24 19:13:14 +02:00
|
|
|
</button>
|
|
|
|
`;
|
|
|
|
}
|
2021-08-13 23:26:18 +02:00
|
|
|
}
|
|
|
|
|
2021-08-27 19:21:40 +02:00
|
|
|
// @ts-ignore TypeScript is not recognizing that Et2Date is a LitElement
|
2021-08-13 23:26:18 +02:00
|
|
|
customElements.define("et2-date", Et2Date);
|