ScalaTags

repository·master·Indexed 20 days ago

https://github.com/com-lihaoyi/scalatags

A high-performance, type-safe XML/HTML/CSS construction library for Scala. It enables the creation of HTML templates using pure Scala code with full IDE support, avoiding separate template languages. It provides both a Text backend for generating strings and a JsDom backend for Scala.js to render directly to dom.Element objects. The library includes a type-safe DSL for inline styles, CSS stylesheets, and comprehensive support for HTML5 attributes and event handlers.

Tokens
13.4K
Snippets
76
Records
80
Agent score
73%

What's inside scalatags

  1. Use the Text module for HTML string generation

    master

    The scalatags.Text module is designed for generating HTML as Strings. It provides a set of bundles (like all, short, tags, attrs, etc.) that allow you to construct HTML structures using a type-safe DSL. The resulting fragments can be rendered to a String using .render or written directly to a java.io.Writer or java.io.OutputStream.

    import scalatags.Text._
    
    // Example of generating an HTML string
    val html: String = div(
      class := "container",
      h1("Hello World"),
      p("This is Scalatags text module.")
    ).render
  2. Auto-escaping and raw input

    master

    By default, all text and attribute values are HTML-escaped to prevent XSS. If you need to insert unsanitized/raw HTML, wrap the string in the raw() function.

    import scalatags.Text.all._
    
    val unsafe = "<script>alert('xss')</script>"
    
    // Escaped: &lt;script&gt;...
    val safe = p(unsafe)
    
    // Unescaped: <script>...</n// WARNING: Use only with trusted input!
    val dangerous = p(raw(unsafe))
  3. How to use Scalatags with a custom Virtual DOM

    master

    The VirtualDom[Output <: FragT, FragT] trait allows you to use Scalatags' DSL to construct fragments for any virtual DOM implementation (e.g., Scala XML trees, Preact/React VDOM nodes in the browser).

    To use it, you must implement a custom VirtualDom instance by providing the following factory methods:

    1. stringToFrag(s: String): FragT: Converts a plain string into your VDOM fragment type.
    2. rawToFrag(s: String): FragT: Converts a raw HTML string into your VDOM fragment type.
    3. makeBuilder(tag: String): vdom.Builder[Output, FragT]: Creates a builder for a specific HTML tag.

    Once instantiated, you can access various DSL namespaces like attrs, tags, styles, and svgTags to build your tree.

    // Conceptual implementation pattern
    object MyReactDom extends VirtualDom[ReactNode, ReactNode] {
      def stringToFrag(s: String): ReactNode = TODO("Convert string to React node")
      def rawToFrag(s: String): ReactNode = TODO("Convert raw HTML to React node")
      def makeBuilder(tag: String): vdom.Builder[ReactNode, ReactNode] = TODO("Return a builder for React")
    
      // The trait provides namespaces like:
      // object attrs, object tags, object styles, etc.
    }
    
    // Usage:
    val myElement = MyReactDom.tags.div(MyReactDom.attrs.id := "foo")(MyReactDom.stringFrag("hello"))
  4. Extend Scalatags with custom attributes, styles, or types

    master

    Scalatags uses a combination of typeclasses and implicit conversions to allow for extensible rendering:

    • Typeclasses: AttrValue[Builder, T] and StyleValue[Builder, T] allow you to define how specific types T are applied as attributes or styles. For example, the JsDom backend provides an implicit to bind anything convertible to js.Any into attributes.
    • Implicit Conversions: These allow arbitrary types to be used as children within a Frag or TypedTag by converting them into a Modifier or Frag.

    You can implement your own typeclass instances to handle complex types like Future (to add a child upon completion) or reactive variables (to update the UI when the value changes).

  5. Use the Scala.js DOM backend

    master

    For Scala.js projects, you can use the JsDom backend to render directly to dom.Element objects instead of strings. This allows for direct DOM manipulation and binding of JavaScript functions to events.

    // Replace scalatags.Text with scalatags.JsDom
    import scalatags.JsDom._
    
    // Renders to a DOM element
    val element = div(
      onclick := { println("Clicked!") },
      "Click me!"
    ).render
    
    // You can now use standard DOM APIs
    val parent = element.parentElement
  6. How TypedTag and Modifiers work together

    master

    In Scalatags, a TypedTag represents an HTML element. To customize a tag (e.g., adding attributes, styles, or children), you use Modifiers.

    Modifiers are applied to a tag using the apply method (or the *args syntax). There are two main types of modifiers:

    1. Attribute/Style Modifiers: These (like AttrPair or StylePair) add metadata to the tag but do not appear as children in the final rendered output.
    2. Fragment Modifiers: These (implementing Frag) represent standalone pieces of content that can be rendered as part of the tag's children.

    When you call .render on a TypedTag, Scalatags walks through all applied modifiers and applies them to a Builder to produce the final output.

    // Conceptual usage pattern
    val myTag = div(
      Attr("class") := "container", // Modifier
      Style("color", "color") := "red", // Modifier
      span("Hello") // Fragment/Child
    )
  7. Define a cascading CascadingStyleSheet

    master

    If you need to define CSS rules using cascading selectors (like .my-class or div.my-class), inherit from CascadingStyleSheet instead of StyleSheet.

    CascadingStyleSheet provides an implicit conversion that allows you to use Cls objects directly as selectors, enabling you to write rules like myClass.selector or target specific tags with classes.

    import scalatags.stylesheet._
    
    abstract class MyCascadingStyles extends CascadingStyleSheet {
      // CascadingStyleSheet allows using class selectors directly
      // in a way that standard StyleSheet does not.
      initStyleSheet()
    }
  8. Understand the core Scalatags abstractions: TypedTag, Frag, and Modifier

    master

    Scalatags is built on three primary abstractions that compose to form HTML/XML structures:

    1. TypedTag[Builder, +Output, FragT]: Represents an HTML/XML tag. It contains a tag name and a collection of modifiers. You can add modifications (like attributes or children) using .apply(xs: Modifier[Builder]* ) and render the entire tree using .render.
    2. Frag[Builder, FragT]: Represents a renderable snippet. It is the smallest standalone atom and can contain zero or more tags and string nodes. It implements Modifier so it can be nested.
    3. Modifier[Builder]: Represents anything that can be applied to a TypedTag. This includes styles, classes, attributes, or any Frag that can be appended as a child.

    In the Text backend, a TypedTag[String] is commonly aliased as Tag.

    trait TypedTag[Builder, +Output, FragT] extends Frag[Builder, FragT]{
      def tag: String
      def modifiers: List[Seq[Modifier[Builder]]]
      def apply(xs: Modifier[Builder]*): Self
      def render: Output
    }
    
    trait Frag[Builder, FragT] extends Modifier[Builder]{
      def render: FragT
    }
    
    trait Modifier[Builder] {
      def applyTo(t: Builder): Unit
    }
  9. Control flow and variables in templates

    master

    Since ScalaTags is pure Scala, you can use standard Scala control flow (if, for) and variables directly within your templates.

    import scalatags.Text.all._
    
    val items = Seq("Apple", "Banana", "Cherry")
    val userLoggedIn = true
    
    val list = div(
      if (userLoggedIn) {
        ul(
          items.map(item => li(item)).foreach(li => li)
        )
      } else {
        p("Please log in")
      }
    )
  10. Compare Text and DOM backends

    master

    Scalatags provides different backends depending on your target runtime:

    • Text Backend (scalatags.Text): Used for generating HTML/XML as a String. It uses a custom text.Builder for high performance. The common alias for a tag here is type Tag = TypedTag[String].
    • DOM Backend (scalatags.JsDom): Used for manipulating the browser DOM (via Scala.js). It uses dom.Element as the builder and produces various DOM element types as output. Common aliases include:
      • type HtmlTag = TypedTag[dom.HTMLElement]
      • type SvgTag = TypedTag[dom.SVGElement]
      • type Tag = TypedTag[dom.Element]

    Both backends share a common structure defined in generic.Bundle, but they differ in how they handle the Builder and Output types.

  11. Use collections as modifiers for tags

    master

    Scalatags provides implicit conversions that allow you to pass standard Scala collections directly as children (modifiers) of a tag. This makes it easy to render lists of elements or handle optional elements without manual iteration.

    Supported collections:

    • Seq[A]
    • Option[A]
    • Array[A]
    • geny.Generator[A] (via GeneratorFrag)

    All elements within these collections must satisfy the Modifier or Frag requirements.

    // Example: Passing a Seq of elements directly to a tag
    val items = Seq(span("one"), span("two"))
    div(items)
    
    // Example: Passing an Option
    val maybeSpan = Some(span("optional"))
    div(maybeSpan)
  12. How StyleTree represents CSS rules

    master

    A StyleTree is an intermediate structure representing a set of CSS rules before they are rendered into a final stylesheet. It consists of selectors (the CSS selector strings), styles (a map of property-value pairs), and children (nested StyleTree objects). The stringify method flattens this tree into standard CSS syntax, handling nesting and pseudo-selectors correctly.

    // A StyleTree can be flattened into CSS via stringify
    val tree = StyleTree(Seq(".cls1"), SortedMap("color" -> "red"), Nil)
    val css = tree.stringify(Nil)
    // Result: .cls1 {
    //   color:red
    // }