new component example works

This commit is contained in:
Andreas Rumpf 2017-06-19 21:25:34 +02:00
commit 4801b8d8c1
4 changed files with 100 additions and 64 deletions

View file

@ -2,59 +2,68 @@
import vdom, vstyles, karax, karaxdsl, jdict, jstrutils, kdom
type
Car = ref object of VComponent
Carousel = ref object of VComponent
counter: int
cntdown: int
cntdown, myid: int
timer: TimeOut
list: seq[cstring]
change: bool
onclick: proc (ev: Event; n: VNode)
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
images: seq[cstring] = @[cstring"a", "b", "c", "d"]
proc carousel*(): VNode =
let c = car(images)
proc render(x: VComponent): VNode =
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 =
case c.counter
case self.counter
of 0: cstring"#4d4d4d"
of 1: cstring"#ff00ff"
of 2: cstring"#00ffff"
of 3: cstring"#ffff00"
else: cstring"red"
result = buildHtml(tdiv(nref = c)):
text images[c.counter]
button(onclick = c.onclick):
result = buildHtml(tdiv()):
text self.list[self.counter]
button(onclick = onclick):
text "Next"
tdiv(style = style(StyleAttr.color, col)):
text "This changes its color."
if c.cntdown != ticksUntilChange:
text &c.cntdown
text "This changes its color." & &self.myid
if self.cntdown != ticksUntilChange:
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 =
result = buildHtml(table):

View file

@ -80,14 +80,11 @@ proc wrapEvent(d: Node; n: VNode; k: EventKind; action: EventHandler) =
# --------------------- DOM diff -----------------------------------------
template detach(n: VNode) =
if not n.nref.isNil():
n.nref.vnode = nil
n.nref.onDetach()
if n.kind == VNodeKind.component:
let x = VComponent(n)
if x.onDetach != nil: x.onDetach(x)
n.dom = nil
template attach(n: VNode) =
if not n.nref.isNil():
n.nref.vnode = n
n.nref.onAttach()
n.dom = result
proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
@ -105,6 +102,13 @@ proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
#n.key = result.key
attach n
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:
result = document.createElement(toTag[n.kind])
attach n
@ -150,9 +154,6 @@ proc eq(a, b: VNode; deep: bool): EqResult =
if a.id != b.id: return different
result = identical
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.text != b.text: return different
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
for i in 0..<a.len:
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:
if a.len != b.len: return different
for i in 0..<a.len:
@ -204,13 +210,23 @@ proc updateStyles(newNode, oldNode: VNode; deep: bool) =
proc updateDom(newNode, oldNode: VNode) =
newNode.dom = oldNode.dom
newNode.nref = oldNode.nref
assert newNode.len == oldNode.len
for i in 0 ..< newNode.len:
updateDom(newNode[i], oldNode[i])
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)
if res == different:
detach(oldNode)
@ -241,32 +257,35 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode;
else:
var commonPrefix = 0
template eqAndUpdate(a, b: VNode; action: untyped) =
let r = eq(a, b, true)
template eqAndUpdate(a: VNode; i: int; b: VNode; action: untyped) =
let r = eq(a[i], b, true)
case r
of identical:
updateDom(a, b)
a[i] = b
#updateDom(a, b)
action
of different: break
of similar:
updateDom(a, b)
updateStyles(a, b, true)
#updateDom(a, b)
a[i] = b
#updateStyles(a, b, true)
action
while commonPrefix < minLength:
eqAndUpdate(newNode[commonPrefix], oldNode[commonPrefix]):
eqAndUpdate(newNode, commonPrefix, oldNode[commonPrefix]):
inc commonPrefix
var oldPos = oldLength - 1
var newPos = newLength - 1
while oldPos >= commonPrefix and newPos >= commonPrefix:
eqAndUpdate(newNode[newPos], oldNode[oldPos]):
eqAndUpdate(newNode, newPos, oldNode[oldPos]):
dec oldPos
dec newPos
var pos = min(oldPos, newPos) + 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
while pos <= newPos:
@ -297,7 +316,7 @@ proc dodraw(kxi: KaraxInstance) =
replaceById(kxi.rootId, asdom)
else:
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"))
if someDirty:
updateDirtyElements(nil, olddom, newtree, kxi)
@ -363,7 +382,7 @@ proc setupErrorHandler*() =
## the error message is put into the console, otherwise `alert`
## is called.
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 =
var x = cstring"Error: " & msg & "\n" & stackTraceAsCstring()
kout(x)

View file

@ -4,7 +4,7 @@ from strutils import startsWith, toLowerAscii
const
StmtContext = ["kout", "inc", "echo", "dec", "!"]
SpecialAttrs = ["id", "class", "value", "key", "style", "nref"]
SpecialAttrs = ["id", "class", "value", "key", "style"]
proc getName(n: NimNode): string =
case n.kind

View file

@ -8,6 +8,7 @@ type
VNodeKind* {.pure.} = enum
text = "#text", int = "#int", bool = "#bool",
vthunk = "#vthunk", dthunk = "#dthunk",
component = "#component",
html, head, title, base, link, meta, style,
script, noscript,
@ -101,16 +102,13 @@ macro buildLookupTables(): untyped =
buildLookupTables()
type
VComponent* = ref object of RootObj
EventHandler* = proc (ev: Event; target: VNode) {.closure.}
VKey* = int
VNode* = ref object
VNode* = ref object of RootObj
kind*: VNodeKind
key*: VKey
id*, class*, text*: cstring
nref*: VComponent
kids: seq[VNode]
# even index: key, odd index: value; done this way for memory efficiency:
attrs: seq[cstring]
@ -121,9 +119,11 @@ type
dom*: Node ## the attached real DOM node. Can be 'nil' if the virtual node
## is not part of the virtual DOM anymore.
method onAttach*(v: VComponent) {.base.} = discard
method onDetach*(v: VComponent) {.base.} = discard
method changed*(v: VComponent): bool {.base.} = false
VComponent* = ref object of VNode
render*: proc(self: VComponent): VNode
changed*: proc(self: VComponent): bool
onAttach*: proc(self: VComponent)
onDetach*: proc(self: VComponent)
proc value*(n: VNode): cstring = n.text
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 =
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 = "") =
if n.attrs.isNil:
n.attrs = @[key, val]
@ -160,6 +167,7 @@ proc getAttr*(n: VNode; key: cstring): cstring =
proc len*(x: VNode): int = x.kids.len
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 newVNode*(kind: VNodeKind): VNode = VNode(kind: kind, key: -1)