update value property of real DOM nodes; minor DOM diff speedups; allow setFocus=false in the DSL
This commit is contained in:
parent
4199c42e3b
commit
c681f0055f
4 changed files with 67 additions and 38 deletions
|
|
@ -17,7 +17,7 @@ type
|
||||||
proc `[]`*[T](s: JSeq[T], i: int): T {.importcpp: "#[#]", noSideEffect.}
|
proc `[]`*[T](s: JSeq[T], i: int): T {.importcpp: "#[#]", noSideEffect.}
|
||||||
proc `[]=`*[T](s: JSeq[T], i: int, v: T) {.importcpp: "#[#] = #", noSideEffect.}
|
proc `[]=`*[T](s: JSeq[T], i: int, v: T) {.importcpp: "#[#] = #", noSideEffect.}
|
||||||
|
|
||||||
proc newJSeq*[T](): JSeq[T] {.importcpp: "[@]".}
|
proc newJSeq*[T](len: int = 0): JSeq[T] {.importcpp: "new Array(#)".}
|
||||||
proc len*[T](s: JSeq[T]): int {.importcpp: "#.length", noSideEffect.}
|
proc len*[T](s: JSeq[T]): int {.importcpp: "#.length", noSideEffect.}
|
||||||
proc add*[T](s: JSeq[T]; x: T) {.importcpp: "#.push(#)", noSideEffect.}
|
proc add*[T](s: JSeq[T]; x: T) {.importcpp: "#.push(#)", noSideEffect.}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,14 @@ proc key*(e: Node): VKey =
|
||||||
else: result = -1
|
else: result = -1
|
||||||
proc `key=`*(e: Node; x: VKey) {.importcpp: "#.karaxKey = #", nodecl.}
|
proc `key=`*(e: Node; x: VKey) {.importcpp: "#.karaxKey = #", nodecl.}
|
||||||
|
|
||||||
|
type
|
||||||
|
PatchKind = enum
|
||||||
|
pkReplace, pkRemove, pkAppend, pkInsertBefore
|
||||||
|
Patch = object
|
||||||
|
k: PatchKind
|
||||||
|
parent, current: Node
|
||||||
|
n: VNode
|
||||||
|
|
||||||
type
|
type
|
||||||
KaraxInstance* = ref object ## underlying karax instance. Usually you don't have
|
KaraxInstance* = ref object ## underlying karax instance. Usually you don't have
|
||||||
## know about this.
|
## know about this.
|
||||||
|
|
@ -24,6 +32,9 @@ type
|
||||||
toFocus: Node
|
toFocus: Node
|
||||||
toFocusV: VNode
|
toFocusV: VNode
|
||||||
renderId: int
|
renderId: int
|
||||||
|
patches: seq[Patch] # we reuse this to save allocations
|
||||||
|
patchLen: int
|
||||||
|
|
||||||
|
|
||||||
var
|
var
|
||||||
kxi*: KaraxInstance ## The current Karax instance. This is always used
|
kxi*: KaraxInstance ## The current Karax instance. This is always used
|
||||||
|
|
@ -33,7 +44,8 @@ var
|
||||||
## in your 'buildHtml' statement, it needs to be named
|
## in your 'buildHtml' statement, it needs to be named
|
||||||
## 'kxi'.
|
## 'kxi'.
|
||||||
|
|
||||||
proc setFocus*(n: VNode; kxi: KaraxInstance = kxi) =
|
proc setFocus*(n: VNode; enabled = true; kxi: KaraxInstance = kxi) =
|
||||||
|
if enabled:
|
||||||
kxi.toFocusV = n
|
kxi.toFocusV = n
|
||||||
|
|
||||||
# ----------------- event wrapping ---------------------------------------
|
# ----------------- event wrapping ---------------------------------------
|
||||||
|
|
@ -199,7 +211,7 @@ proc updateStyles(newNode, oldNode: VNode) =
|
||||||
else: oldNode.dom.style = Style()
|
else: oldNode.dom.style = Style()
|
||||||
oldNode.style = newNode.style
|
oldNode.style = newNode.style
|
||||||
|
|
||||||
proc mergeEvents(newNode, oldNode: VNode) =
|
proc mergeEvents(newNode, oldNode: VNode; kxi: KaraxInstance) =
|
||||||
let d = oldNode.dom
|
let d = oldNode.dom
|
||||||
for i in 0..<oldNode.events.len:
|
for i in 0..<oldNode.events.len:
|
||||||
let k = oldNode.events[i][0]
|
let k = oldNode.events[i][0]
|
||||||
|
|
@ -222,21 +234,22 @@ proc printV(n: VNode; depth: cstring = "") =
|
||||||
for i in 0 ..< n.len:
|
for i in 0 ..< n.len:
|
||||||
printV(n[i], depth & " ")
|
printV(n[i], depth & " ")
|
||||||
|
|
||||||
type
|
template addPatch(kxi: KaraxInstance; ka: PatchKind; parenta, currenta: Node;
|
||||||
PatchKind = enum
|
na: VNode) =
|
||||||
pkReplace, pkRemove, pkAppend, pkInsertBefore
|
let L = kxi.patchLen
|
||||||
Patch = object
|
if L >= kxi.patches.len:
|
||||||
k: PatchKind
|
# allocate more space:
|
||||||
parent, current: Node
|
kxi.patches.add(Patch(k: ka, parent: parenta, current: currenta, n: na))
|
||||||
n: VNode
|
else:
|
||||||
|
kxi.patches[L].k = ka
|
||||||
|
kxi.patches[L].parent = parenta
|
||||||
|
kxi.patches[L].current = currenta
|
||||||
|
kxi.patches[L].n = na
|
||||||
|
inc kxi.patchLen
|
||||||
|
|
||||||
proc addPatch(patches: JSeq[Patch]; k: PatchKind; parent, current: Node;
|
proc apply(kxi: KaraxInstance) =
|
||||||
n: VNode) =
|
for i in 0..<kxi.patchLen:
|
||||||
patches.add(Patch(k: k, parent: parent, current: current, n: n))
|
let p = kxi.patches[i]
|
||||||
|
|
||||||
proc apply(patches: JSeq[Patch]; kxi: KaraxInstance) =
|
|
||||||
for i in 0..<patches.len:
|
|
||||||
let p = patches[i]
|
|
||||||
case p.k
|
case p.k
|
||||||
of pkReplace:
|
of pkReplace:
|
||||||
let nn = vnodeToDom(p.n, kxi)
|
let nn = vnodeToDom(p.n, kxi)
|
||||||
|
|
@ -252,15 +265,18 @@ proc apply(patches: JSeq[Patch]; kxi: KaraxInstance) =
|
||||||
of pkInsertBefore:
|
of pkInsertBefore:
|
||||||
let nn = vnodeToDom(p.n, kxi)
|
let nn = vnodeToDom(p.n, kxi)
|
||||||
p.parent.insertBefore(nn, p.current)
|
p.parent.insertBefore(nn, p.current)
|
||||||
|
kxi.patchLen = 0
|
||||||
|
|
||||||
proc diff(newNode, oldNode: VNode;parent, current: Node; patches: JSeq[Patch]): EqResult =
|
proc diff(newNode, oldNode: VNode; parent, current: Node; kxi: KaraxInstance): EqResult =
|
||||||
result = eq(newNode, oldNode)
|
result = eq(newNode, oldNode)
|
||||||
case result
|
case result
|
||||||
of identical, similar:
|
of identical, similar:
|
||||||
newNode.dom = oldNode.dom
|
newNode.dom = oldNode.dom
|
||||||
if result == similar: updateStyles(newNode, oldNode)
|
if result == similar: updateStyles(newNode, oldNode)
|
||||||
if newNode.events.len != 0 or oldNode.events.len != 0:
|
if newNode.events.len != 0 or oldNode.events.len != 0:
|
||||||
mergeEvents(newNode, oldNode)
|
mergeEvents(newNode, oldNode, kxi)
|
||||||
|
if newNode.kind == VNodeKind.input or newNode.kind == VNodeKind.textarea:
|
||||||
|
oldNode.dom.value = newNode.text
|
||||||
|
|
||||||
let newLength = newNode.len
|
let newLength = newNode.len
|
||||||
var oldLength = oldNode.len
|
var oldLength = oldNode.len
|
||||||
|
|
@ -271,7 +287,7 @@ proc diff(newNode, oldNode: VNode;parent, current: Node; patches: JSeq[Patch]):
|
||||||
var commonPrefix = 0
|
var commonPrefix = 0
|
||||||
|
|
||||||
template eqAndUpdate(a: VNode; i: int; b: VNode; j: int; info, action: untyped) =
|
template eqAndUpdate(a: VNode; i: int; b: VNode; j: int; info, action: untyped) =
|
||||||
let oldLen = patches.len
|
let oldLen = kxi.patchLen
|
||||||
when false:
|
when false:
|
||||||
if oldNode.kind notin {VNodeKind.component, VNodeKind.vthunk, VNodeKind.dthunk}:
|
if oldNode.kind notin {VNodeKind.component, VNodeKind.vthunk, VNodeKind.dthunk}:
|
||||||
assert current != nil
|
assert current != nil
|
||||||
|
|
@ -280,16 +296,16 @@ proc diff(newNode, oldNode: VNode;parent, current: Node; patches: JSeq[Patch]):
|
||||||
|
|
||||||
let r = if oldNode.kind == VNodeKind.component or oldNode.kind == VNodeKind.vthunk or
|
let r = if oldNode.kind == VNodeKind.component or oldNode.kind == VNodeKind.vthunk or
|
||||||
oldNode.kind == VNodeKind.dthunk:
|
oldNode.kind == VNodeKind.dthunk:
|
||||||
diff(a[i], b[j], parent, current, patches)
|
diff(a[i], b[j], parent, current, kxi)
|
||||||
else:
|
else:
|
||||||
diff(a[i], b[j], current, current.childNodes[j], patches)
|
diff(a[i], b[j], current, current.childNodes[j], kxi)
|
||||||
case r
|
case r
|
||||||
of identical, changed, similar:
|
of identical, changed, similar:
|
||||||
a[i] = b[j]
|
a[i] = b[j]
|
||||||
action
|
action
|
||||||
of different:
|
of different:
|
||||||
# undo what 'diff' would have done:
|
# undo what 'diff' would have done:
|
||||||
shrink(patches, oldLen)
|
kxi.patchLen = oldLen
|
||||||
if result != different: result = r
|
if result != different: result = r
|
||||||
break
|
break
|
||||||
#of similar:
|
#of similar:
|
||||||
|
|
@ -311,25 +327,25 @@ proc diff(newNode, oldNode: VNode;parent, current: Node; patches: JSeq[Patch]):
|
||||||
var pos = min(oldPos, newPos) + 1
|
var pos = min(oldPos, newPos) + 1
|
||||||
for i in commonPrefix..pos-1:
|
for i in commonPrefix..pos-1:
|
||||||
if diff(newNode[i], oldNode[i], current, current.childNodes[i],
|
if diff(newNode[i], oldNode[i], current, current.childNodes[i],
|
||||||
patches) != different:
|
kxi) != different:
|
||||||
newNode[i] = oldNode[i]
|
newNode[i] = oldNode[i]
|
||||||
else:
|
else:
|
||||||
result = different
|
result = different
|
||||||
|
|
||||||
if oldPos + 1 == oldLength:
|
if oldPos + 1 == oldLength:
|
||||||
for i in pos..newPos:
|
for i in pos..newPos:
|
||||||
patches.addPatch(pkAppend, current, nil, newNode[i])
|
kxi.addPatch(pkAppend, current, nil, newNode[i])
|
||||||
result = different
|
result = different
|
||||||
else:
|
else:
|
||||||
let before = current.childNodes[oldPos + 1]
|
let before = current.childNodes[oldPos + 1]
|
||||||
for i in pos..newPos:
|
for i in pos..newPos:
|
||||||
patches.addPatch(pkInsertBefore, current, before, newNode[i])
|
kxi.addPatch(pkInsertBefore, current, before, newNode[i])
|
||||||
result = different
|
result = different
|
||||||
# XXX call 'attach' here?
|
# XXX call 'attach' here?
|
||||||
for i in pos..oldPos:
|
for i in pos..oldPos:
|
||||||
detach(oldNode[i])
|
detach(oldNode[i])
|
||||||
#doAssert i < current.childNodes.len
|
#doAssert i < current.childNodes.len
|
||||||
patches.addPatch(pkRemove, current, current.childNodes[i], nil)
|
kxi.addPatch(pkRemove, current, current.childNodes[i], nil)
|
||||||
result = different
|
result = different
|
||||||
|
|
||||||
of changed:
|
of changed:
|
||||||
|
|
@ -340,9 +356,9 @@ proc diff(newNode, oldNode: VNode;parent, current: Node; patches: JSeq[Patch]):
|
||||||
x.updatedImpl(x)
|
x.updatedImpl(x)
|
||||||
if oldExpanded == nil:
|
if oldExpanded == nil:
|
||||||
detach(oldNode)
|
detach(oldNode)
|
||||||
patches.addPatch(pkReplace, parent, current, x.expanded)
|
kxi.addPatch(pkReplace, parent, current, x.expanded)
|
||||||
else:
|
else:
|
||||||
let res = diff(x.expanded, oldExpanded, parent, current, patches)
|
let res = diff(x.expanded, oldExpanded, parent, current, kxi)
|
||||||
if res != different:
|
if res != different:
|
||||||
x.expanded = oldExpanded
|
x.expanded = oldExpanded
|
||||||
assert oldExpanded.dom != nil, "old expanded.dom is nil"
|
assert oldExpanded.dom != nil, "old expanded.dom is nil"
|
||||||
|
|
@ -350,7 +366,7 @@ proc diff(newNode, oldNode: VNode;parent, current: Node; patches: JSeq[Patch]):
|
||||||
assert x.expanded.dom != nil, "expanded.dom is nil"
|
assert x.expanded.dom != nil, "expanded.dom is nil"
|
||||||
of different:
|
of different:
|
||||||
detach(oldNode)
|
detach(oldNode)
|
||||||
patches.addPatch(pkReplace, parent, current, newNode)
|
kxi.addPatch(pkReplace, parent, current, newNode)
|
||||||
|
|
||||||
proc dodraw(kxi: KaraxInstance) =
|
proc dodraw(kxi: KaraxInstance) =
|
||||||
if kxi.renderer.isNil: return
|
if kxi.renderer.isNil: return
|
||||||
|
|
@ -363,9 +379,9 @@ 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)
|
||||||
var patches = newJSeq[Patch]()
|
discard diff(newtree, kxi.currentTree, nil, olddom, kxi)
|
||||||
discard diff(newtree, kxi.currentTree, nil, olddom, patches)
|
#kout cstring"patch len ", patches.len
|
||||||
patches.apply(kxi)
|
apply(kxi)
|
||||||
kxi.currentTree = newtree
|
kxi.currentTree = newtree
|
||||||
#doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
#doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
||||||
|
|
||||||
|
|
@ -375,6 +391,7 @@ proc dodraw(kxi: KaraxInstance) =
|
||||||
# now that it's part of the DOM, give it the focus:
|
# now that it's part of the DOM, give it the focus:
|
||||||
if kxi.toFocus != nil:
|
if kxi.toFocus != nil:
|
||||||
kxi.toFocus.focus()
|
kxi.toFocus.focus()
|
||||||
|
kxi.renderId = 0
|
||||||
|
|
||||||
proc reqFrame(callback: proc()): int {.importc: "window.requestAnimationFrame".}
|
proc reqFrame(callback: proc()): int {.importc: "window.requestAnimationFrame".}
|
||||||
proc cancelFrame(id: int) {.importc: "window.cancelAnimationFrame".}
|
proc cancelFrame(id: int) {.importc: "window.cancelAnimationFrame".}
|
||||||
|
|
@ -386,7 +403,7 @@ proc redraw*(kxi: KaraxInstance = kxi) =
|
||||||
clearTimeout(drawTimeout)
|
clearTimeout(drawTimeout)
|
||||||
drawTimeout = setTimeout(dodraw, 30)
|
drawTimeout = setTimeout(dodraw, 30)
|
||||||
elif true:
|
elif true:
|
||||||
cancelFrame kxi.renderId
|
if kxi.renderId == 0:
|
||||||
kxi.renderId = reqFrame(proc () = kxi.dodraw)
|
kxi.renderId = reqFrame(proc () = kxi.dodraw)
|
||||||
else:
|
else:
|
||||||
dodraw(kxi)
|
dodraw(kxi)
|
||||||
|
|
@ -400,7 +417,8 @@ proc setRenderer*(renderer: proc (): VNode, root: cstring = "ROOT",
|
||||||
clientPostRenderCallback: proc () = nil): KaraxInstance {.discardable.} =
|
clientPostRenderCallback: proc () = nil): KaraxInstance {.discardable.} =
|
||||||
## Setup Karax. Usually the return value can be ignored.
|
## Setup Karax. Usually the return value can be ignored.
|
||||||
result = KaraxInstance(rootId: root, renderer: renderer,
|
result = KaraxInstance(rootId: root, renderer: renderer,
|
||||||
postRenderCallback: clientPostRenderCallback)
|
postRenderCallback: clientPostRenderCallback,
|
||||||
|
patches: newSeq[Patch](60))
|
||||||
kxi = result
|
kxi = result
|
||||||
window.onload = init
|
window.onload = init
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,12 +85,14 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||||
tmp, newDotExpr(bindSym"EventKind", x[0]), x[1], ident("kxi"))
|
tmp, newDotExpr(bindSym"EventKind", x[0]), x[1], ident("kxi"))
|
||||||
elif key in SpecialAttrs:
|
elif key in SpecialAttrs:
|
||||||
result.add newDotAsgn(tmp, key, x[1])
|
result.add newDotAsgn(tmp, key, x[1])
|
||||||
|
elif eqIdent(key, "setFocus"):
|
||||||
|
result.add newCall(x, tmp, x[1], ident"kxi")
|
||||||
else:
|
else:
|
||||||
result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1])
|
result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1])
|
||||||
elif ck != ComponentKind.Tag:
|
elif ck != ComponentKind.Tag:
|
||||||
call.add x
|
call.add x
|
||||||
elif eqIdent(x, "setFocus"):
|
elif eqIdent(x, "setFocus"):
|
||||||
result.add newCall(x, tmp, ident"kxi")
|
result.add newCall(x, tmp, bindSym"true", ident"kxi")
|
||||||
else:
|
else:
|
||||||
result.add tcall2(x, tmp)
|
result.add tcall2(x, tmp)
|
||||||
if tmpContext == nil:
|
if tmpContext == nil:
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,18 @@ type
|
||||||
|
|
||||||
proc renderComb(self: VComponent): VNode =
|
proc renderComb(self: VComponent): VNode =
|
||||||
let self = Combined(self)
|
let self = Combined(self)
|
||||||
|
|
||||||
|
proc bu(ev: Event; n: VNode) =
|
||||||
|
self.a.value = ""
|
||||||
|
self.b.value = ""
|
||||||
|
markDirty(self.a)
|
||||||
|
markDirty(self.b)
|
||||||
|
|
||||||
result = buildHtml(tdiv(style=self.style)):
|
result = buildHtml(tdiv(style=self.style)):
|
||||||
self.a
|
self.a
|
||||||
self.b
|
self.b
|
||||||
|
button(onclick=bu):
|
||||||
|
text "reset"
|
||||||
|
|
||||||
proc changed(self: VComponent): bool =
|
proc changed(self: VComponent): bool =
|
||||||
let self = Combined(self)
|
let self = Combined(self)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue