zul/src/main/resources/web/js/zul/wgt/Notification.ts
/* Notification.ts
Purpose:
Description:
History:
Thu Mar 15 17:12:46 2012, Created by simon
Copyright (C) 2012 Potix Corporation. All Rights Reserved.
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
*/
var _iconMap = {
'warning': 'z-icon-exclamation-circle',
'info': 'z-icon-info-circle',
'error': 'z-icon-times-circle'
} as const,
_dirMap = {
'u': 'up',
'd': 'down',
'l': 'left',
'r': 'right'
} as const;
export type NotificationType = keyof typeof _iconMap;
export interface NotificationOptions {
ref?: zk.Widget;
pos?: string;
off?: zk.Offset;
dur?: number;
type?: zul.wgt.NotificationType;
closable?: boolean;
}
/**
* A notification widget.
* @since 6.0.1
*/
@zk.WrapClass('zul.wgt.Notification')
export class Notification extends zul.wgt.Popup {
/** @internal */
override _keepVisible = true;
/** @internal */
_closable?: boolean;
/** @internal */
_dur?: number;
/** @internal */
_nftPos?: string;
/** @internal */
_dir?: keyof typeof _dirMap | 'n';
/** @internal */
_ref?: zk.Widget;
/** @internal */
_msg: string;
/** @internal */
_type: NotificationOptions['type'];
constructor(msg: string, opts: NotificationOptions) {
// @ts-expect-error: doesn't match zk.Widget.prototype.constructor
super(msg, opts);
this._msg = msg;
this._type = opts.type;
this._ref = opts.ref;
this._dur = opts.dur;
this._closable = opts.closable;
}
override redraw(out: string[]): void {
var uuidHTML = this.uuid,
iconHTML = this.$s('icon');
out.push('<div', this.domAttrs_(), '>');
if (this._ref) //output arrow if reference exist
out.push('<div id="', uuidHTML, '-p" class="', this.$s('pointer'), '"></div>');
out.push('<i id="', uuidHTML, '-icon" class="', iconHTML, ' ', (/*safe*/ _iconMap[this._type!]), '"></i>');
out.push('<div id="', uuidHTML, '-cave" class="', this.$s('content'), '">',
DOMPurify.sanitize(this._msg), '</div>');
if (this._closable)
out.push('<div id="', uuidHTML, '-cls" class="', this.$s('close'),
'"><i id="', uuidHTML, '-clsIcon" class="', iconHTML, ' z-icon-times"></i></div>');
out.push('</div>'); // not encoded to support HTML
}
/** @internal */
override domClass_(no?: zk.DomClassOptions): string {
var type = this._type,
/*safe*/ s = super.domClass_(no);
if (type)
s += ' ' + this.$s(zUtl.encodeXML(type));
return s;
}
/** @internal */
override doClick_(evt: zk.Event, popupOnly?: boolean): void {
var p = evt.domTarget;
if (p == this.$n('cls') || p == this.$n('clsIcon')) //may click on font-icon
this.close();
else
super.doClick_(evt, popupOnly);
}
override onFloatUp(ctl: zk.ZWatchController, opts: zk.FireOptions): void {
if (opts && opts.triggerByFocus) //only mouse click should close notification
return;
if (!this.isVisible())
return;
var wgt: zk.Widget | undefined = ctl.origin as zk.Widget | undefined;
for (var floatFound = false; wgt; wgt = wgt.parent) {
if (wgt == this) {
if (!floatFound)
this.setTopmost();
return;
}
if (wgt == this.parent && wgt.ignoreDescendantFloatUp_(this))
return;
floatFound = floatFound ?? wgt.isFloating_();
}
if (!this._closable && this._dur! <= 0)
this.close({sendOnOpen: true});
}
override open(ref: zk.Widget, offset?: zk.Offset, position?: string, opts?: zul.wgt.PopupOptions): void {
super.open(ref, offset, position, opts);
this._fixarrow(ref); //ZK-1583: modify arrow position based on reference component
zk(this).redoCSS(-1, {'fixFontIcon': true});
}
override position(ref: zk.Widget, offset?: zk.Offset, position?: string, opts?: zk.PositionOptions): void {
if (ref && !ref.$n())
return;
super.position(ref, offset, position, opts);
this._fixarrow(ref); //ZK-1583: modify arrow position based on reference component
}
/** @internal */
override _posInfo(ref?: zul.wgt.Ref, offset?: zk.Offset, position?: string, opts?: zul.wgt.PopupOptions): zul.wgt.PositionInfo | undefined {
this._fixPadding(position);
return super._posInfo(ref, offset, position, opts);
}
/** @internal */
_fixPadding(position?: string): void {
var p = this.$n('p');
if (!p)
return;
var n = this.$n()!,
pw = 2 + (zk(p).borderWidth() / 2) || 0,
ph = 2 + (zk(p).borderHeight() / 2) || 0;
n.style.padding = '0';
// cache arrow direction for _fixarrow() later
switch (position) {
case 'before_start':
case 'before_center':
case 'before_end':
this._dir = 'd';
n.style.paddingBottom = ph + 'px';
break;
case 'after_start':
case 'after_center':
case 'after_end':
this._dir = 'u';
n.style.paddingTop = ph + 'px';
break;
case 'end_before':
case 'end_center':
case 'end_after':
this._dir = 'l';
n.style.paddingLeft = pw + 'px';
break;
case 'start_before':
case 'start_center':
case 'start_after':
this._dir = 'r';
n.style.paddingRight = pw + 'px';
break;
case 'top_left':
case 'top_center':
case 'top_right':
case 'middle_left':
case 'middle_center':
case 'middle_right':
case 'bottom_left':
case 'bottom_center':
case 'bottom_right':
case 'overlap':
case 'overlap_end':
case 'overlap_before':
case 'overlap_after':
this._dir = 'n';
break;
// at_pointer, after_pointer, etc.
default:
this._dir = 'n';
}
}
/** @internal */
_fixarrow(ref: zk.Widget): void {
var p = this.$n('p');
if (!p)
return;
var /*safe*/ pzcls = this.$s('pointer'),
n = this.$n()!,
refn = ref.$n()!,
dir = this._dir,
zkp = zk(p),
pw = zkp.borderWidth(),
ph = zkp.borderHeight(),
nOffset = zk(n).revisedOffset(),
refOffset = zk(refn).revisedOffset(),
arrXOffset = (refn.offsetWidth - pw) / 2,
arrYOffset = (refn.offsetHeight - ph) / 2;
if (dir != 'n') {
// positioning
if (dir == 'u' || dir == 'd') {
var b = dir == 'u',
l1 = (n.offsetWidth - pw) / 2 || 0,
l2 = refOffset[0] - nOffset[0] + arrXOffset || 0;
p.style.left = (refn.offsetWidth >= n.offsetWidth ? l1 : l2) + 'px'; //ZK-1583: assign arrow position to reference widget if it is smaller than notification
p.style[b ? 'top' : 'bottom'] = ((2 - ph / 2) || 0) + 'px';
p.style[b ? 'bottom' : 'top'] = '';
} else {
var b = dir == 'l',
t1 = (n.offsetHeight - ph) / 2 || 0,
t2 = refOffset[1] - nOffset[1] + arrYOffset || 0;
p.style.top = (refn.offsetHeight >= n.offsetHeight ? t1 : t2) + 'px'; //ZK-1583: assign arrow position to reference widget if it is smaller than notification
p.style[b ? 'left' : 'right'] = ((2 - pw / 2) || 0) + 'px';
p.style[b ? 'right' : 'left'] = '';
}
/*safe*/ p.className = pzcls + (_dirMap[dir!] ? ' ' + this.$s(_dirMap[dir!]) : '');
jq(p).show();
} else {
p.className = pzcls;
jq(p).hide();
}
}
/** @internal */
override openAnima_(ref?: zul.wgt.Ref, offset?: zk.Offset, position?: string, opts?: zul.wgt.PopupOptions): void {
var self = this;
jq(this.$n()).fadeIn(500, function () {
self.afterOpenAnima_(ref, offset, position, opts);
});
}
/** @internal */
override closeAnima_(opts?: zul.wgt.PopupOptions): void {
var self = this;
jq(this.$n()).fadeOut(500, function () {
self.afterCloseAnima_(opts);
});
}
/** @internal */
override afterCloseAnima_(opts?: zul.wgt.PopupOptions): void {
if (opts && opts.keepVisible) {
this.setVisible(false);
this.setFloating_(false);
if (opts.sendOnOpen)
this.fire('onOpen', {open: false});
} else {
this.detach();
}
}
/** @internal */
override getPositionArgs_(): zul.wgt.PositionArgs {
return [this._fakeParent, undefined, this._nftPos, undefined];
}
override reposition(): void {
super.reposition();
if (this._ref) this._fixarrow(this._ref);
}
/**
* Shows a notification.
* TODO
*/
static show(msg: string, pid: string, opts: NotificationOptions): void {
if (!opts)
opts = {};
var ref = opts.ref,
pos = opts.pos,
dur = opts.dur,
ntf = new zul.wgt.Notification(msg, opts),
off = opts.off,
n: HTMLElement | undefined,
isInView = true;
if (ref) {
n = ref.$n('real') || ref.$n();
isInView = zk(n).isRealScrollIntoView();
}
// TODO: allow user to specify arrow direction?
//ZK-2687, don't show notification if wgt is not in view
if (!isInView)
return;
if (!pos && !off)
pos = ref ? 'end_center' : 'middle_center';
jq(document.body).append(ntf);
ntf._nftPos = pos;
ntf.open(ref!, off, pos);
// auto dismiss
if (dur! > 0)
setTimeout(function () {
if (ntf.desktop)
ntf.close();
}, dur);
}
}