kotlinx.html

repository·master·Indexed 23 days ago

https://github.com/kotlin/kotlinx.html

A Kotlin DSL for building HTML. It enables developers to generate HTML for DOM trees (JS/WASM/JVM) or stream it directly to a Writer or Appendable (Multiplatform). The library includes the TagConsumer interface for custom renderers, support for raw HTML via unsafe blocks, and an injector package for mapping DOM elements to Kotlin object properties using InjectCapture strategies.

Tokens
3K
Snippets
3
Records
19
Agent score
82%

What's inside kotlinx.html

  1. Build HTML DOM trees

    master

    You can build a DOM tree using JVM, JS, and WASM targets. The library provides extension functions like append and create to integrate with the browser's DOM API.

    • Use append { ... } on a DOM element to add new elements to an existing tree.
    • Use create.tag { ... } to create a new element in memory without immediately attaching it to the document.
    • Use the + operator (unary plus) to add text content inside a tag.
    import kotlinx.browser.document
    import kotlinx.browser.window
    import kotlinx.html.a
    import kotlinx.html.div
    import kotlinx.html.dom.append
    import kotlinx.html.dom.create
    import kotlinx.html.p
    
    fun main() {
        val body = document.body ?: error("No body")
        body.append {
            div {
                p {
                    +"Here is "
                    a("https://kotlinlang.org") { +"official Kotlin site" }
                }
            }
        }
    
        val timeP = document.create.p {
            +"Time: 0"
        }
    
        body.append(timeP)
    
        var time = 0
        window.setInterval({
            time++
            timeP.textContent = "Time: $time"
    
            return@setInterval null
        }, 1000)
    }
  2. Build HTML to a Writer or Appendable

    master

    For non-DOM environments (like JVM) or when you need to stream HTML directly to a buffer, you can build HTML to a Writer or Appendable. On the JVM, you can use the appendHTML() extension function to start building the HTML structure.

    System.out.appendHTML().html {
        body {
            div {
                a("https://kotlinlang.org") {
                    target = ATarget.blank
                    +"Main site"
                }
            }
        }
    }
  3. Inject DOM elements into a bean using InjectorConsumer

    master

    The kotlinx.html.injector package provides utilities for automatically mapping created DOM elements to properties of a Kotlin object (a "bean") based on specific rules. This is useful in WasmJS environments for bridging the gap between HTML generation and object-oriented state management.

    Capture Strategies

    You define how elements are identified using InjectCapture implementations:

    • InjectByClassName(className: String): Matches elements by their CSS class.
    • InjectByTagName(tagName: String): Matches elements by their HTML tag name (case-insensitive).
    • InjectRoot: Matches the root element of the consumer.
    • CustomCapture: An interface for complex matching logic. Implement apply(element: Element): Boolean to define custom criteria.

    Usage Patterns

    You can use the inject extension function on a TagConsumer or appendAndInject on an existing Element to apply these rules during the HTML building process.

  4. Use the unsafe block for raw HTML content

    master

    The unsafe block allows you to inject raw strings or entities directly into the HTML output without any escaping.

    Warning: Using unsafe is risky because it can expose your application to Cross-Site Scripting (XSS) attacks. Use the standard DSL builder whenever possible. If you must use unsafe, ensure the content is properly sanitized or comes from a trusted source.

    Inside an unsafe block, you can use the unary plus operator (+) to append strings, entities, or numbers.

  5. Use `appendAndInject` to build and populate a bean

    master

    The appendAndInject extension function on HTMLElement allows you to append a block of HTML DSL code to an existing element while simultaneously injecting the resulting elements into a provided bean based on matching rules.

    val container = document.getElementById("container") as HTMLElement
    
    val myBean = MyBean()
    
    container.appendAndInject(
        bean = myBean,
        rules = listOf(
            InjectByClassName("target-class") to MyBean::targetField,
            InjectRoot to MyBean::rootField
    )
    ) {
        div(classes = "target-class") {
            textContent = "Hello World"
        }
    }
  6. Use appendAndInject to build and inject elements

    master

    The appendAndInject extension function allows you to append new HTML content to an existing Element while simultaneously injecting the resulting DOM nodes into a provided bean based on a list of rules.

    Signature:

    fun <T : Any> Element.appendAndInject(
        bean: T,
        rules: List<Pair<InjectCapture, KMutableProperty1<T, out Element>>>,
        block: TagConsumer<Element>.() -> Unit
    ): List<Element>

    Parameters:

    • bean: The target object containing properties to be populated.
    • rules: A list of pairs where the first element is an InjectCapture strategy and the second is a property reference (KMutableProperty1) on the bean that accepts an Element.
  7. Use TickerAttribute for boolean presence attributes

    master

    For HTML attributes that are defined by their presence alone (e.g., required, disabled, checked), use TickerAttribute.

    When the value is true, the attribute is added to the tag with its name as the value (e.g., required="required" or simply required depending on the consumer). When false, the attribute is removed entirely from the tag.

  8. Visit tags with visit() and visitAndFinalize()

    master

    You can use the visit extension functions to execute a block of code within the context of a specific tag:

    • visit { ... }: Executes a block of code using the tag as the receiver.
    • visitAndFinalize(consumer) { ... }: Executes a block of code and then calls finalize() on the provided TagConsumer to return a result of type R.
  9. Use inject to wrap a TagConsumer with injection logic

    master

    The inject extension function wraps an existing TagConsumer<Element> with an InjectorConsumer. This allows you to inject elements into a bean as they are being processed by the consumer.

    Signature:

    fun <T : Any> TagConsumer<Element>.inject(
        bean: T,
        rules: List<Pair<InjectCapture, KMutableProperty1<T, out Element>>>
    ): TagConsumer<Element>

    Parameters:

    • bean: The target object to receive the injected elements.
    • rules: A list of InjectCapture strategies paired with bean property references.
  10. Use StringSetAttribute for space-separated attribute values

    master
    Use StringSetAttribute for attributes that contain a list of strings separated by whitespace (e.g., class names). It automatically handles encoding a Set<String> into a space-separated string and decoding a space-separated string back into a Set<String>. If the attribute is missing, it returns an empty set.
  11. Implement CustomCapture for complex element matching

    master

    If standard class or tag name matching is insufficient, you can implement the CustomCapture interface to provide a custom predicate for element injection.

    class MyCustomMatcher(val predicate: (HTMLElement) -> Boolean) : CustomCapture {
        override fun apply(element: HTMLElement): Boolean = predicate(element)
    }
    
    // Usage in rules list:
    // MyCustomMatcher { it.getAttribute("data-special") == "true" } to MyBean::specialField
  12. Implement custom attribute encoding with AttributeEncoder

    master

    If you need to define how a custom type is converted to and from an HTML attribute string, implement the AttributeEncoder<T> interface.

    • encode(attributeName, value): Converts the type T into its string representation for the HTML attribute.
    • decode(attributeName, value): Parses the string value from the HTML attribute back into type T.
    • empty(attributeName, tag): Defines the default value for the attribute if it is missing from the tag.
    interface AttributeEncoder<T> {
        fun encode(attributeName: String, value: T): String
        fun decode(attributeName: String, value: String): T
        fun empty(attributeName: String, tag: Tag): T
    }