/* * Tencent is pleased to support the open source community by making TMagicEditor available. * * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * 通用的事件处理 */ import { EventEmitter } from 'events'; import { has } from 'lodash-es'; import type { DataSource } from '@tmagic/data-source'; import { ActionType, type CodeItemConfig, type CompItemConfig, type DataSourceItemConfig, type EventActionItem, type EventConfig, } from '@tmagic/schema'; import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX, getIdFromEl } from '@tmagic/utils'; import type { default as TMagicApp } from './App'; import type { default as TMagicNode } from './Node'; import { COMMON_EVENT_PREFIX, isCommonMethod, triggerCommonMethod } from './utils'; const getCommonEventName = (commonEventName: string) => { if (commonEventName.startsWith(COMMON_EVENT_PREFIX)) return commonEventName; return `${COMMON_EVENT_PREFIX}${commonEventName}`; }; // 点击在组件内的某个元素上,需要向上寻找到当前组件 const getDirectComponent = (element: HTMLElement | null, app: TMagicApp): TMagicNode | undefined => { if (!element) { return; } const id = getIdFromEl()(element); if (!id) { return getDirectComponent(element.parentElement, app); } const node = app.getNode( id, element.dataset.tmagicIteratorContainerId?.split(','), element.dataset.tmagicIteratorIndex?.split(',').map((i) => globalThis.parseInt(i, 10)), ); return node; }; export default class EventHelper extends EventEmitter { public app: TMagicApp; private nodeEventList = new Map<(fromCpt: TMagicNode, ...args: any[]) => void, symbol>(); private dataSourceEventList = new Map void>>(); constructor({ app }: { app: TMagicApp }) { super(); this.app = app; globalThis.document.body.addEventListener('click', this.commonClickEventHandler); } public destroy() { this.removeNodeEvents(); this.removeAllListeners(); globalThis.document.body.removeEventListener('click', this.commonClickEventHandler); } public bindNodeEvents(node: TMagicNode) { node.events?.forEach((event, index) => { let eventNameKey = `${event.name}_${node.data.id}`; // 页面片容器可以配置页面片内组件的事件,形式为“${nodeId}.${eventName}” const eventNames = event.name.split('.'); if (eventNames.length > 1) { eventNameKey = `${eventNames[1]}_${eventNames[0]}`; } let eventName = Symbol(eventNameKey); if (node.eventKeys.has(eventNameKey)) { eventName = node.eventKeys.get(eventNameKey)!; } else { node.eventKeys.set(eventNameKey, eventName); } const eventHandler = (fromCpt: TMagicNode, ...args: any[]) => { this.eventHandler(index, node, args); }; this.nodeEventList.set(eventHandler, eventName); this.on(eventName, eventHandler); }); } public removeNodeEvents() { Array.from(this.nodeEventList.keys()).forEach((handler) => { const name = this.nodeEventList.get(handler); name && this.off(name, handler); }); this.nodeEventList.clear(); } public bindDataSourceEvents(dataSourceList: DataSource[]) { this.removeDataSourceEvents(dataSourceList); dataSourceList.forEach((dataSource) => { const dataSourceEvent = this.dataSourceEventList.get(dataSource.id) ?? new Map void>(); (dataSource.schema.events || []).forEach((event) => { const [prefix, ...path] = event.name?.split('.') || []; if (!prefix) return; const handler = (...args: any[]) => { this.eventHandler(event, dataSource, args); }; dataSourceEvent.set(event.name, handler); if (prefix === DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX) { // 数据源数据变化 dataSource?.onDataChange(path.join('.'), handler); } else { // 数据源自定义事件 dataSource.on(prefix, handler); } }); this.dataSourceEventList.set(dataSource.id, dataSourceEvent); }); } public removeDataSourceEvents(dataSourceList: DataSource[]) { if (!this.dataSourceEventList.size) { return; } // 先清掉之前注册的事件,重新注册 dataSourceList.forEach((dataSource) => { const dataSourceEvent = this.dataSourceEventList.get(dataSource.id)!; if (!dataSourceEvent) return; Array.from(dataSourceEvent.keys()).forEach((eventName) => { const [prefix, ...path] = eventName.split('.'); if (prefix === DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX) { dataSource.offDataChange(path.join('.'), dataSourceEvent.get(eventName)!); } else { dataSource.off(prefix, dataSourceEvent.get(eventName)!); } }); }); this.dataSourceEventList.clear(); } /** * 事件联动处理函数 * @param eventsConfigIndex 事件配置索引,可以通过此索引从node.event中获取最新事件配置 * @param fromCpt 触发事件的组件 * @param args 事件参数 */ private async eventHandler(config: EventConfig | number, fromCpt: TMagicNode | DataSource | undefined, args: any[]) { const eventConfig = typeof config === 'number' ? (fromCpt as TMagicNode).events[config] : config; if (has(eventConfig, 'actions')) { // EventConfig类型 const { actions } = eventConfig as EventConfig; for (let i = 0; i < actions.length; i++) { if (typeof config === 'number') { // 事件响应中可能会有修改数据源数据的,会更新dsl,所以这里需要重新获取 const actionItem = ((fromCpt as TMagicNode).events[config] as EventConfig).actions[i]; this.actionHandler(actionItem, fromCpt as TMagicNode, args); } else { this.actionHandler(actions[i], fromCpt as DataSource, args); } } } else { // 兼容DeprecatedEventConfig类型 组件动作 await this.compActionHandler(eventConfig as unknown as CompItemConfig, fromCpt as TMagicNode, args); } } private async actionHandler(actionItem: EventActionItem, fromCpt: TMagicNode | DataSource, args: any[]) { if (actionItem.actionType === ActionType.COMP) { const compActionItem = actionItem as CompItemConfig; // 组件动作 await this.compActionHandler(compActionItem, fromCpt, args); } else if (actionItem.actionType === ActionType.CODE) { const codeActionItem = actionItem as CodeItemConfig; // 执行代码块 await this.app.runCode(codeActionItem.codeId, codeActionItem.params || {}, args); } else if (actionItem.actionType === ActionType.DATA_SOURCE) { const dataSourceActionItem = actionItem as DataSourceItemConfig; const [dsId, methodName] = dataSourceActionItem.dataSourceMethod; await this.app.runDataSourceMethod(dsId, methodName, dataSourceActionItem.params || {}, args); } } /** * 执行联动组件动作 * @param eventConfig 联动组件的配置 * @returns void */ private async compActionHandler(eventConfig: CompItemConfig, fromCpt: TMagicNode | DataSource, args: any[]) { if (!this.app.page) throw new Error('当前没有页面'); let { method: methodName, to } = eventConfig; if (Array.isArray(methodName)) { [to, methodName] = methodName; } const toNode = this.app.getNode(to); if (!toNode) throw `ID为${to}的组件不存在`; if (isCommonMethod(methodName)) { return triggerCommonMethod(methodName, toNode); } if (toNode.instance) { if (typeof toNode.instance[methodName] === 'function') { await toNode.instance[methodName](fromCpt, ...args); } } else { toNode.addEventToQueue({ method: methodName, fromCpt, args, }); } } private commonClickEventHandler = (e: MouseEvent) => { if (!e.target) { return; } const node = getDirectComponent(e.target as HTMLElement, this.app); const eventName = `${getCommonEventName('click')}_${node?.data.id}`; if (node?.eventKeys.has(eventName)) { this.emit(node.eventKeys.get(eventName)!, node); } }; }