All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.raquo.laminar.emitter.EventPropTransformation.scala Maven / Gradle / Ivy

The newest version!
package com.raquo.laminar.emitter

import com.raquo.airstream.core.Observer
import com.raquo.airstream.eventbus.EventBus
import com.raquo.domtypes.generic.keys.EventProp
import com.raquo.laminar.nodes.ReactiveElement
import org.scalajs.dom

/**
  * This class represents a set of transformations that can be applied to events feeding into an [[EventPropEmitter]]
  * EventPropTransformation-s are immutable, so can be reused by multiple emitters.
  *
  * Example syntax: `input(onChange().preventDefault.mapTo(true) --> myBooleanWriteBus)`
  *
  * Note: Params are protected to avoid confusing autocomplete options (e.g. "useCapture")
  *
  * @param useCapture
  *          This is not a part of the processing pipeline since it takes effect when we register the event listener,
  *          not when a new event comes in. Therefore this option can only be set on initialization.
  *          See `useCapture` docs here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
  * @param processor
  *          Processes incoming events before they're passed to the next processor or to the listening EventBus.
  *          Returns an Option of the processed value. If None, the value should not passed down the chain.
  */
class EventPropTransformation[Ev <: dom.Event, V](
  protected val eventProp: EventProp[Ev],
  protected val useCapture: Boolean = false,
  protected val processor: Ev => Option[V]
) {

  // @TODO[Performance,Elegance] Is it possible to move these --> methods to ReactiveEventProp?
  // We can't have them in both places, because then type inference does not work

  @inline def -->[El <: ReactiveElement[dom.Element]](observer: Observer[V]): EventPropEmitter[Ev, V, El] = {
    new EventPropEmitter(observer, eventProp, useCapture, processor)
  }

  @inline def -->[El <: ReactiveElement[dom.Element]](onNext: V => Unit): EventPropEmitter[Ev, V, El] = {
    -->(Observer(onNext))
  }

  @inline def -->[BusEv >: V, El <: ReactiveElement[dom.Element]](eventBus: EventBus[BusEv]): EventPropEmitter[Ev, V, El] = {
    -->(eventBus.writer)
  }

  /** Prevent default browser action for the given event (e.g. following the link when it is clicked)
    * https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault
    *
    * Note: this is just a standard transformation, so it will be fired in whatever order you have applied it.
    * So for example, you can [[filter]] events before applying this, preventing default action only for certain events.
    *
    * Example: `input(onKeyUp().filter(ev => ev.keyCode == KeyCode.Tab).preventDefault --> tabKeyUpBus)`
    */
  def preventDefault: EventPropTransformation[Ev, V] = {
    copy(newProcessor = ev => {
      val value = processor(ev)
      ev.preventDefault()
      value
    })
  }

  /** Propagation here refers to DOM Event bubbling or capture propagation.
    * https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation
    *
    * Note: this is just a standard transformation, so it will be fired in whatever order you have applied it.
    * So for example, you can [[filter]] events before applying this, preventing default action only for certain events.
    *
    * Example: `div(onClick.filter(isGoodClick).stopPropagation --> goodClickBus)`
     */
  def stopPropagation: EventPropTransformation[Ev, V] = {
    copy(newProcessor = ev => {
      val value = processor(ev)
      ev.stopPropagation()
      value
    })
  }

  /** Values that do not pass will not propagate down the chain and into the emitter. */
  def filter(passes: V => Boolean): EventPropTransformation[Ev, V] = {
    copy(newProcessor = ev => processor(ev).filter(passes))
  }

  def map[V2](project: V => V2): EventPropTransformation[Ev, V2] = {
    copy(newProcessor = ev => processor(ev).map(project))
  }

  def mapTo[V2](value: => V2): EventPropTransformation[Ev, V2] = {
    copy(newProcessor = ev => processor(ev).map(_ => value))
  }

  def mapToValue[V2](value: V2): EventPropTransformation[Ev, V2] = {
    copy(newProcessor = ev => processor(ev).map(_ => value))
  }

  def collect[V2](pf: PartialFunction[V, V2]): EventPropTransformation[Ev, V2] = {
    copy(newProcessor = ev => processor(ev).collect(pf))
  }

  // Don't need the extra codegen overhead of a case class
  private def copy[V2](newProcessor: Ev => Option[V2]): EventPropTransformation[Ev, V2] = {
    new EventPropTransformation[Ev, V2](eventProp, useCapture, newProcessor)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy