const DANGEROUS_FIELDS = "__version,__event,isClearing,eventQ,listeners"
export type ChangableObjectEventType<E = never> =
| "datachanged"
| "selected"
| "unselected"
| "init"
| E
export class ChangableObjectEvent {
readonly type: string
readonly payload: any
readonly target: IChangableObject
constructor(type: string, payload: any, target: any) {
this.type = type
this.payload = payload
this.target = target
}
}
export type ChangeableObjectEventHandler = (e: ChangableObjectEvent) => void
export abstract class ChangableObject<E extends string = never>
implements IChangableObject
{
id: string = ZERO_ID
/**
* @readonly
*/
$listeners: Record<string, ChangeableObjectEventHandler[]> = {}
/**
* 内部维护的字段,可以访问到该数据所在集合
* @readonly
*/
public $set: IChangableObject = null
/**
* 用于事件限流
* unit: 1/s
*/
readonly notificationRate: number
private lastEventOccurredAt: number
private notifyingIsDisabled = false
/**
* 禁用通知
*/
disableNotifying() {
this.notifyingIsDisabled = true
}
/**
* 开启通知
*/
enableNotifying() {
this.notifyingIsDisabled = false
}
copy(d: Record<string, unknown>, ...excludes: string[]) {
// no private fields
const keys = Object.keys(this)
for (const key of keys) {
if (
DANGEROUS_FIELDS.indexOf(key) > -1 ||
excludes.includes(key) ||
d[key] === undefined
) {
continue
}
this[key] = d[key]
}
}
_notify(type: string, payload?: any) {
if (notifyingIsDisabled || this.notifyingIsDisabled) return
const now = performance.now()
if (
this.notificationRate > 0 &&
now - this.lastEventOccurredAt < this.notificationRate
)
return
pushEvent(new ChangableObjectEvent(type, payload, this))
this.lastEventOccurredAt = now
if (this.$set) {
this.$set.notify(`item:${type}`, this)
}
}
_on(type: string, listener: ChangeableObjectEventHandler) {
const listeners = this.$listeners[type] || (this.$listeners[type] = [])
if (listeners.indexOf(listener) > -1) return this
listeners.push(listener)
return this
}
listens(types: string, listener: ChangeableObjectEventHandler) {
const typeArr = L.Util.splitWords(types)
if (typeArr.length === 0) return
for (const type of typeArr) {
this._on(type, listener)
}
}
unlistens(types: string, listener?: ChangeableObjectEventHandler) {
const typeArr = L.Util.splitWords(types)
if (typeArr.length === 0) return
for (const type of typeArr) {
this._off(type, listener)
}
}
_off(type: string, listener?: ChangeableObjectEventHandler) {
if (!this.$listeners[type]) return
if (listener === undefined) {
this.$listeners[type] = undefined
}
const items = this.$listeners[type]
const index = items.findIndex((x) => x === listener)
if (index === -1) return
items.splice(index, 1)
return this
}
_once(type: string, listener: ChangeableObjectEventHandler) {
const wrap = (e: ChangableObjectEvent) => {
this._off(type, wrap)
listener(e)
}
this._on(type, wrap)
return this
}
abstract update(d: any): void
abstract init(d: any): this
}
export interface ChangableObject<E extends string = never> {
on(
type: ChangableObjectEventType<E>,
listener: ChangeableObjectEventHandler,
): ChangableObject<E>
off(
type: ChangableObjectEventType<E>,
listener?: ChangeableObjectEventHandler,
): ChangableObject<E>
once(
type: ChangableObjectEventType<E>,
listener: ChangeableObjectEventHandler,
): ChangableObject<E>
notify(type: ChangableObjectEventType<E>, payload?: any): void
}
export interface IChangableObject {
id: string
$set: IChangableObject
$listeners: Record<string, ChangeableObjectEventHandler[]>
update(d: any): void
init(d: any): unknown
notify(e: string, payload?: any): void
on(type: string, listener: (...args: any[]) => void): void
off(type: string, listener?: (...args: any[]) => void): void
once(type: string, listener: (...args: any[]) => void): void
onAdded?(context?: IChangableObject): void
onRemoved?(context?: IChangableObject): void
}