zul/src/main/resources/web/js/zul/db/Datebox.ts
/* Datebox.ts
{{IS_NOTE
Purpose:
Description:
History:
Fri Jan 23 10:32:34 TST 2009, Created by Flyworld
}}IS_NOTE
Copyright (C) 2008 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
}}IS_RIGHT
*/
var globallocalizedSymbols: Record<string, zk.LocalizedSymbols> = {},
_quotePattern = /'/g, // move pattern string here to avoid jsdoc failure
_innerDateFormat = 'yyyy/MM/dd ';
@zk.WrapClass('zul.db.Datebox')
export class Datebox extends zul.inp.FormatWidget<DateImpl> {
/** @internal */
_buttonVisible = true;
/** @internal */
_lenient = true;
/** @internal */
_strictDate = false;
/** @internal */
_selectLevel: 'day' | 'month' | 'year' | 'decade' | 'today' = 'day';
/** @internal */
_closePopupOnTimezoneChange = true;
/** @internal */
_pop: CalendarPop;
/** @internal */
_tm: CalendarTime;
// defSet will generate `_timeZone` but actually `_timezone` is used in code.
// Debugger in ZKDemo shows that both properties are created and agree.
// However, the server should expect `_timeZone`.
/** @internal */
_timeZone?: string;
/** @internal */
_constraint?: string;
/** @internal */
_displayedTimeZones?: string;
/** @internal */
_dtzones?: string[];
/** @internal */
_unformater?: string;
/** @internal */
_weekOfYear?: boolean;
/** @internal */
_showTodayLink?: boolean;
/** @internal */
_todayLinkLabel?: string;
/** @internal */
_defaultDateTime?: DateImpl;
/** @internal */
_refDate?: DateImpl;
/** @internal */
_timeZonesReadonly?: boolean;
/** @internal */
static _unformater?: zul.db.Unformater;
localizedFormat?: string;
/** @internal */
_position?: string;
constructor() {
super();
this.listen({ onChange: this }, -1000);
this._pop = new zul.db.CalendarPop();
this._tm = new zul.db.CalendarTime();
this.appendChild(this._pop);
this.appendChild(this._tm);
}
setPosition(position: string): this {
this._position = position;
return this;
}
getPosition(): string | undefined {
return this._position;
}
/**
* Sets whether the button (on the right of the textbox) is visible.
*/
setButtonVisible(buttonVisible: boolean, opts?: Record<string, boolean>): this {
const o = this._buttonVisible;
this._buttonVisible = buttonVisible;
if (o !== buttonVisible || opts?.force) {
zul.inp.RoundUtl.buttonVisible(this, buttonVisible);
}
return this;
}
/**
* @returns whether the button (on the right of the textbox) is visible.
* @defaultValue `true`.
*/
isButtonVisible(): boolean {
return this._buttonVisible;
}
/**
* Sets the date format.
* <p>The following pattern letters are defined:
* <table border="0" cellspacing="3" cellpadding="0">
* <tr bgcolor="#ccccff">
* <th align="left">Letter
* <th align="left">Date or Time Component
* <th align="left">Presentation
* <th align="left">Examples
* <tr><td>`G`
* <td>Era designator
* <td>Text</a>
* <td>`AD`
* <tr bgcolor="#eeeeff">
* <td>`y`
* <td>Year
* <td>Year</a>
* <td>`1996`; `96`
* <tr><td>`M`
* <td>Month in year
* <td>Month</a>
* <td>`July`; `Jul`; `07`
* <tr bgcolor="#eeeeff">
* <td>`w`
* <td>Week in year (starting at 1)
* <td>Number</a>
* <td>`27`
* <tr><td>`W`
* <td>Week in month (starting at 1)
* <td>Number</a>
* <td>`2`
* <tr bgcolor="#eeeeff">
* <td>`D`
* <td>Day in year (starting at 1)
* <td>Number</a>
* <td>`189`
* <tr><td>`d`
* <td>Day in month (starting at 1)
* <td>Number</a>
* <td>`10`
* <tr bgcolor="#eeeeff">
* <td>`F`
* <td>Day of week in month
* <td>Number</a>
* <td>`2`
* <tr><td>`E`
* <td>Day in week
* <td>Text</a>
* <td>`Tuesday`; `Tue`
* </table>
* @param format - the pattern.
*/
override setFormat(format: string, opts?: Record<string, boolean>): this {
const o = this._format;
this._format = format;
if (o !== format || opts?.force) {
if (this._pop) {
this._pop.setFormat(this._format);
if (this._value)
this._value = this._pop.getTime();
}
var inp = this.getInputNode();
if (inp)
inp.value = this.getText();
}
return this;
}
/**
* @returns the full date format of the specified format
*/
override getFormat(): string | undefined {
return this._format;
}
/**
* Sets the constraint.
* @defaultValue `null` (means no constraint all all).
*/
override setConstraint(constraint: string | undefined, opts?: Record<string, boolean>): this {
const o = this._constraint;
this._constraint = constraint;
if (o !== constraint || opts?.force) {
if (typeof constraint == 'string' && !constraint.startsWith('[')/*by server*/)
this._cst = new zul.inp.SimpleDateConstraint(constraint, this);
else
this._cst = constraint;
if (this._cst)
this._reVald = true; //revalidate required
if (this._pop) {
this._pop.setConstraint(this._constraint);
this._pop.rerender();
}
}
return this;
}
/**
* @returns the constraint, or null if no constraint at all.
*/
override getConstraint(): string | undefined {
return this._constraint;
}
/**
* Sets the time zone that this date box belongs to.
* @param timezone - the time zone's ID, such as "America/Los_Angeles".
*/
setTimeZone(timeZone: string, opts?: Record<string, boolean>): this {
const o = this._timeZone;
this._timeZone = timeZone;
if (o !== timeZone || opts?.force) {
this._timeZone = timeZone;
this._tm.setTimezone(timeZone);
this._setTimeZonesIndex();
this._value?.tz(timeZone);
this._pop && this._pop._fixConstraint();
var cst = this._cst;
if (cst && cst instanceof zul.inp.SimpleConstraint)
cst.reparseConstraint();
}
return this;
}
/**
* @returns the time zone that this date box belongs to, such as "America/Los_Angeles"
*/
override getTimeZone(): string | undefined {
return this._timeZone;
}
/**
* Sets whether the list of the time zones to display is readonly.
* If readonly, the user cannot change the time zone at the client.
*/
setTimeZonesReadonly(timeZonesReadonly: boolean, opts?: Record<string, boolean>): this {
const o = this._timeZonesReadonly;
this._timeZonesReadonly = timeZonesReadonly;
if (o !== timeZonesReadonly || opts?.force) {
var select = this.$n<HTMLSelectElement>('dtzones');
if (select) select.disabled = timeZonesReadonly;
}
return this;
}
/**
* @returns whether the list of the time zones to display is readonly.
* If readonly, the user cannot change the time zone at the client.
*/
isTimeZonesReadonly(): boolean {
return !!this._timeZonesReadonly;
}
/**
* Sets a catenation of a list of the time zones' ID, separated by comma,
* that will be displayed at the client and allow user to select.
* @param dtzones - a catenation of a list of the timezones' ID, such as
* `"America/Los_Angeles,GMT+8"`
*/
setDisplayedTimeZones(displayedTimeZones: string, opts?: Record<string, boolean>): this {
const o = this._displayedTimeZones;
this._displayedTimeZones = displayedTimeZones;
if (o !== displayedTimeZones || opts?.force) {
this._dtzones = displayedTimeZones ? displayedTimeZones.split(',') : undefined;
}
return this;
}
/**
* @returns a list of the time zones that will be displayed at the
* client and allow user to select.
* @defaultValue `null`
*/
getDisplayedTimeZones(): string | undefined {
return this._displayedTimeZones;
}
/**
* Sets the unformater function. This method is called from Server side.
* @param unf - the unformater function
*/
setUnformater(unformater: zul.db.Unformater, opts?: Record<string, boolean>): this {
const o = this._unformater;
this._unformater = unformater.toString();
if (o !== this._unformater || opts?.force) {
Datebox._unformater = unformater;
}
return this;
}
/**
* @returns the unformater.
*/
getUnformater(): string | undefined {
return this._unformater;
}
/**
* Sets whether or not date/time parsing is to be lenient.
*
* <p>
* With lenient parsing, the parser may use heuristics to interpret inputs
* that do not precisely match this object's format. With strict parsing,
* inputs must match this object's format.
*
* @defaultValue `true`.
*/
setLenient(lenient: boolean): this {
this._lenient = lenient;
return this;
}
/**
* @returns whether or not date/time parsing is to be lenient.
*
* <p>
* With lenient parsing, the parser may use heuristics to interpret inputs
* that do not precisely match this object's format. With strict parsing,
* inputs must match this object's format.
*/
isLenient(): boolean {
return this._lenient;
}
getLocalizedSymbols(): zk.LocalizedSymbols | undefined {
return this._localizedSymbols;
}
setLocalizedSymbols(localizedSymbols?: [string, zk.LocalizedSymbols], opts?: Record<string, boolean>): this {
const o = this._localizedSymbols;
if (localizedSymbols) {
if (!globallocalizedSymbols[localizedSymbols[0]])
globallocalizedSymbols[localizedSymbols[0]] = localizedSymbols[1];
this._localizedSymbols = globallocalizedSymbols[localizedSymbols[0]];
}
if (o !== localizedSymbols || opts?.force) {
// in this case, we cannot use setLocalizedSymbols() for Timebox
if (this._tm)
this._tm._localizedSymbols = this._localizedSymbols;
if (this._pop)
this._pop.setLocalizedSymbols(this._localizedSymbols);
}
return this;
}
/**
* Sets whether enable to show the week number in the current calendar or
* not. [ZK EE]
* @since 6.5.0
*/
setWeekOfYear(weekOfYear: boolean, opts?: Record<string, boolean>): this {
const o = this._weekOfYear;
this._weekOfYear = weekOfYear;
if (o !== weekOfYear || opts?.force) {
if (this._pop)
this._pop.setWeekOfYear(weekOfYear);
}
return this;
}
/**
* @returns whether enable to show the week number in the current calendar
* or not.
* @defaultValue `false`
* @since 6.5.0
*/
isWeekOfYear(): boolean {
return !!this._weekOfYear;
}
/**
* Sets whether enable to show the link that jump to today in day view
* @since 8.0.0
*/
setShowTodayLink(showTodayLink: boolean, opts?: Record<string, boolean>): this {
const o = this._showTodayLink;
this._showTodayLink = showTodayLink;
if (o !== showTodayLink || opts?.force) {
if (this._pop) {
this._pop.setShowTodayLink(showTodayLink);
}
}
return this;
}
/**
* @returns whether enable to show the link that jump to today in day view
* @defaultValue `false`
* @since 8.0.0
*/
isShowTodayLink(): boolean {
return !!this._showTodayLink;
}
/**
* Sets the label of the link that jump to today in day view
* @since 8.0.4
*/
setTodayLinkLabel(todayLinkLabel: string, opts?: Record<string, boolean>): this {
const o = this._todayLinkLabel;
this._todayLinkLabel = todayLinkLabel;
if (o !== todayLinkLabel || opts?.force) {
if (this._pop) {
this._pop.setTodayLinkLabel(todayLinkLabel);
}
}
return this;
}
/**
* @returns the label of the link that jump to today in day view
* @since 8.0.4
*/
getTodayLinkLabel(): string | undefined {
return this._todayLinkLabel;
}
/**
* Sets whether or not date/time should be strict.
* If true, any invalid input like "Jan 0" or "Nov 31" would be refused.
* If false, it won't be checked and let lenient parsing decide.
* @since 8.6.0
*/
setStrictDate(strictDate: boolean): this {
this._strictDate = strictDate;
return this;
}
/**
* @returns whether or not date/time should be strict.
* @defaultValue `false`.
* @since 8.6.0
*/
isStrictDate(): boolean {
return this._strictDate;
}
/**
* Sets the default datetime if the value is empty.
* @since 9.0.0
* @param defaultDateTime - Default datetime. null means current datetime.
*/
setDefaultDateTime(defaultDateTime: DateImpl): this {
this._defaultDateTime = defaultDateTime;
return this;
}
/**
* @returns the default datetime if the value is empty.
* @defaultValue `null` (means current datetime).
* @since 9.0.0
*/
getDefaultDateTime(): DateImpl | undefined {
return this._defaultDateTime;
}
/**
* Sets the level that a user can select.
* The valid options are "day", "month", and "year".
*
* @param selectLevel - the level that a user can select
* @since 9.5.1
*/
setSelectLevel(selectLevel: 'day' | 'month' | 'year' | 'decade' | 'today'): this {
this._selectLevel = selectLevel;
return this;
}
/**
* @returns the level that a user can select.
* @defaultValue `"day"`
* @since 9.5.1
*/
getSelectLevel(): string {
return this._selectLevel;
}
/**
* Sets whether to auto close the datebox popup after changing the timezone.
*
* @param closePopupOnTimezoneChange - shall close the datebox popup or not
* @since 9.6.0
*/
setClosePopupOnTimezoneChange(closePopupOnTimezoneChange: boolean): this {
this._closePopupOnTimezoneChange = closePopupOnTimezoneChange;
return this;
}
/**
* @returns whether to auto close the datebox popup after changing the timezone.
* @defaultValue `true`
* @since 9.6.0
*/
isClosePopupOnTimezoneChange(): boolean {
return this._closePopupOnTimezoneChange;
}
/**
* A method for component getter symmetry, it will call getValue
* @since 10.0.0
*/
getValueInZonedDateTime(): DateImpl | undefined {
return this.getValue();
}
/**
* A method for component setter symmetry, it will call setValue
* @since 10.0.0
*/
setValueInZonedDateTime(valueInZonedDateTime: DateImpl, fromServer?: boolean): this {
return this.setValue(valueInZonedDateTime, fromServer);
}
/**
* A method for component getter symmetry, it will call getValue
* @since 10.0.0
*/
getValueInLocalDateTime(): DateImpl | undefined {
return this.getValue();
}
/**
* A method for component setter symmetry, it will call setValue
* @since 10.0.0
*/
setValueInLocalDateTime(valueInLocalDateTime: DateImpl, fromServer?: boolean): this {
return this.setValue(valueInLocalDateTime, fromServer);
}
/**
* A method for component getter symmetry, it will call getValue
* @since 10.0.0
*/
getValueInLocalDate(): DateImpl | undefined {
return this.getValue();
}
/**
* A method for component setter symmetry, it will call setValue
* @since 10.0.0
*/
setValueInLocalDate(valueInLocalDate: DateImpl, fromServer?: boolean): this {
return this.setValue(valueInLocalDate, fromServer);
}
/**
* A method for component getter symmetry, it will call getValue
* @since 10.0.0
*/
getValueInLocalTime(): DateImpl | undefined {
return this.getValue();
}
/**
* A method for component setter symmetry, it will call setValue
* @since 10.0.0
*/
setValueInLocalTime(valueInLocalTime: DateImpl, fromServer?: boolean): this {
return this.setValue(valueInLocalTime, fromServer);
}
/**
* @returns the iconSclass name of this Datebox.
* @since 8.6.2
*/
getIconSclass(): string {
return 'z-icon-calendar';
}
override inRoundedMold(): boolean {
return true;
}
/** @internal */
_setTimeZonesIndex(): this {
var select = this.$n<HTMLSelectElement>('dtzones');
if (select && this._timeZone) {
var opts = jq(select).children('option');
for (var i = opts.length; i--;) {
if (opts[i].text == this._timeZone) select.selectedIndex = i;
}
}
return this;
}
override onSize(): void {
if (this.isOpen())
Datebox._reposition(this, true);
}
/**
* @returns the Time format of the specified format
*/
getTimeFormat(): string {
//Note: S (milliseconds not supported yet)
var fmt = this._format!,
aa = fmt.indexOf('a'),
hh = fmt.indexOf('h'),
KK = fmt.indexOf('K'),
HH = fmt.indexOf('H'), // ZK-2964: local nl time format has only one 'H'
kk = fmt.indexOf('k'),
mm = fmt.indexOf('m'),
ss = fmt.indexOf('s'),
hasAM = aa > -1,
//bug 3284144: The databox format parse a wrong result with hh:mm:ss
hasHour1 = (hasAM || hh) ? hh > -1 || KK > -1 : false,
mv = mm > -1 ? 'mm' : '',
sv = ss > -1 ? 'ss' : '';
if (hasHour1) {
var time = Datebox._prepareTimeFormat(hh < KK ? 'KK' : 'hh', mv, sv);
if (aa == -1)
return time;
else if ((hh != -1 && aa < hh) || (KK != -1 && aa < KK))
return 'a ' + time;
else
return time + ' a';
} else
return Datebox._prepareTimeFormat(HH < kk ? 'kk' : HH > -1 ? 'HH' : '', mv, sv);
}
/**
* @returns the Date format of the specified format
*/
getDateFormat(): string {
return this._format!.replace(/[(s.S{1,3})ahKHksm]*:?/g, '').trim();
}
/**
* Drops down or closes the calendar to select a date.
*/
setOpen(open: boolean, _focus_: boolean): this {
if (this.isRealVisible()) {
var pop = this._pop;
if (pop) {
if (open) pop.open(!_focus_);
else pop.close(!_focus_);
}
}
return this;
}
isOpen(): boolean {
return !!this._pop && this._pop.isOpen();
}
override setValue(value: DateImpl, fromServer?: boolean): this {
var tz = this.getTimeZone();
if (tz && value) value.tz(tz);
return super.setValue(value, fromServer);
}
/** @internal */
override coerceFromString_(val: string | undefined, pattern?: string): zul.inp.CoerceFromStringResult | DateImpl | undefined {
var unf = Datebox._unformater,
tz = this.getTimeZone();
if (unf && jq.isFunction(unf)) {
var cusv = unf(val);
if (cusv) {
this._shortcut = val;
return cusv;
}
}
if (val) {
var refDate = this._refDate || this._value,
format = this.getFormat()!,
d = new zk.fmt.Calendar().parseDate(val, pattern || format, !this._lenient, refDate, this._localizedSymbols, tz, this._strictDate);
this._refDate = undefined;
if (!d) return { error: zk.fmt.Text.format(msgzul.DATE_REQUIRED + (this.localizedFormat!.replace(_quotePattern, ''))) };
// B70-ZK-2382 escape shouldn't be used in format including hour
if (!format.match(/[HkKh]/))
d = new zk.fmt.Calendar().escapeDSTConflict(d, tz);
return d;
}
return undefined;
}
/** @internal */
override coerceToString_(val: DateImpl | undefined, pattern?: string): string {
return val ? new zk.fmt.Calendar().formatDate(val, pattern || this.getFormat(), this._localizedSymbols) : '';
}
/** @internal */
override doClick_(evt: zk.Event, popupOnly?: boolean): void {
if (this._disabled) return;
if (this._readonly && this._buttonVisible && this._pop && !this._pop.isOpen())
this._pop.open();
super.doClick_(evt, popupOnly);
}
/** @internal */
override doKeyDown_(evt: zk.Event): void {
this._doKeyDown(evt);
if (!evt.stopped)
super.doKeyDown_(evt);
}
/** @internal */
_doKeyDown(evt: zk.Event): void {
if (jq.nodeName(evt.domTarget, 'option', 'select'))
return;
var keyCode = evt.keyCode,
bOpen = this._pop.isOpen();
if (!evt.shiftKey && keyCode == 9 && evt.domTarget == this.$n('real') && bOpen) { // Tab focus to popup
this._pop.focus();
evt.stop();
return;
}
if (keyCode == 9 || (zk.webkit && keyCode == 0)) { //TAB or SHIFT-TAB (safari)
if (evt.target != this._tm) { // avoid closing the popup if moving focus to the timezone dropdown
if (bOpen) this._pop.close();
return;
}
}
if (evt.altKey && (keyCode == 38 || keyCode == 40)) {//UP/DN
if (bOpen) this._pop.close();
else this._pop.open();
//FF: if we eat UP/DN, Alt+UP degenerate to Alt (select menubar)
var opts = { propagation: true };
evt.stop(opts);
return;
}
//Request 1537962: better responsive
if (bOpen && (keyCode == 13 || keyCode == 27 || keyCode == 32)) { //ENTER or ESC or SPACE
if (keyCode == 13) this.enterPressed_(evt);
else if (keyCode == 27) this.escPressed_(evt);
return;
}
if (keyCode == 18 || keyCode == 27 || keyCode == 13
|| (keyCode >= 112 && keyCode <= 123)) //ALT, ESC, Enter, Fn
return; //ignore it (doc will handle it)
// ZK-2202: should not trigger too early when key code is ENTER
// select current time
if (this._pop.isOpen()) {
this._pop.doKeyDown_(evt);
}
}
/**
* Called when the user presses enter when this widget has the focus ({@link focus}).
* <p>call the close function
* @param evt - the widget event.
* The original DOM event and target can be retrieved by {@link zk.Event#domEvent} and {@link zk.Event#domTarget}
* @internal
*/
enterPressed_(evt: zk.Event): void {
this._pop.close();
this.updateChange_();
evt.stop();
}
/**
* Called when the user presses escape key when this widget has the focus ({@link focus}).
* <p>call the close function
* @param evt - the widget event.
* The original DOM event and target can be retrieved by {@link zk.Event#domEvent} and {@link zk.Event#domTarget}
* @internal
*/
escPressed_(evt: zk.Event): void {
this._pop.close();
evt.stop();
}
/** @internal */
override afterKeyDown_(evt: zk.Event, simulated?: boolean): boolean {
if (!simulated && this._inplace)
jq(this.$n_()).toggleClass(this.getInplaceCSS(), evt.keyCode == 13);
return super.afterKeyDown_(evt, simulated);
}
/** @internal */
override bind_(desktop?: zk.Desktop, skipper?: zk.Skipper, after?: CallableFunction[]): void {
super.bind_(desktop, skipper, after);
var btn = this.$n('btn');
if (btn) {
this.domListen_(btn, zk.android ? 'onTouchstart' : 'onClick', '_doBtnClick');
if (this._inplace) this.domListen_(btn, 'onMouseDown', '_doBtnMouseDown');
}
zWatch.listen({ onSize: this, onScroll: this, onShow: this });
this._pop.setFormat(this.getDateFormat());
}
/** @internal */
override unbind_(skipper?: zk.Skipper, after?: CallableFunction[], keepRod?: boolean): void {
var pop = this._pop;
if (pop)
pop.close(true);
var btn = this.$n('btn');
if (btn) {
this.domUnlisten_(btn, zk.android ? 'onTouchstart' : 'onClick', '_doBtnClick');
if (this._inplace) this.domUnlisten_(btn, 'onMouseDown', '_doBtnMouseDown');
}
zWatch.unlisten({ onSize: this, onScroll: this, onShow: this });
super.unbind_(skipper, after, keepRod);
}
/** @internal */
_doBtnClick(evt: zk.Event): void {
this._inplaceIgnore = false;
if (!this._buttonVisible) return;
if (!this._disabled)
this.setOpen(!jq(this.$n_('pp')).zk.isVisible(), zul.db.DateboxCtrl.isPreservedFocus(this));
// Bug ZK-2544, B70-ZK-2849
evt.stop((this._pop && this._pop._open ? { propagation: true } : undefined));
}
/** @internal */
_doBtnMouseDown(): void {
this._inplaceIgnore = true;
}
/** @internal */
_doTimeZoneChange(evt: zk.Event): void {
var select = this.$n_<HTMLSelectElement>('dtzones'),
timezone = select.value;
this.updateChange_();
this.fire('onTimeZoneChange', { timezone: timezone }, { toServer: true }, 150);
if (this._pop && this._closePopupOnTimezoneChange) this._pop.close();
}
// NOTE: data value will become string after event handler
onChange(evt: zk.Event & { data: zul.db.CalendarOnChangeData }): void {
var data = evt.data,
inpValue = this.getInputNode()!.value;
if (this._pop)
this._pop.setValue(data.value!);
// B50-ZK-631: Datebox format error message not shown with implements CustomConstraint
// pass input value to server for showCustomError
if (!data.value && inpValue
&& this.getFormat() && this._cst == '[c')
data.value = inpValue as unknown as DateImpl; // FIXME: type mismatch
}
onScroll(wgt?: zk.Widget): void {
if (this.isOpen()) {
var pp = this._pop;
// ZK-1552: fix the position of popup when scroll
if (wgt && pp) {
// ZK-2211: should close when the input is out of view
if (this.getInputNode() && zul.inp.InputWidget._isInView(this))
Datebox._reposition(this, true);
else
pp.close();
}
}
}
override onShow(): void {
if (this.__ebox) {
this.setFloating_(true);
this.__ebox.show();
}
}
/**
* @returns the label of the time zone
*/
getTimeZoneLabel(): string {
return '';
}
/** @internal */
redrawpp_(out: string[]): void {
out.push('<div id="', this.uuid, '-pp" class="', this.$s('popup'),
'" style="display:none" role="dialog" aria-labelledby="', /*safe*/ this._pop.uuid, '-title" tabindex="-1">');
for (var w = this.firstChild; w; w = w.nextSibling)
w.redraw(out);
this._redrawTimezone(out);
out.push('</div>');
}
/** @internal */
_redrawTimezone(out: string[]): void {
var timezones = this._dtzones;
if (timezones) {
out.push('<div class="', this.$s('timezone'), '">',
DOMPurify.sanitize(this.getTimeZoneLabel()),
'<select id="', this.uuid, '-dtzones">');
for (var i = 0, len = timezones.length; i < len; i++) {
const timezoneHTML = zUtl.encodeXML(timezones[i]);
out.push('<option value="', timezoneHTML, '">', timezoneHTML, '</option>');
}
out.push('</select></div>');
// B50-ZK-577: Rendering Issue using Datebox with displayedTimeZones
}
}
/** @internal */
override updateChange_(clear?: boolean): boolean {
if (!this.isOpen())
super.updateChange_(clear);
return false;
}
/** @internal */
static _reposition(db: Datebox, silent?: boolean): void {
if (!db.$n()) return;
var pp = db.$n('pp'),
n = db.$n_(),
inp = db.getInputNode();
if (pp) {
// dodgeRef is only ever tested for truthiness, and only here is it set to a non-boolean value.
zk(pp).position(n, db._position, { dodgeRef: n as unknown as boolean });
db._pop.syncShadow();
if (!silent)
zk(inp).focus();
}
}
/** @internal */
static _prepareTimeFormat(h: string, m: string, s: string): string {
var o: string[] = [];
if (h) o.push(h);
if (m) o.push(m);
if (s) o.push(s);
return o.join(':');
}
}
@zk.WrapClass('zul.db.CalendarPop')
export class CalendarPop extends zul.db.Calendar {
override parent!: Datebox;
/** @internal */
_shadow?: zk.eff.Shadow;
/** @internal */
_open?: boolean;
constructor() {
super();
this.listen({ onChange: this }, -1000);
}
setFormat(format: string): this {
this._fmt = format;
return this;
}
setLocalizedSymbols(localizedSymbols?: zk.LocalizedSymbols): this {
this._localizedSymbols = localizedSymbols;
return this;
}
// ZK-2047: should sync shadow when shiftView
override rerender(skipper?: number | zk.Skipper): this {
super.rerender(skipper);
if (this.desktop) this.syncShadow();
return this;
}
close(silent?: boolean): void {
var db = this.parent,
pp = db.$n('pp');
if (!pp || !zk(pp).isVisible()) return;
if (db._inplace) {
db._inplaceIgnore = false;
db._inplaceTimerId = setTimeout(function () {
if (db.desktop) jq(db.$n_()).addClass(db.getInplaceCSS());
}, db._inplaceTimeout);
}
// firefox and safari only
try {
if ((zk.ff || zk.safari) && zk.currentFocus) {
var n = zk.currentFocus.getInputNode ?
zk.currentFocus.getInputNode()! : zk.currentFocus.$n_();
if (jq.nodeName(n, 'input') && jq.isAncestor(pp, n)) // Bug ZK-2922, check ancestor first.
jq(n).blur(); // trigger a missing blur event.
}
} catch (e) {
zk.debugLog((e as Error).message || e as string);
}
if (this._shadow) {
// B65-ZK-1904: Make shadow behavior the same as ComboWidget
this._shadow.destroy();
this._shadow = undefined;
}
pp.style.display = 'none';
pp.className = db.$s('popup');
jq(pp).zk.undoVParent();
db.setFloating_(false);
if (silent)
db.updateChange_();
else if (zul.db.DateboxCtrl.isPreservedFocus(this))
zk(db.getInputNode()).focus();
//remove extra CSS class
var openClass = db.$s('open');
jq(db.$n_()).removeClass(openClass);
jq(pp).removeClass(openClass);
}
isOpen(): boolean {
return zk(this.parent.$n('pp')).isVisible();
}
open(silent?: boolean): void {
var db = this.parent,
dbn = db.$n(), pp = db.$n('pp');
if (!dbn || !pp)
return;
if (db._inplace) db._inplaceIgnore = true;
db.setFloating_(true, { node: pp });
zWatch.fire('onFloatUp', db); //notify all
var topZIndex = this.setTopmost();
this._setView(db._selectLevel);
var /*safe*/ zcls = db.getZclass();
pp.className = dbn.className + ' ' + pp.className;
jq(pp).removeClass(zcls);
pp.style.width = 'auto'; //reset
pp.style.display = 'block';
pp.style.zIndex = (topZIndex > 0 ? topZIndex : 1) as unknown as string;
this.setDomVisible_(pp, true, { visibility: true });
//FF: Bug 1486840
//IE: Bug 1766244 (after specifying position:relative to grid/tree/listbox)
jq(pp).zk.makeVParent();
if (pp.offsetWidth < dbn.offsetWidth) {
pp.style.width = `${dbn.offsetWidth}px`;
} else {
var wd = jq.innerWidth() - 20;
if (wd < dbn.offsetWidth)
wd = dbn.offsetWidth;
if (pp.offsetWidth > wd)
pp.style.width = wd as unknown as string;
}
delete db._shortcut;
var fmt = db.getTimeFormat(),
tz = db.getTimeZone(),
value = db._value || db._defaultDateTime || zUtl.today(fmt, tz);
if (value)
this.setValue(value);
if (fmt) {
var tm = db._tm;
tm.setVisible(true);
tm.setFormat(fmt);
tm.setValue(value || Dates.newInstance().tz(tz));
tm.onSize();
} else {
db._tm.setVisible(false);
}
//add extra CSS class for easy customize
var openClass = db.$s('open');
jq(db.$n_()).addClass(openClass);
jq(pp).addClass(openClass);
Datebox._reposition(db, silent); //ZK-3217: only need to calculate position once during open
}
syncShadow(): void {
if (!this._shadow)
this._shadow = new zk.eff.Shadow(this.parent.$n_('pp'), {
left: -4, right: 4, top: 2, bottom: 3
});
this._shadow.sync();
}
override onChange(evt: zk.Event & { data: zul.db.CalendarOnChangeData }): void {
var date: DateImpl | undefined = this.getTime(),
db = this.parent,
fmt = db.getTimeFormat(),
oldDate = db.getValue(),
tz = db.getTimeZone(),
cal = new zk.fmt.Calendar();
if (fmt) {
var tm = db._tm,
time = tm.getValue()!;
if (fmt.match(/[HK]/i))
date.setHours(time.getHours());
if (fmt.match(/[m]/))
date.setMinutes(time.getMinutes());
if (fmt.match(/[s]/))
date.setSeconds(time.getSeconds());
if (fmt.match(/[S]/))
date.setMilliseconds(time.getMilliseconds());
// B70-ZK-2382 escape shouldn't be used in format including hour
if (!fmt.match(/[HkKh]/))
date = cal.escapeDSTConflict(date, tz);
} else if (oldDate) {
date = Dates.newInstance([date.getFullYear(), date.getMonth(),
date.getDate(), oldDate.getHours(),
oldDate.getMinutes(), oldDate.getSeconds(), oldDate.getMilliseconds()], tz);
//Note: we cannot call setFullYear(), setMonth(), then setDate(),
//since Date object will adjust month if date larger than max one
// B70-ZK-2382 escape shouldn't be used in format including hour
if (!this.getFormat().match(/[HkKh]/))
date = cal.escapeDSTConflict(date, tz);
}
//Bug ZK-1712: no need to set datebox input value when shift view
if (!evt.data.shiftView)
db.getInputNode()!.value = db.coerceToString_(date);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare
if (this._view == db._selectLevel && evt.data.shallClose !== false) {
this.close();
// Bug 3122159 and 3301374
evt.data.value = date;
if (!CalendarPop._equalDate(date, oldDate)) {
db._refDate = date; // for coerceFromString_
db.updateChange_();
}
}
evt.stop();
}
onFloatUp(ctl: zk.ZWatchController): void {
if (this.isOpen()) {
var db = this.parent;
if (!zUtl.isAncestor(db, ctl.origin)) {
this.close(true);
}
}
}
/** @internal */
override bind_(desktop?: zk.Desktop, skipper?: zk.Skipper, after?: CallableFunction[]): void {
super.bind_(desktop, skipper, after);
this._bindTimezoneEvt();
zWatch.listen({ onFloatUp: this });
}
/** @internal */
override unbind_(skipper?: zk.Skipper, after?: CallableFunction[], keepRod?: boolean): void {
zWatch.unlisten({ onFloatUp: this });
this._unbindfTimezoneEvt();
if (this._shadow) {
this._shadow.destroy();
this._shadow = undefined;
}
super.unbind_(skipper, after, keepRod);
}
/** @internal */
_bindTimezoneEvt(): void {
var db = this.parent,
select = db.$n<HTMLSelectElement>('dtzones');
if (select) {
select.disabled = (db.isTimeZonesReadonly() ? 'disable' : '') as unknown as boolean;
db.domListen_(select, 'onChange', '_doTimeZoneChange');
db._setTimeZonesIndex();
}
}
/** @internal */
_unbindfTimezoneEvt(): void {
var db = this.parent,
select = db.$n('dtzones');
if (select)
db.domUnlisten_(select, 'onChange', '_doTimeZoneChange');
}
/** @internal */
override _setView(val: 'day' | 'month' | 'year' | 'decade' | 'today', force?: number): this {
if (this.parent.getTimeFormat())
this.parent._tm.setVisible(val == 'day');
super._setView(val, force);
return this;
}
// ZK-2308
/** @internal */
override doKeyDown_(evt: zk.Event): void {
super.doKeyDown_(evt);
if (evt.keyCode == 27) {
this.parent.escPressed_(evt);
}
}
/** @internal */
override animationSpeed_(): number | 'slow' | 'fast' {
return zk(this.parent).getAnimationSpeed();
}
/** @internal */
override _chooseDate(target: HTMLTableCellElement | undefined, val: number): void {
var db = this.parent,
selectLevel = db._selectLevel;
if (target && !jq(target).hasClass(this.$s('disabled'))) {
var cell = target,
dateobj = this.getTime();
switch (this._view) {
case 'day':
var oldTime = this.getTime();
this._setTime(undefined, cell._monofs != null && cell._monofs != 0 ?
dateobj.getMonth() + cell._monofs : undefined, val, true /*fire onChange */);
var newTime = this.getTime();
if (oldTime.getYear() == newTime.getYear()
&& oldTime.getMonth() == newTime.getMonth()) {
this._markCal({ sameMonth: true }); // optimize
} else {
this.rerender(-1);
this.focus();
}
break;
case 'month':
if (selectLevel == 'month') {
this._setTime(undefined, val, 1, true);
break;
}
this._setTime(undefined, val);
this._setView('day');
break;
case 'year':
if (selectLevel == 'year') {
this._setTime(val, 0, 1, true);
break;
}
this._setTime(val);
this._setView('month');
break;
case 'decade':
//Decade mode Set Year Also
this._setTime(val);
this._setView('year');
break;
}
}
}
/** @internal */
static _equalDate(d1: DateImpl | undefined, d2: DateImpl | undefined): boolean {
return !!((d1 == d2) || (d1 && d2 && d1.getTime() == d2.getTime()));
}
}
@zk.WrapClass('zul.db.CalendarTime')
export class CalendarTime extends zul.db.Timebox {
override parent!: Datebox;
constructor() {
super();
this.listen({ onChanging: this }, -1000);
}
onChanging(evt: zk.Event & { data: zul.db.CalendarOnChangeData }): void {
var db = this.parent,
oldDate = db.getValue() || db._pop.getValue(),
// ZK-2382 we must do the conversion with date and time in the same time
// otherwise the result may be affcted by DST adjustment
dateTime = db.coerceToString_(oldDate, _innerDateFormat) + evt.data.value, //onChanging's data is string
pattern = _innerDateFormat + db.getTimeFormat();
// add 'AM' (or APM in localizedSymbols) by default, if pattern specified AMPM
if (pattern.indexOf('a') > -1) {
var localizedSymbols = db.getLocalizedSymbols();
if (localizedSymbols && localizedSymbols.APM) {
dateTime += dateTime.indexOf(localizedSymbols.APM[0]) < 0 && dateTime.indexOf(localizedSymbols.APM[1]) < 0 ? localizedSymbols.APM[0] : '';
} else {
dateTime += dateTime.indexOf('AM') < 0 && dateTime.indexOf('PM') < 0 ? 'AM' : '';
}
}
var date = db.coerceFromString_(dateTime, pattern);
// do nothing if date converted from String is not a valid Date object e.g. dateTime = "2014/10/10 1 : : "
if (date instanceof DateImpl) {
db.getInputNode()!.value = (evt.data.value as unknown as string)
= db.coerceToString_(date);
db.fire(evt.name, evt.data); //onChanging
}
evt.stop();
}
}
/** @class zul.db.DateboxCtrl
* @import zk.Widget
* The extra control for the Datebox.
* It is designed to be overriden
* @since 6.5.0
*/
export var DateboxCtrl = {
/**
* @returns whether to preserve the focus state.
* @param wgt - a widget
*/
isPreservedFocus(wgt: zk.Widget): boolean {
return true;
}
};
zul.db.DateboxCtrl = DateboxCtrl;