Karax Framework Documentation

repository·master·Indexed 22 days ago

https://github.com/karaxnim/karax

A lightweight, high-performance single-page application (SPA) framework for the Nim programming language. Karax utilizes a virtual DOM approach and Nim's macro system to produce minimal JavaScript output. It features a buildHtml DSL for constructing DOM trees, support for Server Side Rendering (SSR), and a reactivity model based on event-triggered redraws. The framework includes the karun build tool for development and hot-reloading.

Tokens
4.3K
Snippets
19
Records
24
Agent score
77%

What's inside Karax

  1. Use the `key` field in VNodes for data modeling

    master

    Every virtual DOM node includes a special key field, which is an integer. This field is intended to be used as a data model-specific identifier. You can access this key within event handlers to facilitate changes to your data model.

    Note: The key is not a hint for the DOM diffing algorithm; multiple nodes may share the same key value.

  2. Understand `kstring` for JavaScript compatibility

    master

    When compiling to JavaScript, Nim's native string type is handled as a sequence of numbers, which can cause performance penalties and unexpected output (e.g., [45, 67, 85, 34, ...]).

    To work with native JavaScript strings efficiently, use:

    • cstring: Corresponds to the native JavaScript string type.
    • kstring: A cross-platform type that resolves to cstring when compiled with the js backend, and a standard string otherwise.

    Always prefer kstring for text data to ensure your code is both performant on the web and portable to other platforms.

  3. Compose UI using functions instead of components

    master

    Karax does not have a formal "Component" abstraction. Instead, you achieve reusability and organization by using standard Nim functions that return VNodes. Function arguments serve as "props".

    include karax/prelude
    
    type Task = ref object
      id: int
      text: kstring
    
    var tasks = @[
      Task(id: 0, text: "Buy milk"),
      Task(id: 1, text: "Clean table"),
      Task(id: 2, text: "Call mom")
    ]
    
    proc render(t: Task): VNode =
      buildHtml(li):
        text t.text
    
    proc createDom(): VNode =
      buildHtml(tdiv):
        ol:
          for task in tasks:
            task.render()
    
    setRenderer createDom
    include karax/prelude
    
    type Task = ref object
      id: int
      text: kstring
    var tasks = @[
      Task(id: 0, text: "Buy milk"),
      Task(id: 1, text: "Clean table"),
      Task(id: 2, text: "Call mom")
    ]
    proc render(t: Task): VNode =
      buildHtml(li):
        text t.text
    
    proc createDom(): VNode =
      buildHtml(tdiv):
        ol:
          for task in tasks:
            task.render()
    
    setRenderer createDom
  4. How reactivity works in Karax

    master

    Unlike frameworks that use reactive state objects, Karax reacts to events.

    When an event handler (like onclick, keyup, or an AJAX call via karax/kajax) is triggered, Karax automatically triggers a redraw. A redraw calls the function passed to setRenderer, creates a new virtual DOM, compares it to the previous one (diffing), and applies the resulting patches to the real DOM.

    If you are using non-standard events (like setInterval or WebSockets), you must manually trigger a redraw using the redraw(kxi) function to update the UI.

    include karax/prelude
    import karax/kdom except setInterval
    
    # Wrapping setInterval to make it reactive
    proc setInterval(cb: proc(), interval: int): Interval {.discardable.} = 
      kdom.setInterval(proc =
        cb()
        if not kxi.surpressRedraws: redraw(kxi)
      , interval)
    
    var v = 10
    proc update = v += 10
    
    setInterval(update, 200)
    
    proc main: VNode =
      buildHtml(tdiv):
        text $v
    
    setRenderer main
  5. Understand Virtual DOM node types in Karax

    master

    The vdom module manages the Virtual DOM. While most nodes correspond to HTML5 tags, Karax provides specific VNodeKind extensions to support efficient component systems and data handling:

    • VNodeKind.int: A node containing a single integer field.
    • VNodeKind.bool: A node containing a single boolean field.
    • VNodeKind.vthunk: A 'virtual thunk' node. It contains a function that produces a VNode structure on demand.
    • VNodeKind.dthunk: A 'DOM thunk' node. It contains a function that produces a Node DOM structure on demand.
  6. How Karax's event model works

    master

    Karax uses a standard DOM event model. Event handlers in the buildHtml DSL can be defined in two ways:

    1. As a block inside the element:

      button:
        text "Click me"
        proc onclick(ev: Event; n: VNode) = 
          # handle event
    2. As an attribute with a closure:

      button(onclick = () => myFunc()):
        text "Click me"

    Event handlers must have the signature proc(ev: Event; n: VNode) or proc().

    Note on Strings: Karax uses kstring (an alias for cstring on JS targets) for efficient string handling. On JS, kstring is an immutable JavaScript string; on native targets, it maps to Nim's string.

    include karax / prelude
    
    var lines: seq[kstring] = @[]
    
    proc createDom(): VNode =
      result = buildHtml(tdiv):
        button:
          text "Say hello!"
          proc onclick(ev: Event; n: VNode) =
            lines.add "Hello simulated universe"
        for x in lines:
          tdiv:
            text x
    
    setRenderer createDom
  7. Use conditionals and loops in the DOM

    master

    Karax uses standard Nim syntax for logic within the buildHtml DSL.

    Conditionals: Use standard if/else blocks to toggle element existence.

    Loops: Use standard Nim for loops to render lists of elements.

    Note on tdiv: Because div is a reserved keyword in Nim, Karax uses tdiv to represent the <div> element.

    include karax/prelude
    var list = @[kstring"Apples", "Oranges", "Bananas"]
    
    proc createDom(): VNode =
      buildHtml(tdiv):
        for fruit in list:
          p:
            text fruit
            text " is a fruit"
    
    setRenderer createDom
    include karax/prelude
    var list = @[kstring"Apples", "Oranges", "Bananas"]
    proc createDom(): VNode =
      buildHtml(tdiv):
        for fruit in list:
          p:
            text fruit
            text " is a fruit"
    
    setRenderer createDom
  8. Create a basic Karax application

    master

    A Karax application starts by including karax/prelude, defining a function that returns a VNode (using the buildHtml DSL), and then passing that function to setRenderer.

    include karax/prelude # imports many of the basic Karax modules
    
    proc createDom(): VNode = # define a function to return our HTML nodes
      buildHtml(p): # create a paragraph element
        text "Welcome to Karax!" # set the text inside of the paragraph element
    
    setRenderer createDom # tell Karax to use function to render
    include karax/prelude
    
    proc createDom(): VNode = 
      buildHtml(p):
        text "Welcome to Karax!"
    
    setRenderer createDom
  9. Handle event handlers in static/server-side rendering

    master

    When using Karax for static site generation or server-side rendering where a JS interpreter is not present, standard event handlers (like onchange) will cause compiler errors because addEventHandler expects a client-side environment.

    To resolve this, you can override addEventHandler with a template that simply sets the event as a string attribute on the node. This allows the HTML to be generated with the correct attributes (e.g., onchange="this.form.submit()") so that the browser can execute them once the page loads.

    template kxi(): int = 0
    template addEventHandler(n: VNode; k: EventKind; action: string; kxi: int) =
      n.setAttr($k, action)
    
    let
      names = @["nim", "c", "python"]
      selected_name = "nim"
      hello = buildHtml(html):
        form(`method` = "get"):
          select(name="name", onchange="this.form.submit()"):
            for name in names:
              if name == selected_name:
                option(selected = ""): text name
              else:
                option: text name
  10. Install Karax via Nimble

    master

    To install the Karax framework, ensure you have Nim installed on your system, then use the Nim package manager (Nimble) to install the karax package.

    Karax utilizes Nim's JavaScript backend for compilation, making it compatible with most modern web browsers.

    nimble install karax
  11. Pass data to event handlers using closures

    master

    Because Karax event handlers expect specific signatures (proc() or proc(ev: Event; n: VNode)), you cannot pass extra arguments directly. Instead, use Nim's closures to capture data.

    Pattern: Create a function that returns a proc with the required signature.

    proc menuAction(menuEntry: kstring): proc() =
      result = proc() = 
        echo "clicked ", menuEntry
    
    proc buildMenu(menu: seq[kstring]): VNode =
      result = buildHtml(tdiv):
        for m in menu:
          nav(class="navbar is-primary"):
            tdiv(class="navbar-brand"):
              a(class="navbar-item", onclick = menuAction(m)):
                text m
  12. Build multiple Karax instances for a single page

    master

    To host multiple independent Karax applications on the same HTML page, you must compile each Nim file into a separate JavaScript file using a unique kxiname flag. This prevents namespace collisions between the different Karax instances.

    1. Compile the first application: nim js -d:kxiname="app1" . ile1.nim
    2. Compile the second application: nim js -d:kxiname="app2" . ile2.nim
    3. Reference both generated JavaScript files in your index.html and launch it in a browser.
    nim js -d:kxiname="app1" .
    app1.nim
    nim js -d:kxiname="app2" .
    app2.nim