new component example works
This commit is contained in:
parent
d1e0e1367b
commit
4801b8d8c1
4 changed files with 100 additions and 64 deletions
|
|
@ -2,59 +2,68 @@
|
||||||
import vdom, vstyles, karax, karaxdsl, jdict, jstrutils, kdom
|
import vdom, vstyles, karax, karaxdsl, jdict, jstrutils, kdom
|
||||||
|
|
||||||
type
|
type
|
||||||
Car = ref object of VComponent
|
Carousel = ref object of VComponent
|
||||||
counter: int
|
counter: int
|
||||||
cntdown: int
|
cntdown, myid: int
|
||||||
timer: TimeOut
|
timer: TimeOut
|
||||||
list: seq[cstring]
|
list: seq[cstring]
|
||||||
change: bool
|
change: bool
|
||||||
onclick: proc (ev: Event; n: VNode)
|
|
||||||
|
|
||||||
const ticksUntilChange = 5
|
const ticksUntilChange = 5
|
||||||
|
|
||||||
proc car*(images: seq[cstring]): Car =
|
|
||||||
new(result)
|
|
||||||
result.list = images
|
|
||||||
result.cntdown = ticksUntilChange
|
|
||||||
|
|
||||||
proc docount() =
|
|
||||||
dec result.cntdown
|
|
||||||
if result.cntdown == 0:
|
|
||||||
result.counter = (result.counter + 1) mod result.list.len
|
|
||||||
result.cntdown = ticksUntilChange
|
|
||||||
else:
|
|
||||||
result.timer = setTimeout(docount, 30)
|
|
||||||
result.change = true
|
|
||||||
redraw()
|
|
||||||
|
|
||||||
result.onclick = proc (ev: Event; n: VNode) =
|
|
||||||
result.change = false
|
|
||||||
if result.timer != nil:
|
|
||||||
clearTimeout(result.timer)
|
|
||||||
result.timer = setTimeout(docount, 30)
|
|
||||||
|
|
||||||
method changed(c: Car): bool = true
|
|
||||||
|
|
||||||
var
|
var
|
||||||
images: seq[cstring] = @[cstring"a", "b", "c", "d"]
|
images: seq[cstring] = @[cstring"a", "b", "c", "d"]
|
||||||
|
|
||||||
proc carousel*(): VNode =
|
proc render(x: VComponent): VNode =
|
||||||
let c = car(images)
|
let self = Carousel(x)
|
||||||
|
|
||||||
|
proc docount() =
|
||||||
|
dec self.cntdown
|
||||||
|
if self.cntdown == 0:
|
||||||
|
self.counter = (self.counter + 1) mod self.list.len
|
||||||
|
self.cntdown = ticksUntilChange
|
||||||
|
else:
|
||||||
|
self.timer = setTimeout(docount, 30)
|
||||||
|
self.change = true
|
||||||
|
redraw()
|
||||||
|
|
||||||
|
proc onclick(ev: Event; n: VNode) =
|
||||||
|
self.change = false
|
||||||
|
if self.timer != nil:
|
||||||
|
clearTimeout(self.timer)
|
||||||
|
self.timer = setTimeout(docount, 30)
|
||||||
|
|
||||||
let col =
|
let col =
|
||||||
case c.counter
|
case self.counter
|
||||||
of 0: cstring"#4d4d4d"
|
of 0: cstring"#4d4d4d"
|
||||||
of 1: cstring"#ff00ff"
|
of 1: cstring"#ff00ff"
|
||||||
of 2: cstring"#00ffff"
|
of 2: cstring"#00ffff"
|
||||||
of 3: cstring"#ffff00"
|
of 3: cstring"#ffff00"
|
||||||
else: cstring"red"
|
else: cstring"red"
|
||||||
result = buildHtml(tdiv(nref = c)):
|
result = buildHtml(tdiv()):
|
||||||
text images[c.counter]
|
text self.list[self.counter]
|
||||||
button(onclick = c.onclick):
|
button(onclick = onclick):
|
||||||
text "Next"
|
text "Next"
|
||||||
tdiv(style = style(StyleAttr.color, col)):
|
tdiv(style = style(StyleAttr.color, col)):
|
||||||
text "This changes its color."
|
text "This changes its color." & &self.myid
|
||||||
if c.cntdown != ticksUntilChange:
|
if self.cntdown != ticksUntilChange:
|
||||||
text &c.cntdown
|
text &self.cntdown
|
||||||
|
|
||||||
|
var gid: int
|
||||||
|
|
||||||
|
proc carousel(): Carousel =
|
||||||
|
result = Carousel(kind: VNodeKind.component, key: -1)
|
||||||
|
result.render = render
|
||||||
|
result.list = images
|
||||||
|
result.changed = proc (c: VComponent): bool =
|
||||||
|
let x = Carousel(c)
|
||||||
|
result = x.change
|
||||||
|
result.cntdown = ticksUntilChange
|
||||||
|
result.myid = gid
|
||||||
|
inc gid
|
||||||
|
#result.onAttach = proc (_: VComponent) =
|
||||||
|
# result.cntdown = ticksUntilChange
|
||||||
|
# kout cstring"attached!"
|
||||||
|
|
||||||
proc createDom(): VNode =
|
proc createDom(): VNode =
|
||||||
result = buildHtml(table):
|
result = buildHtml(table):
|
||||||
|
|
|
||||||
|
|
@ -80,14 +80,11 @@ proc wrapEvent(d: Node; n: VNode; k: EventKind; action: EventHandler) =
|
||||||
# --------------------- DOM diff -----------------------------------------
|
# --------------------- DOM diff -----------------------------------------
|
||||||
|
|
||||||
template detach(n: VNode) =
|
template detach(n: VNode) =
|
||||||
if not n.nref.isNil():
|
if n.kind == VNodeKind.component:
|
||||||
n.nref.vnode = nil
|
let x = VComponent(n)
|
||||||
n.nref.onDetach()
|
if x.onDetach != nil: x.onDetach(x)
|
||||||
n.dom = nil
|
n.dom = nil
|
||||||
template attach(n: VNode) =
|
template attach(n: VNode) =
|
||||||
if not n.nref.isNil():
|
|
||||||
n.nref.vnode = n
|
|
||||||
n.nref.onAttach()
|
|
||||||
n.dom = result
|
n.dom = result
|
||||||
|
|
||||||
proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
|
proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
|
||||||
|
|
@ -105,6 +102,13 @@ proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
|
||||||
#n.key = result.key
|
#n.key = result.key
|
||||||
attach n
|
attach n
|
||||||
return result
|
return result
|
||||||
|
elif n.kind == VNodeKind.component:
|
||||||
|
let x = VComponent(n)
|
||||||
|
if x.onAttach != nil: x.onAttach(x)
|
||||||
|
assert x.render != nil
|
||||||
|
result = vnodeToDom(x.render(x), kxi)
|
||||||
|
attach n
|
||||||
|
return result
|
||||||
else:
|
else:
|
||||||
result = document.createElement(toTag[n.kind])
|
result = document.createElement(toTag[n.kind])
|
||||||
attach n
|
attach n
|
||||||
|
|
@ -150,9 +154,6 @@ proc eq(a, b: VNode; deep: bool): EqResult =
|
||||||
if a.id != b.id: return different
|
if a.id != b.id: return different
|
||||||
result = identical
|
result = identical
|
||||||
if a.key != b.key: return different
|
if a.key != b.key: return different
|
||||||
if a.nref != nil and changed(a.nref):
|
|
||||||
kout cstring"yes, change!"
|
|
||||||
return different
|
|
||||||
if a.kind == VNodeKind.text:
|
if a.kind == VNodeKind.text:
|
||||||
if a.text != b.text: return different
|
if a.text != b.text: return different
|
||||||
elif a.kind == VNodeKind.vthunk or a.kind == VNodeKind.dthunk:
|
elif a.kind == VNodeKind.vthunk or a.kind == VNodeKind.dthunk:
|
||||||
|
|
@ -160,6 +161,11 @@ proc eq(a, b: VNode; deep: bool): EqResult =
|
||||||
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:
|
||||||
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:
|
||||||
|
# let x = VComponent(b)
|
||||||
|
# assert x.changed != nil
|
||||||
|
# return if x.changed(x): different else: identical
|
||||||
|
return different
|
||||||
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:
|
||||||
|
|
@ -204,13 +210,23 @@ proc updateStyles(newNode, oldNode: VNode; deep: bool) =
|
||||||
|
|
||||||
proc updateDom(newNode, oldNode: VNode) =
|
proc updateDom(newNode, oldNode: VNode) =
|
||||||
newNode.dom = oldNode.dom
|
newNode.dom = oldNode.dom
|
||||||
newNode.nref = oldNode.nref
|
|
||||||
assert newNode.len == oldNode.len
|
assert newNode.len == oldNode.len
|
||||||
for i in 0 ..< newNode.len:
|
for i in 0 ..< newNode.len:
|
||||||
updateDom(newNode[i], oldNode[i])
|
updateDom(newNode[i], oldNode[i])
|
||||||
|
|
||||||
proc updateElement(parent, current: Node, newNode, oldNode: VNode;
|
proc updateElement(parent, current: Node, newNode, oldNode: VNode;
|
||||||
kxi: KaraxInstance) =
|
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)
|
detach(oldNode)
|
||||||
|
|
@ -241,32 +257,35 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode;
|
||||||
else:
|
else:
|
||||||
var commonPrefix = 0
|
var commonPrefix = 0
|
||||||
|
|
||||||
template eqAndUpdate(a, b: VNode; action: untyped) =
|
template eqAndUpdate(a: VNode; i: int; b: VNode; action: untyped) =
|
||||||
let r = eq(a, b, true)
|
let r = eq(a[i], b, true)
|
||||||
case r
|
case r
|
||||||
of identical:
|
of identical:
|
||||||
updateDom(a, b)
|
a[i] = b
|
||||||
|
#updateDom(a, b)
|
||||||
action
|
action
|
||||||
of different: break
|
of different: break
|
||||||
of similar:
|
of similar:
|
||||||
updateDom(a, b)
|
#updateDom(a, b)
|
||||||
updateStyles(a, b, true)
|
a[i] = b
|
||||||
|
#updateStyles(a, b, true)
|
||||||
action
|
action
|
||||||
|
|
||||||
while commonPrefix < minLength:
|
while commonPrefix < minLength:
|
||||||
eqAndUpdate(newNode[commonPrefix], oldNode[commonPrefix]):
|
eqAndUpdate(newNode, commonPrefix, oldNode[commonPrefix]):
|
||||||
inc commonPrefix
|
inc commonPrefix
|
||||||
|
|
||||||
var oldPos = oldLength - 1
|
var oldPos = oldLength - 1
|
||||||
var newPos = newLength - 1
|
var newPos = newLength - 1
|
||||||
while oldPos >= commonPrefix and newPos >= commonPrefix:
|
while oldPos >= commonPrefix and newPos >= commonPrefix:
|
||||||
eqAndUpdate(newNode[newPos], oldNode[oldPos]):
|
eqAndUpdate(newNode, newPos, oldNode[oldPos]):
|
||||||
dec oldPos
|
dec oldPos
|
||||||
dec newPos
|
dec newPos
|
||||||
|
|
||||||
var pos = min(oldPos, newPos) + 1
|
var pos = min(oldPos, newPos) + 1
|
||||||
for i in commonPrefix..pos-1:
|
for i in commonPrefix..pos-1:
|
||||||
updateElement(current, current.childNodes[i], newNode[i], oldNode[i], kxi)
|
if updateElement(current, current.childNodes[i], newNode[i], oldNode[i], kxi):
|
||||||
|
newNode[i] = oldNode[i]
|
||||||
|
|
||||||
var nextChildPos = oldPos + 1
|
var nextChildPos = oldPos + 1
|
||||||
while pos <= newPos:
|
while pos <= newPos:
|
||||||
|
|
@ -297,7 +316,7 @@ proc dodraw(kxi: KaraxInstance) =
|
||||||
replaceById(kxi.rootId, asdom)
|
replaceById(kxi.rootId, asdom)
|
||||||
else:
|
else:
|
||||||
let olddom = document.getElementById(kxi.rootId)
|
let olddom = document.getElementById(kxi.rootId)
|
||||||
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:
|
if someDirty:
|
||||||
updateDirtyElements(nil, olddom, newtree, kxi)
|
updateDirtyElements(nil, olddom, newtree, kxi)
|
||||||
|
|
@ -363,7 +382,7 @@ proc setupErrorHandler*() =
|
||||||
## the error message is put into the console, otherwise `alert`
|
## the error message is put into the console, otherwise `alert`
|
||||||
## is called.
|
## is called.
|
||||||
proc stackTraceAsCstring(): cstring = cstring(getStackTrace())
|
proc stackTraceAsCstring(): cstring = cstring(getStackTrace())
|
||||||
var onerror {.importc: "window.onerror".} =
|
var onerror {.importc: "window.onerror", used.} =
|
||||||
proc (msg, url: cstring, line, col: int, error: cstring): bool =
|
proc (msg, url: cstring, line, col: int, error: cstring): bool =
|
||||||
var x = cstring"Error: " & msg & "\n" & stackTraceAsCstring()
|
var x = cstring"Error: " & msg & "\n" & stackTraceAsCstring()
|
||||||
kout(x)
|
kout(x)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from strutils import startsWith, toLowerAscii
|
||||||
|
|
||||||
const
|
const
|
||||||
StmtContext = ["kout", "inc", "echo", "dec", "!"]
|
StmtContext = ["kout", "inc", "echo", "dec", "!"]
|
||||||
SpecialAttrs = ["id", "class", "value", "key", "style", "nref"]
|
SpecialAttrs = ["id", "class", "value", "key", "style"]
|
||||||
|
|
||||||
proc getName(n: NimNode): string =
|
proc getName(n: NimNode): string =
|
||||||
case n.kind
|
case n.kind
|
||||||
|
|
|
||||||
22
src/vdom.nim
22
src/vdom.nim
|
|
@ -8,6 +8,7 @@ type
|
||||||
VNodeKind* {.pure.} = enum
|
VNodeKind* {.pure.} = enum
|
||||||
text = "#text", int = "#int", bool = "#bool",
|
text = "#text", int = "#int", bool = "#bool",
|
||||||
vthunk = "#vthunk", dthunk = "#dthunk",
|
vthunk = "#vthunk", dthunk = "#dthunk",
|
||||||
|
component = "#component",
|
||||||
|
|
||||||
html, head, title, base, link, meta, style,
|
html, head, title, base, link, meta, style,
|
||||||
script, noscript,
|
script, noscript,
|
||||||
|
|
@ -101,16 +102,13 @@ macro buildLookupTables(): untyped =
|
||||||
buildLookupTables()
|
buildLookupTables()
|
||||||
|
|
||||||
type
|
type
|
||||||
VComponent* = ref object of RootObj
|
|
||||||
|
|
||||||
EventHandler* = proc (ev: Event; target: VNode) {.closure.}
|
EventHandler* = proc (ev: Event; target: VNode) {.closure.}
|
||||||
VKey* = int
|
VKey* = int
|
||||||
|
|
||||||
VNode* = ref object
|
VNode* = ref object of RootObj
|
||||||
kind*: VNodeKind
|
kind*: VNodeKind
|
||||||
key*: VKey
|
key*: VKey
|
||||||
id*, class*, text*: cstring
|
id*, class*, text*: cstring
|
||||||
nref*: VComponent
|
|
||||||
kids: seq[VNode]
|
kids: seq[VNode]
|
||||||
# even index: key, odd index: value; done this way for memory efficiency:
|
# even index: key, odd index: value; done this way for memory efficiency:
|
||||||
attrs: seq[cstring]
|
attrs: seq[cstring]
|
||||||
|
|
@ -121,9 +119,11 @@ type
|
||||||
dom*: Node ## the attached real DOM node. Can be 'nil' if the virtual node
|
dom*: Node ## the attached real DOM node. Can be 'nil' if the virtual node
|
||||||
## is not part of the virtual DOM anymore.
|
## is not part of the virtual DOM anymore.
|
||||||
|
|
||||||
method onAttach*(v: VComponent) {.base.} = discard
|
VComponent* = ref object of VNode
|
||||||
method onDetach*(v: VComponent) {.base.} = discard
|
render*: proc(self: VComponent): VNode
|
||||||
method changed*(v: VComponent): bool {.base.} = false
|
changed*: proc(self: VComponent): bool
|
||||||
|
onAttach*: proc(self: VComponent)
|
||||||
|
onDetach*: 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,6 +143,13 @@ 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,
|
||||||
|
changed: (proc(self: VComponent): bool) not nil,
|
||||||
|
onAttach: proc(self: VComponent) = nil,
|
||||||
|
onDetach: proc(self: VComponent) = nil): VNode =
|
||||||
|
result = VComponent(kind: VNodeKind.component, key: -1, render: render,
|
||||||
|
changed: changed, onAttach: onAttach, onDetach: 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:
|
||||||
n.attrs = @[key, val]
|
n.attrs = @[key, val]
|
||||||
|
|
@ -160,6 +167,7 @@ proc getAttr*(n: VNode; key: cstring): cstring =
|
||||||
|
|
||||||
proc len*(x: VNode): int = x.kids.len
|
proc len*(x: VNode): int = x.kids.len
|
||||||
proc `[]`*(x: VNode; idx: int): VNode = x.kids[idx]
|
proc `[]`*(x: VNode; idx: int): VNode = x.kids[idx]
|
||||||
|
proc `[]=`*(x: VNode; idx: int; y: VNode) = x.kids[idx] = y
|
||||||
proc add*(parent, kid: VNode) = parent.kids.add kid
|
proc add*(parent, kid: VNode) = parent.kids.add kid
|
||||||
proc newVNode*(kind: VNodeKind): VNode = VNode(kind: kind, key: -1)
|
proc newVNode*(kind: VNodeKind): VNode = VNode(kind: kind, key: -1)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue