more reactive stuff works now
This commit is contained in:
parent
9fce711bd6
commit
2482c772f8
4 changed files with 94 additions and 62 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils, reactive
|
import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils, reactive
|
||||||
|
|
||||||
proc newTextInput*(text: RString; focus: RBool): VNode =
|
proc newTextInput*(text: RString; focus: RBool): VNode {.track.} =
|
||||||
proc onFlip(ev: Event; target: VNode) =
|
proc onFlip(ev: Event; target: VNode) =
|
||||||
focus <- not focus.value
|
focus <- not focus.value
|
||||||
|
|
||||||
|
|
@ -23,20 +23,22 @@ var
|
||||||
type
|
type
|
||||||
User = ref object of ReactiveBase
|
User = ref object of ReactiveBase
|
||||||
firstname, lastname: cstring
|
firstname, lastname: cstring
|
||||||
|
selected: bool
|
||||||
|
|
||||||
var gu = newRSeq(@[ (User(firstname: "Some", lastname: "Body")),
|
var gu = newRSeq(@[ (User(firstname: "Some", lastname: "Body")),
|
||||||
(User(firstname: "Some", lastname: "One")),
|
(User(firstname: "Some", lastname: "One")),
|
||||||
(User(firstname: "Some", lastname: "Two"))])
|
(User(firstname: "Some", lastname: "Two"))])
|
||||||
var selected = newReactive[User](nil)
|
var prevSelected: User = nil #newReactive[User](nil)
|
||||||
|
|
||||||
proc toUI*(): RString =
|
proc toUI*(): RString =
|
||||||
result = RString()
|
result = RString()
|
||||||
result.subscribe proc (v: cstring) =
|
result.subscribe proc (v: cstring) =
|
||||||
if v.len > 0:
|
if v.len > 0:
|
||||||
if selected >= 0:
|
let p = prevSelected #selected.value
|
||||||
|
if p != nil:
|
||||||
# XXX what's happening here?
|
# XXX what's happening here?
|
||||||
persons[selected].firstname = v
|
p.firstname = v
|
||||||
notifyObservers(persons[selected])
|
notifyObservers(p)
|
||||||
errmsg <- ""
|
errmsg <- ""
|
||||||
else:
|
else:
|
||||||
errmsg <- "name must not be empty"
|
errmsg <- "name must not be empty"
|
||||||
|
|
@ -76,20 +78,31 @@ discard """
|
||||||
let t = text(u.firstname & " " & u.lastname)
|
let t = text(u.firstname & " " & u.lastname)
|
||||||
observe(u, t.update(u.firstname & " " & u.lastname))
|
observe(u, t.update(u.firstname & " " & u.lastname))
|
||||||
t
|
t
|
||||||
"""
|
|
||||||
|
|
||||||
template observe(s: cstring): RString =
|
template observe(s: cstring): RString =
|
||||||
let tmp = rstr(s)
|
let tmp = rstr(s)
|
||||||
u.subscribeSelf proc () =
|
u.subscribeSelf proc () =
|
||||||
tmp <- s
|
tmp <- s
|
||||||
temp
|
temp
|
||||||
|
"""
|
||||||
|
|
||||||
proc renderUser(u: User; selected: RBool): VNode =
|
proc renderUser(u: User): VNode {.track.} =
|
||||||
result = buildHtml(tdiv):
|
result = buildHtml(tdiv):
|
||||||
let displayName = observe(u.firstname & " " & u.lastname)
|
let displayName = u.firstname & " " & u.lastname
|
||||||
if selected == u:
|
if u.selected:
|
||||||
newTextInput displayName, focus
|
# == selected.value:
|
||||||
|
!(inp <- displayName)
|
||||||
|
newTextInput inp, focus
|
||||||
else:
|
else:
|
||||||
|
button:
|
||||||
|
text "..."
|
||||||
|
proc onclick(ev: Event; n: VNode) =
|
||||||
|
if prevSelected != nil:
|
||||||
|
prevSelected.selected = false
|
||||||
|
notifyObservers(prevSelected)
|
||||||
|
u.selected = true
|
||||||
|
notifyObservers(u)
|
||||||
|
prevSelected = u
|
||||||
text displayName
|
text displayName
|
||||||
button:
|
button:
|
||||||
text "(x)"
|
text "(x)"
|
||||||
|
|
@ -103,6 +116,25 @@ template vmap(x: RSeq; elem, f: untyped): VNode =
|
||||||
doTrackResize(x, tmp, f(x[pos]))
|
doTrackResize(x, tmp, f(x[pos]))
|
||||||
tmp
|
tmp
|
||||||
|
|
||||||
|
template vmapIt(x: RSeq; elem, call: untyped): VNode =
|
||||||
|
var it {.inject}: type(x[0])
|
||||||
|
let tmp = buildHtml(elem):
|
||||||
|
for i in 0..<len(x):
|
||||||
|
it = x[i]
|
||||||
|
call
|
||||||
|
doTrackResize(x, tmp, call)
|
||||||
|
tmp
|
||||||
|
|
||||||
|
proc hasNativeNode(parent, x: Node): bool =
|
||||||
|
if parent == x: return true
|
||||||
|
for i in 0..<parent.len:
|
||||||
|
if hasNativeNode(parent[i], x): return true
|
||||||
|
|
||||||
|
proc text*(s: RString): VNode =
|
||||||
|
result = text(s.value)
|
||||||
|
s.subscribe proc(v: cstring) =
|
||||||
|
if result.dom != nil: result.dom.nodeValue = v
|
||||||
|
|
||||||
proc main(gu: RSeq[User]): VNode =
|
proc main(gu: RSeq[User]): VNode =
|
||||||
result = buildHtml(tdiv):
|
result = buildHtml(tdiv):
|
||||||
tdiv:
|
tdiv:
|
||||||
|
|
@ -113,8 +145,7 @@ proc main(gu: RSeq[User]): VNode =
|
||||||
gu.add User(firstname: "Added", lastname: &clicks)
|
gu.add User(firstname: "Added", lastname: &clicks)
|
||||||
tdiv:
|
tdiv:
|
||||||
text errmsg
|
text errmsg
|
||||||
vmap(gu, tdiv, renderUser)
|
vmapIt(gu, tdiv, renderUser(it))
|
||||||
|
|
||||||
|
|
||||||
proc init(): VNode = main(gu)
|
proc init(): VNode = main(gu)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ proc same(n: VNode, e: Node; nesting = 0): bool =
|
||||||
result = true
|
result = true
|
||||||
if n.kind != VNodeKind.text:
|
if n.kind != VNodeKind.text:
|
||||||
if e.len != n.len:
|
if e.len != n.len:
|
||||||
kout e.len, n.len, toTag[n.kind], nesting
|
echo "expected ", e.len, " real ", n.len, toTag[n.kind], " nesting ", nesting
|
||||||
return false
|
return false
|
||||||
for i in 0 ..< n.len:
|
for i in 0 ..< n.len:
|
||||||
if not same(n[i], e[i], nesting+1): return false
|
if not same(n[i], e[i], nesting+1): return false
|
||||||
|
|
@ -520,6 +520,7 @@ proc runDel*(kxi: KaraxInstance; parent: VNode; position: int) =
|
||||||
kxi.addPatch(pkRemove, current, current.childNodes[position], nil)
|
kxi.addPatch(pkRemove, current, current.childNodes[position], nil)
|
||||||
parent.delete(position)
|
parent.delete(position)
|
||||||
applyPatch(kxi)
|
applyPatch(kxi)
|
||||||
|
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
||||||
|
|
||||||
proc runIns*(kxi: KaraxInstance; parent, kid: VNode; position: int) =
|
proc runIns*(kxi: KaraxInstance; parent, kid: VNode; position: int) =
|
||||||
let current = parent.dom
|
let current = parent.dom
|
||||||
|
|
@ -531,14 +532,18 @@ proc runIns*(kxi: KaraxInstance; parent, kid: VNode; position: int) =
|
||||||
kxi.addPatch(pkInsertBefore, current, before, kid)
|
kxi.addPatch(pkInsertBefore, current, before, kid)
|
||||||
parent.insert(kid, position)
|
parent.insert(kid, position)
|
||||||
applyPatch(kxi)
|
applyPatch(kxi)
|
||||||
|
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
||||||
|
|
||||||
proc runDiff*(kxi: KaraxInstance; oldNode, newNode: VNode) =
|
proc runDiff*(kxi: KaraxInstance; oldNode, newNode: VNode) =
|
||||||
let olddom = oldNode.dom
|
let olddom = oldNode.dom
|
||||||
discard diff(newNode, oldNode, nil, olddom, kxi)
|
doAssert olddom != nil
|
||||||
|
if diff(newNode, oldNode, nil, olddom, kxi) == usenewNode:
|
||||||
|
takeOverFields(newNode, oldNode)
|
||||||
applyComponents(kxi)
|
applyComponents(kxi)
|
||||||
applyPatch(kxi)
|
applyPatch(kxi)
|
||||||
if kxi.currentTree == oldNode:
|
if kxi.currentTree == oldNode:
|
||||||
kxi.currentTree = newNode
|
kxi.currentTree = newNode
|
||||||
|
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
||||||
|
|
||||||
proc dodraw(kxi: KaraxInstance) =
|
proc dodraw(kxi: KaraxInstance) =
|
||||||
if kxi.renderer.isNil: return
|
if kxi.renderer.isNil: return
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
|
|
||||||
import jdict
|
|
||||||
|
|
||||||
type
|
type
|
||||||
Message = enum
|
Message = enum
|
||||||
Unchanged
|
Unchanged
|
||||||
|
|
@ -19,7 +17,7 @@ type
|
||||||
ReactiveBase* = ref object of RootObj ## everything that is a "reactive"
|
ReactiveBase* = ref object of RootObj ## everything that is a "reactive"
|
||||||
## value derives from that
|
## value derives from that
|
||||||
sinks*: seq[proc(msg: Message, pos: int)]
|
sinks*: seq[proc(msg: Message, pos: int)]
|
||||||
dups: JDict[cstring, bool] # prevent duplicate registrations
|
id: int
|
||||||
Reactive*[T] = ref object of ReactiveBase
|
Reactive*[T] = ref object of ReactiveBase
|
||||||
value*: T
|
value*: T
|
||||||
|
|
||||||
|
|
@ -33,14 +31,16 @@ type
|
||||||
s: seq[T]
|
s: seq[T]
|
||||||
L: RInt
|
L: RInt
|
||||||
|
|
||||||
proc addSink(x: ReactiveBase; sink: proc(msg: Message; pos: int)) =
|
var rid: int
|
||||||
x.sinks.add sink
|
|
||||||
|
|
||||||
proc addSink(x: ReactiveBase; key: cstring; sink: proc(msg: Message; pos: int)) =
|
proc addSink(x: ReactiveBase; sink: proc(msg: Message; pos: int)) =
|
||||||
if x.dups == nil: x.dups = newJDict[cstring, bool]()
|
#if x.sinks.len > 0:
|
||||||
if not x.dups.contains(key):
|
# x.sinks[0] = sink
|
||||||
x.dups[key] = true
|
#else:
|
||||||
x.sinks.add sink
|
x.sinks.add sink
|
||||||
|
if x.id == 0:
|
||||||
|
inc rid
|
||||||
|
x.id = rid
|
||||||
|
|
||||||
proc broadcast(x: ReactiveBase, msg: Message; pos = 0) =
|
proc broadcast(x: ReactiveBase, msg: Message; pos = 0) =
|
||||||
for s in x.sinks: s(msg, pos)
|
for s in x.sinks: s(msg, pos)
|
||||||
|
|
@ -93,7 +93,7 @@ proc notifyObservers*(x: ReactiveBase) =
|
||||||
x.broadcast(Mark)
|
x.broadcast(Mark)
|
||||||
x.broadcast(Changed)
|
x.broadcast(Changed)
|
||||||
|
|
||||||
proc subscribeVal*[T](x: Reactive[T], f: proc(x: T)) =
|
proc subscribe*[T](x: Reactive[T], f: proc(x: T)) =
|
||||||
let reactor = proc (msg: Message, pos: int) =
|
let reactor = proc (msg: Message, pos: int) =
|
||||||
case msg:
|
case msg:
|
||||||
of Mark: discard
|
of Mark: discard
|
||||||
|
|
@ -186,11 +186,10 @@ proc map*[T, U](x: RSeq[T], f: proc(x: T): U): RSeq[U] =
|
||||||
|
|
||||||
import macros
|
import macros
|
||||||
|
|
||||||
template trackImpl(r: ReactiveBase; key: cstring; a, b: untyped) =
|
template trackImpl(r: ReactiveBase; a, b: untyped) =
|
||||||
when r is ReactiveBase:
|
when r is ReactiveBase:
|
||||||
addSink r, key, proc(m: Message; pos: int) =
|
addSink r, proc(m: Message; pos: int) =
|
||||||
if m == Changed:
|
if m == Changed: karax.runDiff(kxi, a, b)
|
||||||
runDiff(kxi, a, b)
|
|
||||||
|
|
||||||
template doTrack*(r: ReactiveBase; a, b: untyped) {.dirty.} =
|
template doTrack*(r: ReactiveBase; a, b: untyped) {.dirty.} =
|
||||||
bind addSink, Message, RSeq, Changed, Deleted, Inserted
|
bind addSink, Message, RSeq, Changed, Deleted, Inserted
|
||||||
|
|
@ -207,40 +206,30 @@ template doTrackResize*(r: ReactiveBase; a, b: untyped) {.dirty.} =
|
||||||
case m
|
case m
|
||||||
of Deleted: karax.runDel(kxi, a, pos)
|
of Deleted: karax.runDel(kxi, a, pos)
|
||||||
of Inserted:
|
of Inserted:
|
||||||
|
let it {.used.} = r[pos]
|
||||||
karax.runIns(kxi, a, b, pos)
|
karax.runIns(kxi, a, b, pos)
|
||||||
else: discard
|
else: discard
|
||||||
|
|
||||||
template notifyImpl(r: untyped) =
|
|
||||||
when r is ReactiveBase:
|
|
||||||
notifyObservers(r)
|
|
||||||
|
|
||||||
proc root(n: NimNode): NimNode =
|
|
||||||
result = n
|
|
||||||
while result.kind in {nnkDotExpr, nnkBracketExpr}: result = result[0]
|
|
||||||
|
|
||||||
proc analyse(n: NimNode; paramList: seq[NimNode]): NimNode =
|
|
||||||
result = n
|
|
||||||
#if n.kind in {nnkAsgn, nnkFastAsgn}:
|
|
||||||
#
|
|
||||||
# n[0]
|
|
||||||
#else:
|
|
||||||
# recurse()
|
|
||||||
|
|
||||||
macro track*(procDef: untyped): untyped =
|
macro track*(procDef: untyped): untyped =
|
||||||
let params = params(procDef)
|
let params = params(procDef)
|
||||||
let key = lineInfo(procDef)
|
|
||||||
var call = newCall(procDef.name)
|
|
||||||
var trackings = newStmtList()
|
var trackings = newStmtList()
|
||||||
var paramList: seq[NimNode] = @[]
|
var inner = copyNimTree(procDef)
|
||||||
|
inner[0] = ident($procDef.name & "Inner")
|
||||||
|
var call = newCall(inner[0])
|
||||||
|
trackings.add inner
|
||||||
|
trackings.add newAssignment(ident"result", call)
|
||||||
|
|
||||||
for j in 1..<params.len:
|
for j in 1..<params.len:
|
||||||
let x = params[j]
|
let x = params[j]
|
||||||
expectKind x, nnkIdentDefs
|
expectKind x, nnkIdentDefs
|
||||||
for i in 0..x.len-3: paramList.add(x[i])
|
for i in 0..x.len-3:
|
||||||
|
let param = x[i]
|
||||||
|
call.add(param)
|
||||||
|
|
||||||
for b in procDef.body: trackings.add analyse(b, paramList)
|
for j in 1..<call.len:
|
||||||
for param in paramList:
|
trackings.add getAst(trackImpl(call[j], ident"result", call))
|
||||||
call.add param
|
|
||||||
trackings.add getAst(trackImpl(param, key, ident"result", call))
|
|
||||||
|
|
||||||
result = copyNimTree(procDef)
|
result = copyNimTree(procDef)
|
||||||
result.body = trackings
|
result.body = trackings
|
||||||
|
when defined(debugKaraxDsl):
|
||||||
|
echo repr result
|
||||||
|
|
|
||||||
25
src/vdom.nim
25
src/vdom.nim
|
|
@ -220,6 +220,20 @@ proc getAttr*(n: VNode; key: cstring): cstring =
|
||||||
proc takeOverAttr*(newNode, oldNode: VNode) =
|
proc takeOverAttr*(newNode, oldNode: VNode) =
|
||||||
shallowCopy oldNode.attrs, newNode.attrs
|
shallowCopy oldNode.attrs, newNode.attrs
|
||||||
|
|
||||||
|
proc takeOverFields*(newNode, oldNode: VNode) =
|
||||||
|
template take(field) =
|
||||||
|
shallowCopy oldNode.field, newNode.field
|
||||||
|
take kind
|
||||||
|
take index
|
||||||
|
take id
|
||||||
|
take class
|
||||||
|
take text
|
||||||
|
take kids
|
||||||
|
take attrs
|
||||||
|
take events
|
||||||
|
take style
|
||||||
|
take dom
|
||||||
|
|
||||||
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 `[]=`*(x: VNode; idx: int; y: VNode) = x.kids[idx] = y
|
||||||
|
|
@ -239,15 +253,8 @@ proc tree*(kind: VNodeKind; attrs: openarray[(cstring, cstring)];
|
||||||
result = tree(kind, kids)
|
result = tree(kind, kids)
|
||||||
for a in attrs: result.setAttr(a[0], a[1])
|
for a in attrs: result.setAttr(a[0], a[1])
|
||||||
|
|
||||||
proc rawtext*(s: string): VNode = VNode(kind: VNodeKind.text, text: cstring(s), index: -1)
|
proc text*(s: string): VNode = VNode(kind: VNodeKind.text, text: cstring(s), index: -1)
|
||||||
proc rawtext*(s: cstring): VNode = VNode(kind: VNodeKind.text, text: s, index: -1)
|
proc text*(s: cstring): VNode = VNode(kind: VNodeKind.text, text: s, index: -1)
|
||||||
|
|
||||||
import reactive
|
|
||||||
|
|
||||||
proc text*(s: RString): VNode =
|
|
||||||
result = VNode(kind: VNodeKind.text, text: s, index: -1)
|
|
||||||
s.subscribe proc(v: cstring) =
|
|
||||||
result.dom.value = v
|
|
||||||
|
|
||||||
iterator items*(n: VNode): VNode =
|
iterator items*(n: VNode): VNode =
|
||||||
for i in 0..<n.kids.len: yield n.kids[i]
|
for i in 0..<n.kids.len: yield n.kids[i]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue