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

com.raquo.waypoint.SplitRender.scala Maven / Gradle / Ivy

There is a newer version: 8.0.1
Show newest version
package com.raquo.waypoint

// #TODO Test all this

import com.raquo.airstream.core.Signal
import scala.reflect.ClassTag

case class SplitRender[Page, View](
  pageSignal: Signal[Page],
  renderers: List[Renderer[Page, View]] = Nil
) {

  /** Add a standard renderer for Page type */
  def collect[P <: Page : ClassTag](render: P => View): SplitRender[Page, View] = {
    val renderer = new CollectPageRenderer[Page, View]({ case p: P => render(p) })
    copy(renderers = renderers :+ renderer)
  }

  /** Just a convenience method for static pages. `page` is expected to be an `object`. */
  def collectStatic[P <: Page](page: P)(view: => View): SplitRender[Page, View] = {
    collectStaticPF { case `page` => view }
  }

  /** Similar to `collectStatic`, but evaluates `view` only once, when this method is called. */
  def collectStaticStrict[P <: Page](page: P)(view: View): SplitRender[Page, View] = {
    collectStaticPF { case `page` => view }
  }

  // @TODO[Naming] This is really the PF equivalent of collectStatic, right...?
  def collectStaticPF(render: PartialFunction[Page, View]): SplitRender[Page, View] = {
    val renderer = new CollectPageRenderer[Page, View](render)
    copy(renderers = renderers :+ renderer)
  }

  /** This renderer is efficient. It creates only a single element (instance of Out)
    * that takes Signal[P] as a parameter instead of creating a Signal of elements.
    */
  def collectSignal[P <: Page : ClassTag](render: Signal[P] => View): SplitRender[Page, View] = {
    collectSignalPF({ case p: P => p })(render)
  }

  def collectSignalPF[P](pf: PartialFunction[Page, P])(render: Signal[P] => View): SplitRender[Page, View] = {
    val renderer = new CollectPageSignalRenderer[Page, P, View](pf, render)
    copy(renderers = renderers :+ renderer)
  }

  /** Signal of output elements. Put this in your DOM with:
    * `child <-- SplitRender(page).collect(...).collectSignal(...).signal`
    */
  lazy val signal: Signal[View] = {
    var maybeCurrentRenderer: Option[Renderer[Page, View]] = None

    pageSignal.map { nextPage =>
      val iterator = renderers.iterator
      var nextView: Option[View] = None

      // Here's how this works.
      // - We try to render the next page using every rendered in the router
      // - The renderer that recognizes this page will return Some(view)
      // - pageSignal will return this view, BUT
      //   - If this was the same renderer as previous page,
      //     this element might actually be the same element as before
      //     because the renderer internals update the view internals
      //     instead of re-creating the element
      //   - If this was a new renderer, we need to notify the previous renderer
      //     that it's no longer active by calling discard() on it

      while (nextView.isEmpty && iterator.hasNext) {
        val renderer = iterator.next()
        val newView = renderer.render(nextPage)
        if (newView.isDefined) {
          maybeCurrentRenderer.filter(_ != renderer).foreach(_.discard())
          maybeCurrentRenderer = Some(renderer)
          nextView = newView
        }
      }

      nextView.getOrElse(throw new Exception("Page did not match any renderer: " + nextPage.toString))
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy