Merge branch 'master' into devel

This commit is contained in:
Araq 2017-09-18 17:37:16 +02:00
commit a6a1d7e150
9 changed files with 620 additions and 21 deletions

120
experiments/nextgen.nim Normal file
View file

@ -0,0 +1,120 @@
import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils, reactive
proc textInput*(text: RString; focus: RBool): VNode {.track.} =
proc onFlip(ev: Event; target: VNode) =
focus <- not focus.value
proc onKeyupEnter(ev: Event; target: VNode) =
text <- target.value
proc onkeyup(ev: Event; n: VNode) =
# keep displayValue up to date, but do not tell the client yet!
text.value = n.value
result = buildHtml(input(`type`="text",
value=text.value, onblur=onFlip, onfocus=onFlip,
onkeyupenter=onkeyupenter, onkeyup=onkeyup, setFocus=focus.value))
var
errmsg = rstr("")
makeReactive:
type
User = ref object
firstname, lastname: cstring
selected: bool
var gu = newRSeq(@[ (User(rawFirstname: "Some", rawLastname: "Body")),
(User(rawFirstname: "Some", rawLastname: "One")),
(User(rawFirstname: "Some", rawLastname: "Two"))])
var prevSelected: User = nil #newReactive[User](nil)
proc unselect() =
if prevSelected != nil:
prevSelected.selected = false
prevSelected = nil
proc select(u: User) =
unselect()
u.selected = true
prevSelected = u
proc toUI*(isFirstname: bool): RString =
result = RString()
result.subscribe proc (v: cstring) =
if v.len > 0:
let p = prevSelected #selected.value
if p != nil:
if isFirstName:
p.firstname = v
else:
p.lastname = v
unselect()
errmsg <- ""
else:
errmsg <- "name must not be empty"
var inpFirstname = toUI(true)
var inpLastname = toUI(false)
proc adaptFocus(def = false): RBool =
result = RBool()
result.value = def
when false:
result.subscribe proc (hasFocus: bool) =
if not hasFocus:
unselect()
var focusA = adaptFocus()
var focusB = adaptFocus()
proc styler(): VStyle =
result = style(
(StyleAttr.position, cstring"relative"),
(StyleAttr.paddingLeft, cstring"10px"),
(StyleAttr.paddingRight, cstring"5px"),
(StyleAttr.height, cstring"30px"),
(StyleAttr.lineHeight, cstring"30px"),
(StyleAttr.border, cstring"solid 8px " & (if focusA.value: cstring"red" else: cstring"black")),
(StyleAttr.fontSize, cstring"12px"),
(StyleAttr.fontWeight, cstring"600")
)
var clicks = 0
proc renderUser(u: User): VNode {.track.} =
result = buildHtml(tdiv):
if u.selected:
inpFirstname <- u.firstname
tdiv:
textInput inpFirstname, focusA
inpLastname <- u.lastname
tdiv:
textInput inpLastname, focusB
else:
button:
text "..."
proc onclick(ev: Event; n: VNode) =
select(u)
text u.firstname & " " & u.lastname
button:
text "(x)"
proc onclick(ev: Event; n: VNode) =
gu.deleteElem(u)
proc main(gu: RSeq[User]): VNode =
result = buildHtml(tdiv):
tdiv:
button:
text "Add User"
proc onclick(ev: Event; n: VNode) =
inc clicks
gu.add User(rawFirstname: "Added", rawLastname: &clicks)
tdiv:
text errmsg
vmapIt(gu, tdiv, renderUser(it))
proc init(): VNode = main(gu)
setInitializer(init)

View file

@ -1 +0,0 @@
--path: "../src"

View file

