ESL Event Listeners - Extended `EventTarget`s and standard optimizations

Extended EventTargets and standard optimizations

ESLDecoratedEventTarget.for

In cases where the original event of the target happens too frequently to be handled every time, it might be helpful to limit its processing. In purpose to do that ESL allows the creation of decorated EventTargets. The decorated target will process the original target events dispatching with the passed async call decoration function (such as debounce or throttle).

The ESLDecoratedEventTarget.for creates an instance that decorates the passed original EventTarget event emitting. The instances of ESLDecoratedEventTarget are lazy and do not subscribe to the original event until they have their own subscriptions of the same event type.

⚠ Note ESLDecoratedEventTarget.for method is cached, so created instances will be reused if the inner cache does not refuse additional arguments of the decorator. The cache does not handle multiple and non-primitive arguments.

ESLDecoratedEventTarget.for(
  target: EventTarget,
  decorator: (fn: EventListener, ...args: any[]) => EventListener,
  ...args: any[]
): ESLDecoratedEventTarget;

Parameters:

Example:

class Component {
  @listen({
    event: 'scroll', 
    target: ESLDecoratedEventTarget.for(window, throttle)
  })
  onScroll() {}
}

Sharing of the decorated targets

As was mentioned above, the method ESLDecoratedEventTarget.for works with a cache for simple cases. But in some cases, we might be interested in creating wrappers with a complex param, or we want to limit params usage across the project.

It might sound obvious, but there are no restrictions on sharing exact instances instead of using the method cache.

// shared-event-targets.ts
export const DEBOUNCED_WINDOW = ESLDecoratedEventTarget.for(window, debounce, 1000);
// module.ts
class Component {
  @listen({event: 'resize', target: DEBOUNCED_WINDOW})
  onResize() {}
}

Optimize window.resize handling with debouncing

import {debounce} from '.../debounce';

ESLEventUtils.subscribe(host, {
  event: 'resize',
  target: /* instead just window */ ESLDecoratedEventTarget.for(window, debounce, 250)
}, onResizeDebounced);

The sample above allows you to reuse the debounced by 250 milliseconds version of the window, to receive fewer resize events (same as any other event types observed on the debounced window version)

Optimize window.scroll handling with throttling

import {throttle} from '.../throttle';

ESLEventUtils.subscribe(host, {
  event: 'scroll',
  target: /* instead just window */ ESLDecoratedEventTarget.for(window, throttle, 250)
}, onScrollThrottled);

The sample above allows you to reuse throttled by 250 milliseconds version of the window, to receive no more than one event per 250 milliseconds scroll events (same as any other event types observed on the debounced window version)

ESLResizeObserverTarget.for

When you deal with responsive interfaces, you might need to observe an element resize instead of responding to the whole window change. There is a tool for this in the native DOM API - `ResizeObserver'. The only problem is that it does not use events, while in practice, we work with it in the same way.

ESLResizeObserverTarget.for creates cached ResizeObserver adaptation to EventTarget (ESLResizeObserverTarget) that allows you to get resize events when the observed element changes its size.

ESLResizeObserverTarget.for(el: Element): ESLResizeObserverTarget;

Parameters:

ESLResizeObserverTarget creates itself once for an observed object with a weak reference-based cache. So any way of creating ESLResizeObserverTarget will always produce the same instance.

ESLResizeObserverTarget.for(el) /**always*/ === ESLResizeObserverTarget.for(el) So there is no reason to cache it manually.

Usage example:

ESLEventUtils.subscribe(host, {
  event: 'resize',
  target: ESLResizeObserverTarget.for(el)
}, onResize);
// or
ESLEventUtils.subscribe(host, {
  event: 'resize',
  target: (host) => ESLResizeObserverTarget.for(host.el)
}, onResize);

ESLSwipeGestureTarget.for new

ESLSwipeGestureTarget.for is a simple and easy-to-use way to listen for swipe events on any element.

ESLSwipeGestureTarget.for creates a synthetic target that produces swipe events. It detects pointerdown and pointerup events and based on the distance (threshold) between start and end points and time (timeout) between pointerdown and pointerup events, trigger swipe event on the target element.

ESLSwipeGestureTarget.for(el: Element, settings?: ESLSwipeGestureSetting): ESLSwipeGestureTarget;

Parameters:

Usage example:

ESLEventUtils.subscribe(host, {
  event: 'swipe',
  target: ESLSwipeGestureTarget.for(el)
}, onSwipe);
// or
ESLEventUtils.subscribe(host, {
  event: 'swipe',
  target: (host) => ESLSwipeGestureTarget.for(host.el, {
    threshold: '30px',
    timeout: 1000
  })
}, onSwipe);

ESLWheelTarget.for new

ESLWheelTarget.for is a simple way to listen for 'inert' (long wheel) scroll events on any element. This utility detects wheel events, and based on the total amount (distance) of wheel events and time (timeout) between the first and the last events, it triggers longwheel event on the target element.

ESLWheelTarget.for(el: Element, settings?: ESLWheelTargetSetting): ESLWheelTarget;

Parameters:

The ESLWheelTargetSetting configuration includes these optional attributes:

Usage example:

ESLEventUtils.subscribe(host, {
  event: 'longwheel',
  target: ESLWheelTarget.for(el)
}, onWheel);
// or
ESLEventUtils.subscribe(host, {
  event: 'longwheel',
  target: (host) => ESLWheelTarget.for(host.el, {
    threshold: 30,
    timeout: 1000
  })
}, onWheel);

ESLIntersectionTarget.for new

ESLIntersectionTarget.for is a way to listen for intersections using Intersection Observer API but in an EventTarget way.

ESLIntersectionTarget.for creates a synthetic target that produces intersection events. It detects intersections by creating IntersectionObserver instance, created using passed settings: IntersectionObserverInit.

Note: ESLIntersectionTarget does not share IntersectionObserver instances unlike caching capabilities of adapters mentioned above.

ESLIntersectionTarget.for(el: Element | Element[], settings?: IntersectionObserverInit): ESLIntersectionTarget;

Parameters:

Event API: Throws ESLIntersectionEvent that implements IntersectionObserverEntry original interface.