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

elmish.View.kt Maven / Gradle / Ivy

/*
 * Copyright 2019 the original author or authors.
 *
 * 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 elmish

import org.w3c.dom.Element
import org.w3c.dom.events.Event
import kotlinx.dom.addClass
import kotlinx.dom.appendElement
import kotlinx.dom.appendText


val empty = View.Empty


val h1 = ViewFactory("h1")


val h2 = ViewFactory("h2")


val div = ViewFactory("div")


val pre = ViewFactory("pre")


val code = ViewFactory("code")


val span = ViewFactory("span")


val small = ViewFactory("small")


val ol = ViewFactory("ol")


val ul = ViewFactory("ul")


val li = ViewFactory("li")


val a = ViewFactory("a")


fun  render(view: View, into: Element, send: (I) -> Unit) {
    into.innerHTML = ""
    into.appendElementFor(view, send)
}


data class ViewFactory(val elementName: String) {

    operator fun  invoke(innerText: String): View =
        View(elementName, innerText = innerText)

    operator fun  invoke(children: List>): View =
        View(elementName, children = children)

    operator fun  invoke(vararg children: View): View =
        View(elementName, children = children.asList())

    operator fun  invoke(
        attributes: List>,
        vararg children: View
    ): View = View(
        elementName,
        attributes = attributes,
        children = children.asList()
    )

    operator fun  invoke(
        attributes: List>,
        innerText: String
    ): View = View(
        elementName,
        attributes = attributes,
        innerText = innerText
    )

    operator fun  invoke(
        innerText: String,
        vararg children: View
    ): View = View(
        elementName,
        innerText = innerText,
        children = children.asList()
    )
}


/**
 * A minimalist HTML data structure for type-safe composition of fragments.
 */
sealed class View {

    fun  map(f: (I) -> O): View =
        MappedView(this, f)

    companion object {

        operator fun  invoke(
            elementName: String,
            attributes: List> = emptyList(),
            innerText: String? = null,
            children: List> = emptyList()
        ): View = Element(
            elementName,
            attributes,
            innerText,
            children
        )
    }

    object Empty : View()

    data class Element(
        val elementName: String,
        val attributes: List> = emptyList(),
        val innerText: String? = null,
        val children: List> = emptyList()
    ) : View()

    data class MappedView(
        val view: View,
        val mapping: (I) -> O
    ) : View()
}


fun  attributes(builder: Attributes.() -> Unit): List> =
    mutableListOf>().also { attributes ->
        builder(Attributes { attributes.add(it) })
    }


class Attributes(private val add: (Attribute) -> Unit) {

    fun onClick(handler: EventHandler) = add(Attribute.OnEvent("click", handler))

    fun className(value: String) = add(Attribute.ClassName(value))

    fun title(value: String) = add(Attribute.Named("title", value))

    fun href(value: String) = add(Attribute.Named("href", value))

    fun src(value: String) = add(Attribute.Named("src", value))
}


typealias EventHandler = (Event) -> I


sealed class Attribute {

    class OnEvent(
        val eventName: String,
        val handler: (Event) -> I
    ) : Attribute()

    class ClassName(
        val value: String
    ) : Attribute()

    class Named(
        val name: String,
        val value: String
    ) : Attribute()
}


private
fun  Element.appendElementFor(view: View, send: (I) -> Unit) {
    when (view) {
        is View.Element -> appendElement(view.elementName) {
            view.innerText?.let(this::appendText)
            view.children.forEach { child ->
                appendElementFor(child, send)
            }
            view.attributes.forEach { a ->
                when (a) {
                    is Attribute.OnEvent -> addEventListener(
                        a.eventName,
                        { event ->
                            event.stopPropagation()
                            send(a.handler(event))
                        }
                    )
                    is Attribute.ClassName -> addClass(
                        a.value
                    )
                    is Attribute.Named -> setAttribute(
                        a.name,
                        a.value
                    )
                }
            }
        }
        is View.MappedView<*, *> -> {
            @Suppress("unchecked_cast")
            val mappedView = view as View.MappedView
            appendElementFor(mappedView.view) {
                send(mappedView.mapping(it))
            }
        }
        View.Empty -> return
    }
}