@ -23,7 +23,7 @@ proc ajax(meth, url: cstring; headers: openarray[(cstring, cstring)];
proc statechange(r: HttpRequest; cb: proc()) {.importcpp: "#.onreadystatechange = #".}
proc send(r: HttpRequest; data: cstring) {.importcpp: "#.send(#)".}
proc open(r: HttpRequest; meth, url: cstring; async: bool) {.importcpp: "#.open(@)".}
proc newRequest(): HttpRequest {.importcpp: "new XMLHttpRequest(@)",}
proc newRequest(): HttpRequest {.importcpp: "new XMLHttpRequest(@)".}
var this {.importc: "this".}: ThisObj
let ajax = newRequest()

View file

@ -37,6 +37,7 @@ type
patchLenV: int
runCount: int
components: seq[ComponentPair]
surpressRedraws*: bool
when defined(stats):
recursion: int
@ -74,7 +75,7 @@ proc wrapEvent(d: Node; n: VNode; k: EventKind;
let action = action
let n = n
result = proc (ev: Event) =
if n.kind == VNodeKind.textarea or n.kind == VNodeKind.input:
if n.kind == VNodeKind.textarea or n.kind == VNodeKind.input or n.kind == VNodeKind.select:
keyeventBody()
else: action(ev, n)
@ -117,7 +118,7 @@ proc applyEvents(n: VNode; kxi: KaraxInstance) =
for i in 0..<len(n.events):
n.events[i][2] = wrapEvent(dest, n, n.events[i][0], n.events[i][1])
proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
proc vnodeToDom*(n: VNode; kxi: KaraxInstance): Node =
if n.kind == VNodeKind.text:
result = document.createTextNode(n.text)
attach n
@ -134,6 +135,7 @@ proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
return result
elif n.kind == VNodeKind.component:
let x = VComponent(n)
if x.realDomImpl != nil: return x.realDomImpl(x)
if x.onAttachImpl != nil: x.onAttachImpl(x)
assert x.renderImpl != nil
if x.expanded == nil:
@ -175,16 +177,17 @@ proc same(n: VNode, e: Node; nesting = 0): bool =
result = true
if n.kind != VNodeKind.text:
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
for i in 0 ..< n.len:
if not same(n[i], e[i], nesting+1): return false
else:
kout toTag[n.kind], e.nodename
echo "VDOM: ", toTag[n.kind], " DOM: ", e.nodename
proc replaceById(id: cstring; newTree: Node) =
let x = document.getElementById(id)
x.parentNode.replaceChild(newTree, x)
newTree.id = id
type
EqResult = enum
@ -331,7 +334,8 @@ proc applyPatch(kxi: KaraxInstance) =
if n.kind == VNodeKind.component:
let x = VComponent(n)
if x.onDetachImpl != nil: x.onDetachImpl(x)
n.dom = nil
# XXX for some reason this causes assertion errors otherwise:
if not kxi.surpressRedraws: n.dom = nil
kxi.patchLen = 0
for i in 0..<kxi.patchLenV:
let p = kxi.patchesV[i]
@ -366,10 +370,11 @@ proc diff(newNode, oldNode: VNode; parent, current: Node; kxi: KaraxInstance): E
if newNode.events.len != 0 or oldNode.events.len != 0:
mergeEvents(newNode, oldNode, kxi)
if oldNode.kind == VNodeKind.input or oldNode.kind == VNodeKind.textarea:
if oldNode.text != newNode.text:
oldNode.text = newNode.text
oldNode.dom.value = newNode.text
when false:
if oldNode.kind == VNodeKind.input or oldNode.kind == VNodeKind.textarea:
if oldNode.text != newNode.text:
oldNode.text = newNode.text
oldNode.dom.value = newNode.text
let newLength = newNode.len
let oldLength = oldNode.len
@ -463,24 +468,42 @@ proc applyComponents(kxi: KaraxInstance) =
while i < kxi.components.len:
let x = kxi.components[i].oldNode
let newNode = kxi.components[i].newNode
if x.changedImpl != nil and x.changedImpl(x, newNode):
when defined(karaxDebug):
echo "Processing component ", newNode.text, " changed impl set ", x.changedImpl != nil
if x.realDomImpl != nil:
let current = kxi.components[i].current
let parent = kxi.components[i].parent
kxi.addPatch(pkReplace, parent, current, x)
elif x.changedImpl != nil and x.changedImpl(x, newNode):
when defined(karaxDebug):
echo "Component ", newNode.text, " did change"
let current = kxi.components[i].current
let parent = kxi.components[i].parent
x.updatedImpl(x, newNode)
let oldExpanded = x.expanded
x.expanded = x.renderImpl(x)
when defined(karaxDebug):
echo "Component ", newNode.text, " re-rendered"
x.renderedVersion = x.version
if oldExpanded == nil:
detach(x)
kxi.addPatch(pkReplace, parent, current, x.expanded)
when defined(karaxDebug):
echo "Component ", newNode.text, ": old expansion didn't exist"
else:
let res = diff(x.expanded, oldExpanded, parent, current, kxi)
if res == usenewNode:
when defined(karaxDebug):
echo "Component ", newNode.text, ": re-render triggered a DOM change (case A)"
discard "diff created a patchset for us, so this is fine"
elif res != different:
when defined(karaxDebug):
echo "Component ", newNode.text, ": re-render triggered no DOM change whatsoever"
x.expanded = oldExpanded
assert oldExpanded.dom != nil, "old expanded.dom is nil"
else:
when defined(karaxDebug):
echo "Component ", newNode.text, ": re-render triggered a DOM change (case B)"
assert x.expanded.dom != nil, "expanded.dom is nil"
inc i
setLen(kxi.components, 0)
@ -493,6 +516,40 @@ when defined(stats):
result = m + 1
inc total
proc runDel*(kxi: KaraxInstance; parent: VNode; position: int) =
detach(parent[position])
let current = parent.dom
kxi.addPatch(pkRemove, current, current.childNodes[position], nil)
parent.delete(position)
applyPatch(kxi)
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
proc runIns*(kxi: KaraxInstance; parent, kid: VNode; position: int) =
let current = parent.dom
if position >= parent.len:
kxi.addPatch(pkAppend, current, nil, kid)
parent.add(kid)
else:
let before = current.childNodes[position]
kxi.addPatch(pkInsertBefore, current, before, kid)
parent.insert(kid, position)
applyPatch(kxi)
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
proc runDiff*(kxi: KaraxInstance; oldNode, newNode: VNode) =
let olddom = oldNode.dom
doAssert olddom != nil
discard diff(newNode, oldNode, nil, olddom, kxi)
# this is a bit nasty: Since we cannot patch the 'parent' of
# the current VNode (because we don't store it at all!), we
# need to override the fields individually:
takeOverFields(newNode, oldNode)
applyComponents(kxi)
applyPatch(kxi)
if kxi.currentTree == oldNode:
kxi.currentTree = newNode
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
proc dodraw(kxi: KaraxInstance) =
if kxi.renderer.isNil: return
let newtree = kxi.renderer()
@ -562,6 +619,18 @@ proc setRenderer*(renderer: proc (): VNode, root: cstring = "ROOT",
kxi = result
window.onload = init
proc setInitializer*(renderer: proc (): VNode, root: cstring = "ROOT",
clientPostRenderCallback: proc () = nil): KaraxInstance {.discardable.} =
## Setup Karax. Usually the return value can be ignored.
result = KaraxInstance(rootId: root, renderer: renderer,
postRenderCallback: clientPostRenderCallback,
patches: newSeq[Patch](60),
patchesV: newSeq[PatchV](30),
components: @[],
surpressRedraws: true)
kxi = result
window.onload = init
proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler;
kxi: KaraxInstance = kxi) =
## Implements the foundation of Karax's event management.
@ -571,7 +640,19 @@ proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler;
## a ``redraw``.
proc wrapper(ev: Event; n: VNode) =
action(ev, n)
redraw(kxi)
if not kxi.surpressRedraws: redraw(kxi)
addEventListener(n, k, wrapper)
proc addEventHandler*(n: VNode; k: EventKind; action: proc();
kxi: KaraxInstance = kxi) =
## Implements the foundation of Karax's event management.
## Karax DSL transforms ``tag(onEvent = handler)`` to
## ``tempNode.addEventHandler(tagNode, EventKind.onEvent, wrapper)``
## where ``wrapper`` calls the passed ``action`` and then triggers
## a ``redraw``.
proc wrapper(ev: Event; n: VNode) =
action()
if not kxi.surpressRedraws: redraw(kxi)
addEventListener(n, k, wrapper)
proc setOnHashChange*(action: proc (hashPart: cstring)) =

View file

@ -74,7 +74,7 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
result = n
of nnkVarSection, nnkLetSection, nnkConstSection:
result = n
of nnkCallKinds:
of nnkCallKinds - {nnkInfix}:
let op = getName(n[0])
let ck = isComponent(op)
if ck != ComponentKind.None:

View file

@ -583,7 +583,7 @@ proc javaEnabled*(h: Navigator): bool
# ClassList "methods"
proc add*(c: ClassList, class: cstring)
proc remove*(c: ClassList, class: cstring)
proc contains*(c: ClassList, class: cstring):bool
proc contains*(c: ClassList, class: cstring): bool
proc toggle*(c: ClassList, class: cstring)
# Style "methods"
@ -644,9 +644,9 @@ type
proc getBoundingClientRect*(e: Node): BoundingRect {.
importcpp: "getBoundingClientRect", nodecl.}
proc clientHeight*(): int {.
importcpp: "(window.innerHeight || document.documentElement.clientHeight)@", nodecl}
importcpp: "(window.innerHeight || document.documentElement.clientHeight)@", nodecl.}
proc clientWidth*(): int {.
importcpp: "(window.innerWidth || document.documentElement.clientWidth)@", nodecl}
importcpp: "(window.innerWidth || document.documentElement.clientWidth)@", nodecl.}
proc inViewport*(el: Node): bool =
let rect = el.getBoundingClientRect()

View file

@ -2,9 +2,9 @@
## This module contains wrappers for the HTML 5 local storage.
proc getItem*(key: cstring): cstring {.importc: "localStorage.getItem"}
proc setItem*(key, value: cstring) {.importc: "localStorage.setItem"}
proc getItem*(key: cstring): cstring {.importc: "localStorage.getItem".}
proc setItem*(key, value: cstring) {.importc: "localStorage.setItem".}
proc hasItem*(key: cstring): bool {.importcpp: "(localStorage.getItem(#) !== null)".}
proc clear*() {.importc: "localStorage.clear"}
proc clear*() {.importc: "localStorage.clear".}
proc removeItem*(key: cstring) {.importc: "localStorage.removeItem".}

380
src/reactive.nim Normal file
View file

@ -0,0 +1,380 @@
import jdict
type
Message = enum
Unchanged
Changed
Mark
Inserted
Deleted
Replaced
State = object
stale: int
outdated: bool
phantom: bool
type
SinkSeq = seq[proc(msg: Message, pos: int)]
ReactiveBase* = ref object of RootObj ## everything that is a "reactive"
## value derives from that
sinks*: SinkSeq
dups: JDict[cstring, int]
id: int
Reactive*[T] = ref object of ReactiveBase
value*: T
RString* = Reactive[cstring]
# RTime* = Reactive[Time]
RInt* = Reactive[int]
RBool* = Reactive[bool]
RSeq*[T] = ref object of ReactiveBase
s: seq[T]
L: RInt
var rid: int
proc addSink(x: ReactiveBase; key: cstring; sink: proc(msg: Message; pos: int)) =
if x.dups == nil: x.dups = newJDict[cstring, int]()
if not x.dups.contains(key):
x.dups[key] = x.sinks.len
x.sinks.add sink
if x.id == 0:
inc rid
x.id = rid
else:
# update existing entry:
x.sinks[x.dups[key]] = sink
proc addSink(x: ReactiveBase; sink: proc(msg: Message; pos: int)) =
x.sinks.add sink
if x.id == 0:
inc rid
x.id = rid
var inhibited: int
proc broadcast(x: ReactiveBase, msg: Message; pos = 0) =
if inhibited == 0:
for s in x.sinks: s(msg, pos)
var toTrack: seq[(cstring, proc (msg: Message; pos: int))] = @[]
template withTrack(key, t, body) =
toTrack.add((key, t))
body
discard toTrack.pop()
proc trackDependency*(r: ReactiveBase) =
for t in toTrack:
r.addSink t[0], t[1]
template wrapObserver(f: untyped) =
var state: State
proc helper(msg: Message; pos: int) =
case msg.kind:
of Changed, Unchanged:
state.outdated = state.outdated or (msg.kind == Changed)
dec state.stale
if state.stale == 0:
if state.outdated:
var t = f()
let thisChanged = if x.value != t: Changed else: Unchanged
x.value = t
x.broadcast(thisChanged, pos)
state.outdated = false
else:
x.broadcast(Unchanged, pos)
of Mark:
if state.stale == 0: x.broadcast(msg, pos)
inc state.stale
of Inserted, Deleted, Replaced:
dec state.stale
if state.stale == 0:
x.broadcast(msg, pos)
#state.phantom = true
helper
template glitchFree(f: untyped) =
var state: State
proc helper(msg: Message; pos: int) =
case msg.kind:
of Changed, Unchanged:
state.outdated = state.outdated or (msg.kind == Changed)
dec state.stale
if state.stale == 0:
if state.outdated:
x.value = f
x.broadcast(msg.kind, pos)
state.outdated = false
else:
x.broadcast(Unchanged, pos)
of Mark:
if state.stale == 0: x.broadcast(msg, pos)
inc state.stale
of Inserted, Deleted, Replaced:
dec state.stale
if state.stale == 0:
x.broadcast(msg, pos)
helper
proc `<-`*[T](x: Reactive[T], val: T) =
#if x.value != val:
x.value = val
x.broadcast(Mark)
x.broadcast(Changed)
proc notifyObservers*(x: ReactiveBase) =
x.broadcast(Mark)
x.broadcast(Changed)
proc subscribe*[T](x: Reactive[T], f: proc(x: T)) =
let reactor = proc (msg: Message, pos: int) =
case msg:
of Mark: discard
of Changed, Unchanged: f(x.value)
of Inserted, Deleted, Replaced: discard
x.addSink reactor
proc subscribeSelf*[T: ReactiveBase](x: T, f: proc()) =
let reactor = proc (msg: Message, pos: int) =
case msg:
of Mark: discard
of Changed, Unchanged: f()
of Inserted, Deleted, Replaced: discard
x.addSink reactor
template lift1(op: untyped) =
proc op[T](a: Reactive[T]): (proc(): T) =
result = proc(): T = op(a.now)
template lift2(op: untyped) =
proc op[T](a, b: Reactive[T]): (proc(): T) =
result = proc(): T = op(a.now, b.now)
lift1 `not`
lift2 `&`
proc newReactive*[T](x: T): Reactive[T] =
result = Reactive[T](value: x)
proc rstr*(x: cstring): RString =
result = RString(value: x)
proc newRSeq*[T](len: int = 0): RSeq[T] =
result = RSeq[T](s: newSeq[T](len), L: newReactive[int](0))
proc newRSeq*[T](data: seq[T]): RSeq[T] =
result = RSeq[T](s: newSeq[T](data.len), L: newReactive[int](0))
for i in 0..high(data):
result.s[i] = data[i]
proc `[]=`*[T](x: RSeq[T]; index: int; v: T) =
x.s[index] = v
x.broadcast(Mark)
x.broadcast(Replaced, index)
proc `[]`*[T](x: RSeq[T]; index: int): T = x.s[index]
proc len*[T](x: RSeq[T]): int = x.s.len
proc add*[T](x: RSeq[T]; y: T) =
let position = x.s.len
x.s.add(y)
x.broadcast(Mark)
x.broadcast(Inserted, position)
proc insert*[T](x: RSeq[T]; y: T; position = 0) =
x.s.insert(y, position)
x.broadcast(Mark)
x.broadcast(Inserted, position)
proc delete*[T](x: RSeq[T]; position = 0) =
x.s.delete(position)
x.broadcast(Mark)
x.broadcast(Deleted, position)
proc deleteElem*[T](x: RSeq[T]; y: T) =
var position = -1
for i in 0..<x.len:
if x[i] == y:
position = i; break
if position >= 0:
x.s.delete(position)
x.broadcast(Mark)
x.broadcast(Deleted, position)
when false:
proc `:=`[T](x: Reactive[T], f: proc(): T) =
toTrack = wrapObserver(f())
x.value = f()
toTrack = nil
proc map*[T, U](x: RSeq[T], f: proc(x: T): U): RSeq[U] =
let xl = x.L.value
let res = newRSeq[U](xl)
for i in 0..<xl:
res.s[i] := proc(): U = f(x[i].now)
let reactor = proc (msg: Message, pos: int) =
case msg:
of Mark, Changed, Unchanged: discard "nothing to do"
of Inserted:
res.insert(f(x[pos]), pos)
of Deleted:
res.delete(pos)
x.addSink reactor
result = res
import macros
template protect(body: untyped) =
#var tmp: seq[proc(msg: Message, pos: int)]
#swap(r.sinks, tmp)
inc inhibited
body
dec inhibited
#swap(r.sinks, tmp)
template trackStart(key: cstring; a, b: untyped) =
proc differ(m: Message; pos: int) =
if m == Changed:
protect:
karax.runDiff(kxi, a, b)
toTrack.add((key, differ))
template trackEnd() =
discard toTrack.pop()
template doTrack(r: ReactiveBase; a, b: untyped) {.dirty.} =
bind addSink, Message, RSeq, Changed, Deleted, Inserted
addSink r, proc(m: Message; pos: int) =
#when r is RSeq:
#echo "Message: ", m, " ", pos
if m == Changed:
protect r:
karax.runDiff(kxi, a, b)
template doTrackResize(r: ReactiveBase; a, b: untyped) {.dirty.} =
bind addSink, Message, RSeq, Changed, Deleted, Inserted
addSink r, proc(m: Message; pos: int) =
#when r is RSeq:
#echo "Message: ", m, " ", pos
case m
of Deleted: karax.runDel(kxi, a, pos)
of Inserted:
let it {.used.} = r[pos]
karax.runIns(kxi, a, b, pos)
else: discard
macro track*(procDef: untyped): untyped =
let params = params(procDef)
var trackings = newStmtList()
var inner = copyNimTree(procDef)
inner[0] = ident($procDef.name & "Inner")
var call = newCall(inner[0])
for j in 1..<params.len:
let x = params[j]
expectKind x, nnkIdentDefs
for i in 0..x.len-3:
let param = x[i]
call.add(param)
trackings.add inner
let key = newCall("cstring", newLit lineInfo(procDef))
trackings.add getAst(trackStart(key, ident"result", call))
trackings.add newAssignment(ident"result", call)
trackings.add getAst(trackEnd())
result = copyNimTree(procDef)
result.body = trackings
when defined(debugKaraxDsl):
echo repr result
proc generatePrivateAccessors(name, hidden, typ, fieldTyp: NimNode): NimNode =
template helper(name, hidden, typ, fieldTyp) {.dirty.} =
proc name(self: typ): fieldTyp =
trackDependency(self)
result = self.hidden
proc `name=`(self: typ; val: fieldTyp) =
self.hidden = val
notifyObservers(self)
result = getAst(helper(name, hidden, typ, fieldTyp))
proc generatePublicAccessors(name, hidden, typ, fieldTyp: NimNode): NimNode =
template helper(name, hidden, typ, fieldTyp) {.dirty.} =
proc name*(self: typ): fieldTyp =
trackDependency(self)
result = self.hidden
proc `name=`*(self: typ; val: fieldTyp) =
self.hidden = val
notifyObservers(self)
result = getAst(helper(name, hidden, typ, fieldTyp))
proc transform(n: NimNode; stmts, obj: NimNode): NimNode =
if n.kind == nnkIdentDefs and obj.kind == nnkIdent:
for i in 0..n.len-3:
let it = n[i]
let itB = if it.kind == nnkPostFix: it[1] else: it
let hidden = newIdentNode("raw" & $itB)
if it.kind == nnkPostFix:
stmts.add generatePublicAccessors(itB, hidden, obj, n[n.len-2])
else:
stmts.add generatePrivateAccessors(itB, hidden, obj, n[n.len-2])
n[i] = hidden
result = n
else:
var objB = if n.kind == nnkTypeDef: n[0] else: obj
result = copyNimNode(n)
for i in 0..<n.len:
if n.kind == nnkObjectTy and i == 1 and n[1].kind == nnkEmpty:
result.add newTree(nnkOfInherit, ident("ReactiveBase"))
else:
result.add transform(n[i], stmts, objB)
macro makeReactive*(n: untyped): untyped =
var a = newStmtList()
a.add newEmptyNode()
let t = transform(n, a, newEmptyNode())
a[0] = t
result = a
when defined(debugKaraxDsl):
echo repr result
import vdom
template vmap*(x: RSeq; elem, f: untyped): VNode =
let tmp = buildHtml(elem):
for i in 0..<len(x):
f(x[i])
doTrackResize(x, tmp, f(x[pos]))
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 text*(s: RString): VNode =
result = text(s.value)
s.subscribe proc(v: cstring) =
if result.dom != nil: result.dom.nodeValue = v
when isMainModule:
makeReactive:
type
Foo = ref object of ReactiveBase
x, y: int
Bar = ref object of ReactiveBase
z: string
exported*: int
Another = ref object
a, b: cstring
c: bool

View file

@ -133,6 +133,7 @@ type
updatedImpl*: proc(self, newInstance: VComponent)
onAttachImpl*: proc(self: VComponent)
onDetachImpl*: proc(self: VComponent)
realDomImpl*: proc(self: VComponent): kdom.Node
version*: int ## Update this to trigger a redraw by karax. Usually you
## should call 'markDirty' instead which is an alias for
## 'inc version'.
@ -185,7 +186,7 @@ proc getDebugId(): int =
gid
template newComponent*[T](t: typeDesc[T];
render: (proc(self: VComponent): VNode) not nil,
render: (proc(self: VComponent): VNode) = nil,
onAttach: proc(self: VComponent) = nil,
onDetach: proc(self: VComponent) = nil,
changed: (proc(self, newInstance: VComponent): bool) = defaultChangedImpl,
@ -219,10 +220,28 @@ proc getAttr*(n: VNode; key: cstring): cstring =
proc takeOverAttr*(newNode, oldNode: VNode) =
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 `[]`*(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 delete*(parent: VNode; position: int) =
parent.kids.delete(position)
proc insert*(parent, kid: VNode; position: int) =
parent.kids.insert(kid, position)
proc newVNode*(kind: VNodeKind): VNode = VNode(kind: kind, index: -1)
proc tree*(kind: VNodeKind; kids: varargs[VNode]): VNode =