@rdkmaster/jigsaw-labs
Version:
Jigsaw, the next generation component set for RDK
434 lines (388 loc) • 15.4 kB
text/typescript
import {
NgModule, Component, Renderer2, Input, ElementRef, NgZone
} from "@angular/core";
import {CommonModule} from "@angular/common";
import {AbstractDialogComponentBase, DialogCallback} from "../dialog/dialog";
import {
ButtonInfo,
PopupService,
PopupEffect,
PopupInfo,
PopupPositionType,
PopupPositionValue
} from "../../service/popup.service";
import {JigsawTrustedHtmlModule} from "../../directive/trusted-html/trusted-html"
import {CommonUtils} from "../../core/utils/common-utils";
import {JigsawButtonModule} from "../button/button";
/**
* 提示框所处的位置,目前支持左上、左下、右上、右下4个方向。
*/
export enum NotificationPosition {
leftTop, leftBottom, rightTop, rightBottom
}
/**
* 一个提示消息的额外信息,用于详细配置一个弹出的消息的各种属性,所有属性都是可选的。
*/
export class NotificationMessage {
/**
* 提示框的标题,默认不显示标题
*/
caption?: string;
/**
* 提示框的图标,目前仅支持font-awesome就Jigsaw自研的iconfont图标。默认无图标。
*/
icon?: string;
/**
* 提示框所处的位置,目前支持左上、左下、右上、右下4个方向,默认右上角
*/
position?: NotificationPosition;
/**
* 提示信息超时自动关闭的毫秒数,为0则表示不关闭,默认8000ms
*/
timeout?: number;
/**
* 给提示框快速设置交互按钮,默认无按钮
*
* $demo = notification/full
*/
buttons?: ButtonInfo | ButtonInfo[];
/**
* `buttons`被单击了后的回调函数
*/
callback?: DialogCallback;
/**
* `callback`执行的上下文
*/
callbackContext?: any;
/**
* 提示框的高度,单位是px,默认由内容自动撑开
*/
height?: string | number;
/**
* 提示框的默认宽度,单位是px,默认350px
*/
width?: string | number;
/**
* 当消息的内容是html片段,并且包含了交互动作时,以此属性值作为这些函数的上下文对象。
*
* 例如使用下面的代码弹出一个提示
*
* ```
* JigsawNotification.show('执行xxxx成功啦,单击<a onclick="showDetail()">这里</a>查看详情。');
* ```
*
* 注意到提示内容带了一个超链,并且配置了`showDetail()`作为回调函数,在这个情况下,
* `innerHtmlContext`这个对象上必须已经定义了`showDetail()`函数,否则在用户单击了链接之后,控制台将报错。
*
* 此外,`innerHtmlContext`还作为回调函数中`this`的上下文,再如`showDetail()`函数有如下代码:
*
* ```
* showDetail() {
* ...
* alert(this.detail)
* }
* ```
*
* 在这个情况下,`innerHtmlContext`这个对象上必须已经定义了`detail`属性,否则一样报错。
*
* $demo = notification/full
*/
innerHtmlContext?: any;
}
/**
* all notification instances, put the list here to make it private
*
* @internal
*/
const notificationInstances = {
leftTop: [], leftBottom: [], rightTop: [], rightBottom: []
};
export class JigsawNotification extends AbstractDialogComponentBase {
constructor(renderer: Renderer2, elementRef: ElementRef) {
super();
this.renderer = renderer;
this.elementRef = elementRef;
}
protected getPopupElement(): HTMLElement {
return this.elementRef.nativeElement;
}
/**
* 初始化数据,应用一般无需使用。
*
* @param value
*/
public set initData(value: any) {
if (!value) return;
this.message = value.message || 'the "message" property in the initData goes here.';
this.caption = value.caption;
this.icon = value.icon;
this.buttons = value.buttons;
this.position = value.position;
this.innerHtmlContext = value.innerHtmlContext;
this._callback = value.callback;
this._callbackContext = value._callbackContext;
this._timeout = value.timeout;
}
/**
* 需要提示给用户的消息,支持基础html标记,支持附加交互动作。必选。
*/
public message: string;
/**
* 提示框的标题,默认不显示标题
*/
public caption: string;
/**
* 提示框的图标,目前仅支持font-awesome就Jigsaw自研的iconfont图标。默认无图标。
*/
public icon: string;
/**
* 给提示框快速设置交互按钮
*
* $demo = notification/full
*/
public buttons: ButtonInfo[];
/**
* 当`message`里包含html交互动作时,`JigsawNotification`在执行给定的回调函数时,会将这个对象作为函数的上下文。
* 简单的说,这个属性值和回调函数里的`this`是同一个对象。
*
* $demo = notification/full
*/
public innerHtmlContext: any;
private _timeout;
private _timer;
private _callback: (button: ButtonInfo) => void;
private _callbackContext: any;
private _position: NotificationPosition;
/**
* 提示框所处的位置,目前支持左上、左下、右上、右下4个方向。
*
* @return {NotificationPosition | string}
*/
public get position(): NotificationPosition | string {
return this._position;
}
public set position(value: NotificationPosition | string) {
const v: string = NotificationPosition[value];
if (CommonUtils.isUndefined(v)) {
console.error('invalid NotificationPosition value: ' + value);
return;
}
this._position = NotificationPosition[v];
}
private _popupInfoValue: PopupInfo;
/**
* @internal
*/
get _popupInfo(): PopupInfo {
return this._popupInfoValue;
}
set _popupInfo(value: PopupInfo) {
this._popupInfoValue = value;
if (!this._popupInfoValue) {
return;
}
this._popupInfoValue.answer.subscribe(answer => this._$close(answer));
this._$onLeave();
}
/**
* @internal
*/
public _$close(answer?: ButtonInfo) {
if (this._popupInfoValue) {
this._popupInfoValue.answer.unsubscribe();
this._popupInfoValue.dispose();
}
if (this._timeout) {
this.clearCallLater(this._timeout);
// set `_timeout` to 0 to make sure `_$onLeave()` do not trigger `_$close` twice!
this._timeout = 0;
}
CommonUtils.safeInvokeCallback(this._callbackContext, this._callback, [answer]);
const removeListener = this.renderer.listen(this.elementRef.nativeElement, 'animationend',
() => {
removeListener();
JigsawNotification.reposition(this._position);
const instances = notificationInstances[NotificationPosition[this._position]];
const idx = instances.indexOf(this._popupInfoValue);
if (idx != -1) {
instances.splice(idx, 1);
} else {
console.error('can find popupInfo in the notification list, this should not happen!');
}
const leftTop = notificationInstances[NotificationPosition[NotificationPosition.leftTop]];
const leftBottom = notificationInstances[NotificationPosition[NotificationPosition.leftBottom]];
const rightTop = notificationInstances[NotificationPosition[NotificationPosition.rightTop]];
const rightBottom = notificationInstances[NotificationPosition[NotificationPosition.rightBottom]];
if (leftTop.length + leftBottom.length + rightTop.length + rightBottom.length == 0) {
JigsawNotification._removeResizeListener();
JigsawNotification._removeResizeListener = null;
}
this._popupInfoValue = null;
this._timeout = null;
this._callbackContext = null;
this._callback = null;
this.buttons = null;
this.renderer = null;
this.elementRef = null;
this.innerHtmlContext = null;
});
}
/**
* @internal
*/
_$onEnter() {
if (this._timer) {
this.clearCallLater(this._timer);
this._timer = null;
}
}
/**
* @internal
*/
_$onLeave() {
if (this._timeout > 0) {
this.clearCallLater(this._timer);
this._timer = this.callLater(() => this._$close(), this._timeout);
}
}
// ===================================================================================
// static code section
// ===================================================================================
/**
* @internal
*/
public static _zone: NgZone;
/**
* @internal
*/
public static _renderer: Renderer2;
/**
* @internal
*/
public static _removeResizeListener;
private static _positionReviser(position: NotificationPosition, element: HTMLElement): PopupPositionValue {
let left = 0;
if (position == NotificationPosition.rightTop || position == NotificationPosition.rightBottom) {
left += document.body.clientWidth - element.offsetWidth - 24;
} else {
left = 24;
}
const instances = notificationInstances[NotificationPosition[position]];
let initTop = 0, flag = 0;
if (position == NotificationPosition.leftBottom || position == NotificationPosition.rightBottom) {
initTop = document.body.clientHeight - element.offsetHeight - 24;
flag = -1;
} else {
initTop = 24;
flag = 1;
}
let top = instances.reduce(
(y, popupInfo) => popupInfo.element === element || popupInfo.element.offsetHeight == 0 ? y :
y + flag * (popupInfo.element.offsetHeight + 12), initTop);
return {left, top};
}
/**
* 用于重新定位视图上所有的提示框,一般需要在视图上的提示框有变动时调用。
* `JigsawNotification.show`方法、以及提示框被关掉时,Jigsaw会自动调用此方法,无需应用再次调用。
*
* @param {NotificationPosition} position 调整哪个方向上的提示框,可选,默认调试所有4个方向。
*/
public static reposition(position?: NotificationPosition) {
if (CommonUtils.isUndefined(position)) {
this.reposition(NotificationPosition.leftTop);
this.reposition(NotificationPosition.rightTop);
this.reposition(NotificationPosition.leftBottom);
this.reposition(NotificationPosition.rightBottom);
return;
}
const instances = notificationInstances[NotificationPosition[position]];
const instancesCopy = instances.concat();
instances.splice(0, instances.length);
instancesCopy.forEach(popupInfo => {
// console.log(popupInfo.element.offsetHeight, popupInfo.element.innerText);
const p = this._positionReviser(position, popupInfo.element);
const options = {posType: PopupPositionType.fixed, pos: {x: p.left, y: p.top}};
PopupService.instance.setPosition(options, popupInfo.element);
instances.push(popupInfo);
});
}
/**
* 方便快速地将一些信息以卡片的方式弹出在视图上,起到通知用户的作用。
* 这种提示方式相比[alert]($demo=alert/popup)柔和许多,对用户干扰较少,**建议优先使用**。
* 只有在一些非要用户立即处理不可的通知才通过[alert]($demo=alert/popup)的方式通知用户。
*
* $demo = notification/full
*
* @param {string} message 消息内容,支持基础html标记的富文本,也支持在html中添交互加动作。
* @return {PopupInfo} 返回的是被弹出的`JigsawNotification`组件实例的相关信息
*/
public static show(message: string): PopupInfo;
/**
* @param {string} message
* @param {string} caption 提示框的标题
* @return {PopupInfo}
*/
public static show(message: string, caption: string): PopupInfo;
/**
* @param {string} message
* @param {NotificationMessage} options 提示框的所有配置项,对提示做的行为做详细配置
* @return {PopupInfo}
*/
public static show(message: string, options?: NotificationMessage): PopupInfo;
/**
* @internal
*/
public static show(message: string, options?: string | NotificationMessage): PopupInfo {
if (CommonUtils.isUndefined(message)) {
return;
}
const opt = <NotificationMessage>(typeof options == 'string' ? {caption: options} : options ? options : {});
opt.width = opt.hasOwnProperty('width') ? opt.width : 350;
opt.timeout = +opt.timeout >= 0 ? +opt.timeout : 8000;
opt.position = typeof opt.position === 'string' ? NotificationPosition[<string>opt.position] : opt.position;
if (CommonUtils.isUndefined(NotificationPosition[opt.position])) {
opt.position = NotificationPosition.rightTop;
}
const popupOptions = {
size: {width: opt.width, height: opt.height}, disposeOnRouterChanged: false,
showEffect: PopupEffect.bubbleIn, hideEffect: PopupEffect.bubbleOut, modal: false,
posReviser: (pos, element) => this._positionReviser(opt.position, element),
pos: {x: 0, y: 0}, // `pos` not null to tell PopupService don't add resize event listener
posType: PopupPositionType.fixed
};
const initData = {
message: message, caption: opt.caption, icon: opt.icon, timeout: opt.timeout,
buttons: opt.buttons instanceof ButtonInfo ? [opt.buttons] : opt.buttons,
callbackContext: opt.callbackContext, callback: opt.callback, position: opt.position,
innerHtmlContext: opt.innerHtmlContext
};
const popupInfo = PopupService.instance.popup(JigsawNotification, popupOptions, initData);
popupInfo.instance._popupInfo = popupInfo;
notificationInstances[NotificationPosition[opt.position]].push(popupInfo);
setTimeout(() => this.reposition(opt.position));
if (!this._removeResizeListener) {
this._zone.runOutsideAngular(() => {
this._removeResizeListener = this._renderer.listen(window, 'resize', () => this.reposition());
});
}
return popupInfo;
};
}
export class JigsawNotificationModule {
}