diff --git a/lib/core/src/helpers/global-event.ts b/lib/core/src/helpers/global-event.ts index f9c3a2bbdd..4950acf719 100644 --- a/lib/core/src/helpers/global-event.ts +++ b/lib/core/src/helpers/global-event.ts @@ -1,26 +1,27 @@ -import {$, Cash} from '@zui/core'; +import {$, Cash} from '../cash'; +import {evalValue} from './raw-data'; +import {deepGet} from '@zui/helpers/src/object/deep-get'; +import {getZData} from './z'; export type GlobalEventOptions = { on: string; - selector: string; + selector?: string; prevent?: boolean; stop?: boolean; - call?: string; + call?: string | ((...args: unknown[]) => void); self?: boolean; - params?: unknown[]; + params?: unknown[] | string; once?: boolean; - if?: string; - do?: string; + if?: string | ((...args: unknown[]) => boolean); + do?: string | ((...args: unknown[]) => void); [option: string]: unknown; }; -function handleGlobalEvent(this: Cash, event: Event) { - const $this = $(this); - const options = $this.dataset()!; +function processGlobalEvent($element: Cash, event: Event, options: GlobalEventOptions) { if (!(options.on as string || 'click').split(' ').includes(event.type)) { return; } - const $target = options.selector ? $(event.target as HTMLElement).closest(options.selector as string) : $this; + const $target = options.selector ? $(event.target as HTMLElement).closest(options.selector as string) : $element; if (!$target.length) { return; } @@ -40,7 +41,7 @@ function handleGlobalEvent(this: Cash, event: Event) { if (options.onceCalled) { return; } - $this.dataset('once-called', true); + $element.dataset('once-called', true); } if (parseBool(options.prevent)) { event.preventDefault(); @@ -52,46 +53,99 @@ function handleGlobalEvent(this: Cash, event: Event) { return; } - const runParams: [string, unknown][] = [['$element', $this], ['event', event], ['options', options], ['$target', $target]]; - if (options.if && !$.runJS(options.if as string, ...runParams)) { + const runParams: [string, unknown][] = [['$element', $element], ['event', event], ['options', options], ['$target', $target]]; + const runCode = (code: string | ((...args: unknown[]) => boolean)) => { + if (typeof code === 'function') { + return code(...runParams); + } + return $.runJS(code as string, ...runParams); + }; + if (options.if !== undefined && !runCode(options.if)) { return; } - const call = options.call as string; + const call = options.call; if (call) { - let callback = window[call as keyof Window]; - const isFuncName = /^[$A-Z_][0-9A-Z_$.]*$/i.test(call as string); - if (!callback) { - callback = $.runJS(call as string, ...runParams); - } - if (!isFuncName || !$.isFunction(callback)) { - return; + let callback: unknown; + if (typeof call === 'string') { + const isFuncName = /^[$A-Z_][0-9A-Z_$.]*$/i.test(call); + callback = isFuncName ? deepGet(window, call) : runCode(call); + } else { + callback = call; } - const params: unknown[] = []; - const paramsOption = options.params; - options.params = params; - if (typeof paramsOption === 'string' && paramsOption.length) { - if (paramsOption[0] === '[') { - params.push(...(parseVal(paramsOption) as unknown[])); + if (typeof callback === 'function') { + const params: unknown[] = []; + const paramsOption = options.params; + options.params = params; + if (typeof paramsOption === 'string' && paramsOption.length) { + if (paramsOption[0] === '[') { + params.push(...(parseVal(paramsOption) as unknown[])); + } else { + params.push(...paramsOption.split(', ').map(x => { + x = x.trim(); + if (x === '$element') return $element; + if (x === 'event') return event; + if (x === 'options') return options; + if (x.startsWith('$element.') || x.startsWith('event.') || x.startsWith('options.')) return runCode(x); + return parseVal(x); + })); + } + } else if (Array.isArray(paramsOption)) { + params.push(...paramsOption); } else { - params.push(...paramsOption.split(', ').map(x => { - x = x.trim(); - if (x === '$element') return $this; - if (x === 'event') return event; - if (x === 'options') return options; - if (x.startsWith('$element.') || x.startsWith('event.') || x.startsWith('options.')) return $.runJS(x, ...runParams); - return parseVal(x); - })); + params.push(paramsOption); } - } else { - params.push(paramsOption); + callback(...params); } - callback(...params); } if (options.do) { - $.runJS(options.do as string, ...runParams); + runCode(options.do as string); } } -$(document).on('click.zui.global change.zui.global inited.zui.global', '[data-on]', handleGlobalEvent); +/** + * Handle global event. + * @param event The event object + * @example + * ```html + * Click or changed + * Click or changed + * Click + * Click or changed + * Click or changed + * + * [data-on] is deprecated + * ``` + */ +function handleGlobalEvent(this: Cash, event: Event) { + const $element = $(this); + const type = event.type; + const zuiOn = $element.attr('zui-on'); + if (zuiOn) { + const [events, code] = zuiOn.split('~').map(x => x.trim()); + if (events && code) { + processGlobalEvent($element, event, $.extend({ + on: events, + }, code.startsWith('{') ? evalValue(code) : {do: code})); + } + } + const zuiOnEvent = $element.attr(`zui-on-${type}`); + if (zuiOnEvent) { + processGlobalEvent($element, event, $.extend({ + on: type, + }, zuiOnEvent.startsWith('{') ? evalValue(zuiOnEvent) : {do: zuiOnEvent})); + } + + const dataOn = $element.attr('data-on'); + if (dataOn) { + processGlobalEvent($element, event, getZData($element, {prefix: 'data-', evalValue: ['call', 'if', 'do']}) as GlobalEventOptions); + console.warn(`[ZUI] Use [zui-on-${type}] instead of [data-on="${type}"] on element: `, $element[0]); + } +} + +export function registerGlobalListener(events: string[]) { + $(document).off('.zui.global').on(events.map(event => `${event}.zui.global`).join(' '), `[zui-on],${events.map(x => `[zui-on-${x}]`)},[data-on]`, handleGlobalEvent); +} + +registerGlobalListener(['click', 'change', 'inited']);