stateful components now in a workable state; renamed .component macro to .compact which much better captures its meaning
This commit is contained in:
parent
4801b8d8c1
commit
c86916e37d
10 changed files with 185 additions and 291 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
import karax, karaxdsl, vdom, kdom, components
|
import karax, karaxdsl, vdom, kdom, compact
|
||||||
|
|
||||||
const
|
const
|
||||||
Play = 0
|
Play = 0
|
||||||
|
|
@ -12,7 +12,7 @@ proc play(n: Node) {.importcpp.}
|
||||||
proc pause(n: Node) {.importcpp.}
|
proc pause(n: Node) {.importcpp.}
|
||||||
proc `width=`(n: Node, w: int) {.importcpp: "#.width = #".}
|
proc `width=`(n: Node, w: int) {.importcpp: "#.width = #".}
|
||||||
|
|
||||||
proc mplayer*(id, resource: cstring): VNode {.component.} =
|
proc mplayer*(id, resource: cstring): VNode {.compact.} =
|
||||||
proc handler(ev: Event; n: VNode) =
|
proc handler(ev: Event; n: VNode) =
|
||||||
let myVideo = document.getElementById(id)
|
let myVideo = document.getElementById(id)
|
||||||
case n.key
|
case n.key
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
import vdom, karax, karaxdsl, jstrutils, components, localstorage
|
import vdom, karax, karaxdsl, jstrutils, compact, localstorage
|
||||||
|
|
||||||
type
|
type
|
||||||
Filter = enum
|
Filter = enum
|
||||||
|
|
@ -77,7 +77,7 @@ proc toChecked(checked: bool): cstring =
|
||||||
proc selected(v: Filter): cstring =
|
proc selected(v: Filter): cstring =
|
||||||
(if filter == v: cstring"selected" else: cstring(nil))
|
(if filter == v: cstring"selected" else: cstring(nil))
|
||||||
|
|
||||||
proc createEntry(id: int; d: cstring; completed, selected: bool): VNode {.component.} =
|
proc createEntry(id: int; d: cstring; completed, selected: bool): VNode {.compact.} =
|
||||||
result = buildHtml(tr):
|
result = buildHtml(tr):
|
||||||
li(class=toClass(completed)):
|
li(class=toClass(completed)):
|
||||||
if not selected:
|
if not selected:
|
||||||
|
|
@ -92,7 +92,7 @@ proc createEntry(id: int; d: cstring; completed, selected: bool): VNode {.compon
|
||||||
onblur = focusLost,
|
onblur = focusLost,
|
||||||
onkeyupenter = editEntry, value = d, setFocus)
|
onkeyupenter = editEntry, value = d, setFocus)
|
||||||
|
|
||||||
proc makeFooter(entriesCount, completedCount: int): VNode {.component.} =
|
proc makeFooter(entriesCount, completedCount: int): VNode {.compact.} =
|
||||||
result = buildHtml(footer(class = "footer")):
|
result = buildHtml(footer(class = "footer")):
|
||||||
span(class = "todo-count"):
|
span(class = "todo-count"):
|
||||||
strong:
|
strong:
|
||||||
|
|
@ -111,7 +111,7 @@ proc makeFooter(entriesCount, completedCount: int): VNode {.component.} =
|
||||||
button(class = "clear-completed", onclick = clearCompleted):
|
button(class = "clear-completed", onclick = clearCompleted):
|
||||||
text "Clear completed (" & &completedCount & ")"
|
text "Clear completed (" & &completedCount & ")"
|
||||||
|
|
||||||
proc makeHeader(): VNode {.component.} =
|
proc makeHeader(): VNode {.compact.} =
|
||||||
result = buildHtml(header(class = "header")):
|
result = buildHtml(header(class = "header")):
|
||||||
h1:
|
h1:
|
||||||
text "todos"
|
text "todos"
|
||||||
|
|
|
||||||
|
|
@ -51,13 +51,13 @@ proc render(x: VComponent): VNode =
|
||||||
|
|
||||||
var gid: int
|
var gid: int
|
||||||
|
|
||||||
|
proc changed(c: VComponent): bool =
|
||||||
|
let x = Carousel(c)
|
||||||
|
result = x.change
|
||||||
|
|
||||||
proc carousel(): Carousel =
|
proc carousel(): Carousel =
|
||||||
result = Carousel(kind: VNodeKind.component, key: -1)
|
result = newComponent(Carousel, render, changed)
|
||||||
result.render = render
|
|
||||||
result.list = images
|
result.list = images
|
||||||
result.changed = proc (c: VComponent): bool =
|
|
||||||
let x = Carousel(c)
|
|
||||||
result = x.change
|
|
||||||
result.cntdown = ticksUntilChange
|
result.cntdown = ticksUntilChange
|
||||||
result.myid = gid
|
result.myid = gid
|
||||||
inc gid
|
inc gid
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
import vdom, karax, karaxdsl, jdict, jstrutils, components
|
import vdom, karax, karaxdsl, jdict, jstrutils, compact
|
||||||
|
|
||||||
type
|
type
|
||||||
Filter = enum
|
Filter = enum
|
||||||
|
|
|
||||||
121
src/compact.nim
Normal file
121
src/compact.nim
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
## Components in Karax are built by the ``.component`` macro annotation.
|
||||||
|
|
||||||
|
import macros, jdict, kdom, vdom, tables, strutils
|
||||||
|
|
||||||
|
var
|
||||||
|
vcomponents* = newJDict[cstring, proc(args: seq[VNode]): VNode]()
|
||||||
|
dcomponents* = newJDict[cstring, proc(args: seq[VNode]): Node]()
|
||||||
|
|
||||||
|
type
|
||||||
|
ComponentKind* {.pure.} = enum
|
||||||
|
None,
|
||||||
|
Tag,
|
||||||
|
VNode,
|
||||||
|
Node
|
||||||
|
|
||||||
|
var
|
||||||
|
allcomponents {.compileTime.} = initTable[string, ComponentKind]()
|
||||||
|
|
||||||
|
proc isComponent*(x: string): ComponentKind {.compileTime.} =
|
||||||
|
allcomponents.getOrDefault(x)
|
||||||
|
|
||||||
|
proc addTags() {.compileTime.} =
|
||||||
|
let x = (bindSym"VNodeKind").getTypeImpl
|
||||||
|
expectKind(x, nnkEnumTy)
|
||||||
|
for i in ord(VNodeKind.html)..ord(VNodeKind.high):
|
||||||
|
# +1 because of empty node at the start of the enum AST:
|
||||||
|
let tag = $x[i+1]
|
||||||
|
allcomponents[tag] = ComponentKind.Tag
|
||||||
|
|
||||||
|
static:
|
||||||
|
addTags()
|
||||||
|
|
||||||
|
proc unpack(symbolicType: NimNode; index: int): NimNode {.compileTime.} =
|
||||||
|
#let t = symbolicType.getTypeImpl
|
||||||
|
let t = repr(symbolicType)
|
||||||
|
case t
|
||||||
|
of "cstring":
|
||||||
|
result = quote do:
|
||||||
|
args[`index`].text
|
||||||
|
of "int", "VKey":
|
||||||
|
result = quote do:
|
||||||
|
args[`index`].intValue
|
||||||
|
of "bool":
|
||||||
|
result = quote do:
|
||||||
|
args[`index`].intValue != 0
|
||||||
|
elif t.endsWith"Kind":
|
||||||
|
result = quote do:
|
||||||
|
`symbolicType`(args[`index`].intValue)
|
||||||
|
else:
|
||||||
|
# just pass it along, maybe there is some conversion for it:
|
||||||
|
result = quote do:
|
||||||
|
args[`index`]
|
||||||
|
|
||||||
|
proc newname*(n: NimNode): NimNode =
|
||||||
|
if n.kind == nnkPostfix:
|
||||||
|
n[1] = newname(n[1])
|
||||||
|
result = n
|
||||||
|
elif n.kind == nnkSym:
|
||||||
|
result = ident($n.symbol)
|
||||||
|
else:
|
||||||
|
result = n
|
||||||
|
|
||||||
|
macro compact*(prc: untyped): untyped =
|
||||||
|
## A 'compact' tree generation proc is one that only depends on its
|
||||||
|
## inputs and should be stored as a compact virtual DOM tree and
|
||||||
|
## only expanded on demand (when its inputs changed).
|
||||||
|
var n = prc.copyNimNode
|
||||||
|
for i in 0..6: n.add prc[i].copyNimTree
|
||||||
|
expectKind(n, nnkProcDef)
|
||||||
|
if n[0].kind == nnkEmpty:
|
||||||
|
error("please pass a non anonymous proc", n[0])
|
||||||
|
let name = n[0]
|
||||||
|
let params = params(n)
|
||||||
|
let rettype = repr params[0]
|
||||||
|
var isvirtual = ComponentKind.None
|
||||||
|
if rettype == "VNode":
|
||||||
|
isvirtual = ComponentKind.VNode
|
||||||
|
elif rettype == "Node":
|
||||||
|
isvirtual = ComponentKind.Node
|
||||||
|
else:
|
||||||
|
error "component must return VNode or Node", params[0]
|
||||||
|
let realName = if name.kind == nnkPostfix: name[1] else: name
|
||||||
|
let nn = $realName
|
||||||
|
n[0] = ident("inner" & nn)
|
||||||
|
var unpackCall = newCall(n[0])
|
||||||
|
var counter = 0
|
||||||
|
for i in 1.. <params.len:
|
||||||
|
let param = params[i]
|
||||||
|
let L = param.len
|
||||||
|
let typ = param[L-2]
|
||||||
|
for j in 0 .. L-3:
|
||||||
|
unpackCall.add unpack(typ, counter)
|
||||||
|
inc counter
|
||||||
|
|
||||||
|
template vwrapper(pname, unpackCall) {.dirty.} =
|
||||||
|
proc pname(args: seq[VNode]): VNode =
|
||||||
|
unpackCall
|
||||||
|
|
||||||
|
template dwrapper(pname, unpackCall) {.dirty.} =
|
||||||
|
proc pname(args: seq[VNode]): Node =
|
||||||
|
unpackCall
|
||||||
|
|
||||||
|
template vregister(key, val) =
|
||||||
|
bind jdict.`[]=`
|
||||||
|
`[]=`(vcomponents, cstring(key), val)
|
||||||
|
|
||||||
|
template dregister(key, val) =
|
||||||
|
bind jdict.`[]=`
|
||||||
|
`[]=`(dcomponents, cstring(key), val)
|
||||||
|
|
||||||
|
result = newTree(nnkStmtList, n)
|
||||||
|
|
||||||
|
if isvirtual == ComponentKind.VNode:
|
||||||
|
result.add getAst(vwrapper(newname name, unpackCall))
|
||||||
|
result.add getAst(vregister(newLit(nn), realName))
|
||||||
|
else:
|
||||||
|
result.add getAst(dwrapper(newname name, unpackCall))
|
||||||
|
result.add getAst(dregister(newLit(nn), realName))
|
||||||
|
allcomponents[nn] = isvirtual
|
||||||
|
when defined(debugKaraxDsl):
|
||||||
|
echo repr result
|
||||||
|
|
@ -1,225 +0,0 @@
|
||||||
## Components in Karax are built by the ``.component`` macro annotation.
|
|
||||||
|
|
||||||
import macros, jdict, kdom, vdom, tables, strutils
|
|
||||||
|
|
||||||
type
|
|
||||||
StateDict*[V] = ref object
|
|
||||||
defaultValue*: V
|
|
||||||
|
|
||||||
proc get[V](d: StateDict[V], k: VKey): V {.importcpp: "#[#]".}
|
|
||||||
proc put[V](d: StateDict[V], k: VKey, v: V) {.importcpp: "#[#] = #".}
|
|
||||||
|
|
||||||
proc contains*[V](d: StateDict[V], k: VKey): bool {.importcpp: "#.hasOwnProperty(#)".}
|
|
||||||
proc del*[V](d: StateDict[V], k: VKey) {.importcpp: "delete #[#]".}
|
|
||||||
|
|
||||||
proc newStateDict*[V](): StateDict[V] {.importcpp: "{@}".}
|
|
||||||
|
|
||||||
var
|
|
||||||
dirty = newStateDict[bool]()
|
|
||||||
someDirty*: bool
|
|
||||||
|
|
||||||
proc markDirty*(key: VKey) =
|
|
||||||
dirty.put(key, true)
|
|
||||||
someDirty = true
|
|
||||||
|
|
||||||
proc unmarkDirty*(key: VKey) = dirty.del key
|
|
||||||
proc isDirty*(key: VKey): bool = dirty.contains(key)
|
|
||||||
|
|
||||||
proc `[]`*[V](d: StateDict[V], k: VKey): V =
|
|
||||||
if d.contains(k): result = d.get(k)
|
|
||||||
else: result = d.defaultValue
|
|
||||||
|
|
||||||
proc `[]=`*[V](d: StateDict[V], k: VKey, v: V) =
|
|
||||||
d.put(k, v)
|
|
||||||
markDirty(k)
|
|
||||||
|
|
||||||
var
|
|
||||||
vcomponents* = newJDict[cstring, proc(args: seq[VNode]): VNode]()
|
|
||||||
dcomponents* = newJDict[cstring, proc(args: seq[VNode]): Node]()
|
|
||||||
|
|
||||||
type
|
|
||||||
ComponentKind* {.pure.} = enum
|
|
||||||
None,
|
|
||||||
Tag,
|
|
||||||
VNode,
|
|
||||||
Node
|
|
||||||
|
|
||||||
var
|
|
||||||
allcomponents {.compileTime.} = initTable[string, ComponentKind]()
|
|
||||||
|
|
||||||
proc isComponent*(x: string): ComponentKind {.compileTime.} =
|
|
||||||
allcomponents.getOrDefault(x)
|
|
||||||
|
|
||||||
proc addTags() {.compileTime.} =
|
|
||||||
let x = (bindSym"VNodeKind").getTypeImpl
|
|
||||||
expectKind(x, nnkEnumTy)
|
|
||||||
for i in ord(VNodeKind.html)..ord(VNodeKind.high):
|
|
||||||
# +1 because of empty node at the start of the enum AST:
|
|
||||||
let tag = $x[i+1]
|
|
||||||
allcomponents[tag] = ComponentKind.Tag
|
|
||||||
|
|
||||||
static:
|
|
||||||
addTags()
|
|
||||||
|
|
||||||
|
|
||||||
template toState(x): untyped = newIdentNode("state" & x)
|
|
||||||
proc accessState(sv: NimNode): NimNode {.compileTime.} =
|
|
||||||
newTree(nnkBracketExpr, sv, newIdentNode("key"))
|
|
||||||
|
|
||||||
proc stateDecl(n: NimNode; names: TableRef[string, bool]; decl: NimNode) =
|
|
||||||
case n.kind
|
|
||||||
of nnkVarSection, nnkLetSection:
|
|
||||||
for c in n:
|
|
||||||
expectKind c, nnkIdentDefs
|
|
||||||
let typ = c[^2]
|
|
||||||
let val = c[^1]
|
|
||||||
let usedType = if typ.kind != nnkEmpty: typ else: newCall("type", val)
|
|
||||||
if usedType.kind == nnkEmpty:
|
|
||||||
error("cannot determine the variable's type", c)
|
|
||||||
for i in 0 .. c.len-3:
|
|
||||||
let v = $c[i]
|
|
||||||
let sv = toState v
|
|
||||||
decl.add(quote do:
|
|
||||||
var `sv` = newStateDict[`usedType`]())
|
|
||||||
if val.kind != nnkEmpty:
|
|
||||||
decl.add newTree(nnkAsgn, newDotExpr(sv, newIdentNode"defaultValue"),
|
|
||||||
val)
|
|
||||||
else:
|
|
||||||
error("component state must have a primitive initializer")
|
|
||||||
names[v] = true
|
|
||||||
of nnkStmtList, nnkStmtListExpr:
|
|
||||||
for x in n: stateDecl(x, names, decl)
|
|
||||||
of nnkDo:
|
|
||||||
stateDecl(n.body, names, decl)
|
|
||||||
of nnkCommentStmt: discard
|
|
||||||
else:
|
|
||||||
error("invalid 'state' declaration", n)
|
|
||||||
|
|
||||||
proc doState(n: NimNode; names: TableRef[string, bool];
|
|
||||||
decl: NimNode): NimNode =
|
|
||||||
case n.kind
|
|
||||||
of nnkCallKinds:
|
|
||||||
# handle 'state' declaration and remove it from the AST:
|
|
||||||
if n.len == 2 and repr(n[0]) == "state":
|
|
||||||
stateDecl(n[1], names, decl)
|
|
||||||
return newTree(nnkNone)
|
|
||||||
of nnkSym, nnkIdent:
|
|
||||||
let v = $n
|
|
||||||
if v in names:
|
|
||||||
let sv = toState v
|
|
||||||
return accessState(sv)
|
|
||||||
else: discard
|
|
||||||
result = copyNimNode(n)
|
|
||||||
for i in 0..<n.len:
|
|
||||||
let x = doState(n[i], names, decl)
|
|
||||||
if x.kind != nnkNone: result.add x
|
|
||||||
|
|
||||||
proc compBody(body, decl: NimNode): NimNode =
|
|
||||||
var names = newTable[string, bool]()
|
|
||||||
result = doState(body, names, decl)
|
|
||||||
|
|
||||||
proc unpack(symbolicType: NimNode; index: int): NimNode {.compileTime.} =
|
|
||||||
#let t = symbolicType.getTypeImpl
|
|
||||||
let t = repr(symbolicType)
|
|
||||||
case t
|
|
||||||
of "cstring":
|
|
||||||
result = quote do:
|
|
||||||
args[`index`].text
|
|
||||||
of "int", "VKey":
|
|
||||||
result = quote do:
|
|
||||||
args[`index`].intValue
|
|
||||||
of "bool":
|
|
||||||
result = quote do:
|
|
||||||
args[`index`].intValue != 0
|
|
||||||
elif t.endsWith"Kind":
|
|
||||||
result = quote do:
|
|
||||||
`symbolicType`(args[`index`].intValue)
|
|
||||||
else:
|
|
||||||
# just pass it along, maybe there is some conversion for it:
|
|
||||||
result = quote do:
|
|
||||||
args[`index`]
|
|
||||||
|
|
||||||
proc newname*(n: NimNode): NimNode =
|
|
||||||
if n.kind == nnkPostfix:
|
|
||||||
n[1] = newname(n[1])
|
|
||||||
result = n
|
|
||||||
elif n.kind == nnkSym:
|
|
||||||
result = ident($n.symbol)
|
|
||||||
else:
|
|
||||||
result = n
|
|
||||||
|
|
||||||
macro component*(prc: untyped): untyped =
|
|
||||||
## A component takes an proc body and registers it as a component to the
|
|
||||||
## virtual dom.
|
|
||||||
var n = prc.copyNimNode
|
|
||||||
for i in 0..6: n.add prc[i].copyNimTree
|
|
||||||
expectKind(n, nnkProcDef)
|
|
||||||
if n[0].kind == nnkEmpty:
|
|
||||||
error("please pass a non anonymous proc", n[0])
|
|
||||||
let name = n[0]
|
|
||||||
let params = params(n)
|
|
||||||
let rettype = repr params[0]
|
|
||||||
var isvirtual = ComponentKind.None
|
|
||||||
if rettype == "VNode":
|
|
||||||
isvirtual = ComponentKind.VNode
|
|
||||||
elif rettype == "Node":
|
|
||||||
isvirtual = ComponentKind.Node
|
|
||||||
else:
|
|
||||||
error "component must return VNode or Node", params[0]
|
|
||||||
let realName = if name.kind == nnkPostfix: name[1] else: name
|
|
||||||
let nn = $realName
|
|
||||||
n[0] = ident("inner" & nn)
|
|
||||||
var unpackCall = newCall(n[0])
|
|
||||||
var counter = 0
|
|
||||||
for i in 1.. <params.len:
|
|
||||||
let param = params[i]
|
|
||||||
let L = param.len
|
|
||||||
let typ = param[L-2]
|
|
||||||
for j in 0 .. L-3:
|
|
||||||
unpackCall.add unpack(typ, counter)
|
|
||||||
inc counter
|
|
||||||
|
|
||||||
let decl = newTree(nnkStmtList)
|
|
||||||
let newBody = compBody(n, decl)
|
|
||||||
|
|
||||||
template vwrapper(pname, unpackCall) {.dirty.} =
|
|
||||||
proc pname(args: seq[VNode]): VNode =
|
|
||||||
unpackCall
|
|
||||||
|
|
||||||
template dwrapper(pname, unpackCall) {.dirty.} =
|
|
||||||
proc pname(args: seq[VNode]): Node =
|
|
||||||
unpackCall
|
|
||||||
|
|
||||||
template vregister(key, val) =
|
|
||||||
bind jdict.`[]=`
|
|
||||||
`[]=`(vcomponents, cstring(key), val)
|
|
||||||
|
|
||||||
template dregister(key, val) =
|
|
||||||
bind jdict.`[]=`
|
|
||||||
`[]=`(dcomponents, cstring(key), val)
|
|
||||||
|
|
||||||
result = newTree(nnkStmtList, decl, newBody)
|
|
||||||
|
|
||||||
if isvirtual == ComponentKind.VNode:
|
|
||||||
result.add getAst(vwrapper(newname name, unpackCall))
|
|
||||||
result.add getAst(vregister(newLit(nn), realName))
|
|
||||||
else:
|
|
||||||
result.add getAst(dwrapper(newname name, unpackCall))
|
|
||||||
result.add getAst(dregister(newLit(nn), realName))
|
|
||||||
allcomponents[nn] = isvirtual
|
|
||||||
when defined(debugKaraxDsl):
|
|
||||||
echo repr result
|
|
||||||
|
|
||||||
when isMainModule:
|
|
||||||
proc public*(key: VKey; x, y: int, b: bool; s: cstring): VNode {.component.} =
|
|
||||||
state:
|
|
||||||
var foo = 89
|
|
||||||
|
|
||||||
proc callback() =
|
|
||||||
foo = 78
|
|
||||||
|
|
||||||
let cc = callback
|
|
||||||
|
|
||||||
when false:
|
|
||||||
proc private(x, y: int, b: bool; s: cstring): VNode {.component.} =
|
|
||||||
discard
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
## Karax -- Single page applications for Nim.
|
## Karax -- Single page applications for Nim.
|
||||||
|
|
||||||
import kdom, vdom, jstrutils, components, jdict, vstyles
|
import kdom, vdom, jstrutils, compact, jdict, vstyles
|
||||||
|
|
||||||
export kdom.Event
|
export kdom.Event
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ proc wrapEvent(d: Node; n: VNode; k: EventKind; action: EventHandler) =
|
||||||
template detach(n: VNode) =
|
template detach(n: VNode) =
|
||||||
if n.kind == VNodeKind.component:
|
if n.kind == VNodeKind.component:
|
||||||
let x = VComponent(n)
|
let x = VComponent(n)
|
||||||
if x.onDetach != nil: x.onDetach(x)
|
if x.onDetachImpl != nil: x.onDetachImpl(x)
|
||||||
n.dom = nil
|
n.dom = nil
|
||||||
template attach(n: VNode) =
|
template attach(n: VNode) =
|
||||||
n.dom = result
|
n.dom = result
|
||||||
|
|
@ -104,9 +104,9 @@ proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
|
||||||
return result
|
return result
|
||||||
elif n.kind == VNodeKind.component:
|
elif n.kind == VNodeKind.component:
|
||||||
let x = VComponent(n)
|
let x = VComponent(n)
|
||||||
if x.onAttach != nil: x.onAttach(x)
|
if x.onAttachImpl != nil: x.onAttachImpl(x)
|
||||||
assert x.render != nil
|
assert x.renderImpl != nil
|
||||||
result = vnodeToDom(x.render(x), kxi)
|
result = vnodeToDom(x.renderImpl(x), kxi)
|
||||||
attach n
|
attach n
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
|
|
@ -147,7 +147,7 @@ proc replaceById(id: cstring; newTree: Node) =
|
||||||
|
|
||||||
type
|
type
|
||||||
EqResult = enum
|
EqResult = enum
|
||||||
different, similar, identical
|
changed, different, similar, identical
|
||||||
|
|
||||||
proc eq(a, b: VNode; deep: bool): EqResult =
|
proc eq(a, b: VNode; deep: bool): EqResult =
|
||||||
if a.kind != b.kind: return different
|
if a.kind != b.kind: return different
|
||||||
|
|
@ -162,15 +162,16 @@ proc eq(a, b: VNode; deep: bool): EqResult =
|
||||||
for i in 0..<a.len:
|
for i in 0..<a.len:
|
||||||
if eq(a[i], b[i], deep) == different: return different
|
if eq(a[i], b[i], deep) == different: return different
|
||||||
elif b.kind == VNodeKind.component:
|
elif b.kind == VNodeKind.component:
|
||||||
# let x = VComponent(b)
|
# different component names mean different components:
|
||||||
# assert x.changed != nil
|
if a.text != b.text: return different
|
||||||
# return if x.changed(x): different else: identical
|
let x = VComponent(b)
|
||||||
return different
|
assert x.changedImpl != nil
|
||||||
|
return if x.changedImpl(x): changed else: identical
|
||||||
elif deep:
|
elif deep:
|
||||||
if a.len != b.len: return different
|
if a.len != b.len: return different
|
||||||
for i in 0..<a.len:
|
for i in 0..<a.len:
|
||||||
let res = eq(a[i], b[i], deep)
|
let res = eq(a[i], b[i], deep)
|
||||||
if res == different: return different
|
if res <= different: return different
|
||||||
elif res == similar:
|
elif res == similar:
|
||||||
# but continue, maybe something makes it 'different'!
|
# but continue, maybe something makes it 'different'!
|
||||||
result = similar
|
result = similar
|
||||||
|
|
@ -181,21 +182,22 @@ proc eq(a, b: VNode; deep: bool): EqResult =
|
||||||
# Do not test event listeners here!
|
# Do not test event listeners here!
|
||||||
return result
|
return result
|
||||||
|
|
||||||
proc updateDirtyElements(parent, current: Node, newNode: VNode,
|
when false:
|
||||||
kxi: KaraxInstance) =
|
proc updateDirtyElements(parent, current: Node, newNode: VNode,
|
||||||
if newNode.key >= 0 and isDirty(newNode.key):
|
kxi: KaraxInstance) =
|
||||||
unmarkDirty(newNode.key)
|
if newNode.key >= 0 and isDirty(newNode.key):
|
||||||
let n = vnodeToDom(newNode, kxi)
|
unmarkDirty(newNode.key)
|
||||||
if parent == nil:
|
let n = vnodeToDom(newNode, kxi)
|
||||||
replaceById(kxi.rootId, n)
|
if parent == nil:
|
||||||
else:
|
replaceById(kxi.rootId, n)
|
||||||
parent.replaceChild(n, current)
|
else:
|
||||||
elif newNode.kind != VNodeKind.text and newNode.kind != VNodeKind.vthunk and
|
parent.replaceChild(n, current)
|
||||||
newNode.kind != VNodeKind.dthunk:
|
elif newNode.kind != VNodeKind.text and newNode.kind != VNodeKind.vthunk and
|
||||||
for i in 0..newNode.len-1:
|
newNode.kind != VNodeKind.dthunk:
|
||||||
updateDirtyElements(current, current[i], newNode[i], kxi)
|
for i in 0..newNode.len-1:
|
||||||
# leave early if we know there cannot be anything left to do:
|
updateDirtyElements(current, current[i], newNode[i], kxi)
|
||||||
#if dirtyCount <= 0: return
|
# leave early if we know there cannot be anything left to do:
|
||||||
|
#if dirtyCount <= 0: return
|
||||||
|
|
||||||
proc updateStyles(newNode, oldNode: VNode; deep: bool) =
|
proc updateStyles(newNode, oldNode: VNode; deep: bool) =
|
||||||
# we keep the oldNode, but take over the style from the new node:
|
# we keep the oldNode, but take over the style from the new node:
|
||||||
|
|
@ -216,21 +218,17 @@ proc updateDom(newNode, oldNode: VNode) =
|
||||||
|
|
||||||
proc updateElement(parent, current: Node, newNode, oldNode: VNode;
|
proc updateElement(parent, current: Node, newNode, oldNode: VNode;
|
||||||
kxi: KaraxInstance): bool =
|
kxi: KaraxInstance): bool =
|
||||||
if oldNode.kind == newNode.kind and oldNode.kind == VNodeKind.component:
|
|
||||||
let x = VComponent(oldNode)
|
|
||||||
assert x.changed != nil
|
|
||||||
if x.changed(x):
|
|
||||||
let n = vnodeToDom(x.render(x), kxi)
|
|
||||||
if parent == nil:
|
|
||||||
replaceById(kxi.rootId, n)
|
|
||||||
else:
|
|
||||||
parent.replaceChild(n, current)
|
|
||||||
return true
|
|
||||||
|
|
||||||
let res = eq(newNode, oldNode, deep=false)
|
let res = eq(newNode, oldNode, deep=false)
|
||||||
if res == different:
|
if res <= different:
|
||||||
detach(oldNode)
|
var n: Node
|
||||||
let n = vnodeToDom(newNode, kxi)
|
if res == changed:
|
||||||
|
assert oldNode.kind == VNodeKind.component
|
||||||
|
let x = VComponent(oldNode)
|
||||||
|
n = vnodeToDom(x.renderImpl(x), kxi)
|
||||||
|
result = true
|
||||||
|
else:
|
||||||
|
detach(oldNode)
|
||||||
|
n = vnodeToDom(newNode, kxi)
|
||||||
if parent == nil:
|
if parent == nil:
|
||||||
replaceById(kxi.rootId, n)
|
replaceById(kxi.rootId, n)
|
||||||
else:
|
else:
|
||||||
|
|
@ -264,7 +262,7 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode;
|
||||||
a[i] = b
|
a[i] = b
|
||||||
#updateDom(a, b)
|
#updateDom(a, b)
|
||||||
action
|
action
|
||||||
of different: break
|
of different, changed: break
|
||||||
of similar:
|
of similar:
|
||||||
#updateDom(a, b)
|
#updateDom(a, b)
|
||||||
a[i] = b
|
a[i] = b
|
||||||
|
|
@ -318,9 +316,6 @@ proc dodraw(kxi: KaraxInstance) =
|
||||||
let olddom = document.getElementById(kxi.rootId)
|
let olddom = document.getElementById(kxi.rootId)
|
||||||
discard updateElement(nil, olddom, newtree, kxi.currentTree, kxi)
|
discard updateElement(nil, olddom, newtree, kxi.currentTree, kxi)
|
||||||
#assert same(newtree, document.getElementById("ROOT"))
|
#assert same(newtree, document.getElementById("ROOT"))
|
||||||
if someDirty:
|
|
||||||
updateDirtyElements(nil, olddom, newtree, kxi)
|
|
||||||
someDirty = false
|
|
||||||
kxi.currentTree = newtree
|
kxi.currentTree = newtree
|
||||||
|
|
||||||
if not kxi.postRenderCallback.isNil:
|
if not kxi.postRenderCallback.isNil:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
import macros, karax, vdom, components
|
import macros, karax, vdom, compact
|
||||||
from strutils import startsWith, toLowerAscii
|
from strutils import startsWith, toLowerAscii
|
||||||
|
|
||||||
const
|
const
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
## Include file that contains the common imports for the Karax framework.
|
## Include file that contains the common imports for the Karax framework.
|
||||||
|
|
||||||
import karax, karaxdsl, vdom, components
|
import karax, karaxdsl, vdom, compact
|
||||||
|
|
|
||||||
19
src/vdom.nim
19
src/vdom.nim
|
|
@ -120,10 +120,10 @@ type
|
||||||
## is not part of the virtual DOM anymore.
|
## is not part of the virtual DOM anymore.
|
||||||
|
|
||||||
VComponent* = ref object of VNode
|
VComponent* = ref object of VNode
|
||||||
render*: proc(self: VComponent): VNode
|
renderImpl*: proc(self: VComponent): VNode
|
||||||
changed*: proc(self: VComponent): bool
|
changedImpl*: proc(self: VComponent): bool
|
||||||
onAttach*: proc(self: VComponent)
|
onAttachImpl*: proc(self: VComponent)
|
||||||
onDetach*: proc(self: VComponent)
|
onDetachImpl*: proc(self: VComponent)
|
||||||
|
|
||||||
proc value*(n: VNode): cstring = n.text
|
proc value*(n: VNode): cstring = n.text
|
||||||
proc `value=`*(n: VNode; v: cstring) = n.text = v
|
proc `value=`*(n: VNode; v: cstring) = n.text = v
|
||||||
|
|
@ -143,12 +143,15 @@ proc vthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
|
||||||
proc dthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
|
proc dthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
|
||||||
VNode(kind: VNodeKind.dthunk, text: name, key: -1, kids: @args)
|
VNode(kind: VNodeKind.dthunk, text: name, key: -1, kids: @args)
|
||||||
|
|
||||||
proc vcomponent*(render: (proc(self: VComponent): VNode) not nil,
|
template newComponent*[T](t: typeDesc[T];
|
||||||
|
render: (proc(self: VComponent): VNode) not nil,
|
||||||
changed: (proc(self: VComponent): bool) not nil,
|
changed: (proc(self: VComponent): bool) not nil,
|
||||||
onAttach: proc(self: VComponent) = nil,
|
onAttach: proc(self: VComponent) = nil,
|
||||||
onDetach: proc(self: VComponent) = nil): VNode =
|
onDetach: proc(self: VComponent) = nil): T =
|
||||||
result = VComponent(kind: VNodeKind.component, key: -1, render: render,
|
## Use this template to create new components.
|
||||||
changed: changed, onAttach: onAttach, onDetach: onDetach)
|
T(kind: VNodeKind.component, key: -1,
|
||||||
|
text: cstring(astToStr(t)), renderImpl: render,
|
||||||
|
changedImpl: changed, onAttachImpl: onAttach, onDetachImpl: onDetach)
|
||||||
|
|
||||||
proc setAttr*(n: VNode; key: cstring; val: cstring = "") =
|
proc setAttr*(n: VNode; key: cstring; val: cstring = "") =
|
||||||
if n.attrs.isNil:
|
if n.attrs.isNil:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue