/actim

A small web frontend framework for nim

Primary LanguageNimMIT LicenseMIT

This is in early development, so use with caution

Actim

Actim is a small, simple web frontend framework.

Actim works by simply defining a renderer proc that generates a VNode. This renderer will be called whenever some event occurs. And the DOM will be updated where its needed (dom diffing).

Example

import actim

let style1 = newVStyle:
  "padding": 5.px
  "background-color": "#44ffaa"

template repeateCount(n: int, body: untyped): VNode =
  buildVNode "div":
    for i in 1 .. n:
      ++ text(i, ":")
      body

proc buildDom: VNode =
  var testText {.global.} = "foo"

  buildVNode "div":
    style: newVStyle:
      "font-weight": "bold"

    handle "click":
      testText = "ba"

    ++ text testText

    ++ "a":
      attr "href": "/"
      ++ text "ho"

    ++ "br"

    +> repeateCount 3:
      style style1
      ++ text "foo"

setRenderer buildDom

Implicit vnode var

In every scope you have access to an implicit vnode variable, which is the currently constructed vnode.

So lets look at a short example by rewriting the following code:

buildVNode "div":
  style: newVStyle:
    "font-weight": "bold"

  handle "click":
    echo "click"

  ++ text "foo"

to

buildVNode "div":
  vnode.styles.add: newVStyle:
    "font-weight": "bold"

  vnode.handlers["click"] = proc(e: Event) =
    echo "click"

  vnode.childs &= text "foo"

In practice this is primarly usefull inside handlers, but maybe there are some other special cases where this comes in handy. If you need that manual access its there.

Components

Defining components is really simple. Just write a proc/template/macro that generates the VDom.

Examples

let textInputStyle = newVStyle:
  "background-color": "#444"
  "color": "white"
  "padding": 20.px

proc drawTextInput*(styles: varargs[VStyle]): VNode =
  buildVNode "input":
    attr "type": "text"
    for s in styles:
      style s
    style textInputStyle
template drawNav*(body: untyped): VNode =
  let
    navStyle = newVStyle:
      "background-color": "#bbb"
      "padding": 10.px

    optionStyleBase = newVStyle:
      "padding": 5.px

    optionStyle = extendVStyle optionStyleBase:
      "color": "#333"

    selectedOptionStyle = extendVStyle optionStyleBase:
      "color": "white"
      "background-color": "black"

  var 
    select {.global.} = 0
    optionNum = 0

  template option(title: static string, onclick: untyped) {.inject.} =
    ++ "div":
      ++ text title
      if optionNum == select:
        style selectedOptionStyle
      else:
        style optionStyle
        let n = optionNum
        handle "click":
          select = n
          onclick

    inc optionNum

  buildVNode "div":
    style navStyle
    body

# use:

proc buildDom: VNode =
  drawNav:

    option "foo":
      echo "clicked"

    option "ba": discard

setRenderer buildDom

Routing

To use routing via the hash part of the url, use a proc(route: string): VNode as renderer.
Take a look at tests/test2.nim for a small example.

Contributions

Issues and PRs are welcome.

TODO

  • Better documentation (espacially readme)
  • complete ajax module