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

korolev.Component.scala Maven / Gradle / Ivy

There is a newer version: 1.16.0-M5
Show newest version
/*
 * Copyright 2017-2020 Aleksey Fomkin
 *
 * 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.
 */

package korolev

import korolev.Context._
import korolev.effect.Effect
import korolev.state.{StateDeserializer, StateSerializer}
import levsha.Document.Node

import scala.util.Random

/**
  * Component definition. Every Korolev application is a component.
  * Extent it to declare component in object oriented style.
  *
  * @param id Unique identifier of the component.
  *           Use it when you create component declaration dynamically
  *
  * @tparam F Control monad
  * @tparam S State of the component
  * @tparam E Type of events produced by component
  */
abstract class Component[
    F[_],
    S,
    P,
    E
](
    val initialState: Either[P => F[S], S],
    val id: String
) {

  def this(initialState: S) = this(Right(initialState), Component.randomId())
  def this(initialState: S, id: String) = this(Right(initialState), id)
  def this(loadState: P => F[S]) = this(Left(loadState), Component.randomId())

  /**
    * Component context.
    *
    * {{{
    *  import context._
    * }}}
    */
  val context = Context[F, S, E]

  /**
    * Component render
    */
  def render(parameters: P, state: S): context.Node

  /**
   * Render component while state is not loaded.
   */
  def renderNoState(parameters: P): context.Node =
    levsha.dsl.html.div()

  /**
   * Parameters may be changed.
   * Maybe you want to reload state.
   * The function returns optional effect.
   * If no reload required it returns None,
   * else it returns effect with new state.
   */
  def maybeUpdateState(parameters: P, currentState: S): Option[F[S]] = None
}

object Component {

  /** (context, state) => document */
  type Render[F[_], S, P, E] = (Context[F, S, E], P, S) => Node[Binding[F, S, E]]

  /**
    * Create component in functional style
    * @param f Component renderer
    * @see [[Component]]
    */
  def apply[F[_]: Effect, S: StateSerializer: StateDeserializer, P, E](
      initialState: S,
      id: String = Component.randomId())(f: Render[F, S, P, E]): Component[F, S, P, E] = {
    new Component[F, S, P, E](initialState, id) {
      def render(parameters: P, state: S): Node[Binding[F, S, E]] = f(context, parameters, state)
    }
  }

  final val TopLevelComponentId = "top-level"

  private[korolev] def randomId() = Random.alphanumeric.take(6).mkString
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy