Merge branch 'dom_diff_problem' of github.com:xored/karax into devel
This commit is contained in:
commit
0cc5e85c82
6 changed files with 289 additions and 16 deletions
19
diffDomTests.html
Normal file
19
diffDomTests.html
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Diff dom tests</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="body">
|
||||||
|
|
||||||
|
<div id="ROOT" />
|
||||||
|
|
||||||
|
<script type="text/javascript" src="nimcache/diffDomTests.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
onload(window.location.pathname.split('/')[1])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
178
diffDomTests.nim
Normal file
178
diffDomTests.nim
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
|
||||||
|
import vdom, times, karax, karaxdsl, jdict, jstrutils, parseutils, sequtils
|
||||||
|
|
||||||
|
var
|
||||||
|
entries: seq[cstring]
|
||||||
|
timeout : Timeout
|
||||||
|
|
||||||
|
proc reset() =
|
||||||
|
kout cstring"reset started"
|
||||||
|
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ]
|
||||||
|
redraw()
|
||||||
|
kout cstring"reset finished"
|
||||||
|
|
||||||
|
proc checkOrder(order : seq[int]): bool =
|
||||||
|
var ul = getElementById("ul")
|
||||||
|
if ul == nil or len(ul.children) != len(order):
|
||||||
|
return false
|
||||||
|
var pos = 0
|
||||||
|
for child in ul.children:
|
||||||
|
if child.id != $order[pos]:
|
||||||
|
return false
|
||||||
|
inc pos
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc check1() =
|
||||||
|
let result = checkOrder(@[0, 1, 2, 3, 4, 7, 5])
|
||||||
|
if result:
|
||||||
|
kout cstring"test1 - OK"
|
||||||
|
else:
|
||||||
|
kout cstring"test1 - FAIL"
|
||||||
|
|
||||||
|
# result: 0 1 2 3 4 7 5
|
||||||
|
proc test1() =
|
||||||
|
kout cstring"test1 started"
|
||||||
|
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ]
|
||||||
|
entries.insert(cstring("7"), 5)
|
||||||
|
redraw()
|
||||||
|
timeout = setTimeout(check1, 20)
|
||||||
|
|
||||||
|
proc check2() =
|
||||||
|
let result = checkOrder(@[8, 0, 1, 2, 3, 4, 7, 5])
|
||||||
|
if result:
|
||||||
|
kout cstring"test1 - OK"
|
||||||
|
else:
|
||||||
|
kout cstring"test2 - FAIL"
|
||||||
|
|
||||||
|
# result: 8 0 1 2 3 4 7 5
|
||||||
|
proc test2() =
|
||||||
|
kout cstring"test2 started"
|
||||||
|
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ]
|
||||||
|
entries.insert(cstring("7"), 5)
|
||||||
|
entries.insert(cstring("8"), 0)
|
||||||
|
redraw()
|
||||||
|
timeout = setTimeout(check2, 20)
|
||||||
|
|
||||||
|
proc check3() =
|
||||||
|
let result = checkOrder(@[2, 3, 4, 1])
|
||||||
|
if result:
|
||||||
|
kout cstring"test3 - OK"
|
||||||
|
else:
|
||||||
|
kout cstring"test3 - FAIL"
|
||||||
|
|
||||||
|
# result: 2 3 4 1
|
||||||
|
proc test3() =
|
||||||
|
kout cstring"test3 started"
|
||||||
|
entries = @[cstring("2"), cstring("3"), cstring("4"), cstring("1") ]
|
||||||
|
redraw()
|
||||||
|
timeout = setTimeout(check3, 20)
|
||||||
|
|
||||||
|
proc check4() =
|
||||||
|
let result = checkOrder(@[5, 6, 7, 8])
|
||||||
|
if result:
|
||||||
|
kout cstring"test4 - OK"
|
||||||
|
else:
|
||||||
|
kout cstring"test4 - FAIL"
|
||||||
|
|
||||||
|
# result: 5 6 7 8
|
||||||
|
proc test4() =
|
||||||
|
kout cstring"test4 started"
|
||||||
|
entries = @[cstring("5"), cstring("6"), cstring("7"), cstring("8") ]
|
||||||
|
redraw()
|
||||||
|
timeout = setTimeout(check4, 20)
|
||||||
|
|
||||||
|
proc check5() =
|
||||||
|
let result = checkOrder(@[0, 1, 3, 5, 4, 5])
|
||||||
|
if result:
|
||||||
|
kout cstring"test5 - OK"
|
||||||
|
else:
|
||||||
|
kout cstring"test5 - FAIL"
|
||||||
|
|
||||||
|
# result: 0 1 3 5 4 5
|
||||||
|
proc test5() =
|
||||||
|
kout cstring"test5 started"
|
||||||
|
entries = @[cstring("0"), cstring("1"), cstring("3"), cstring("5"), cstring("4"), cstring("5") ]
|
||||||
|
redraw()
|
||||||
|
timeout = setTimeout(check5, 20)
|
||||||
|
|
||||||
|
proc check6() =
|
||||||
|
let result = checkOrder(@[])
|
||||||
|
if result:
|
||||||
|
kout cstring"test6 - OK"
|
||||||
|
else:
|
||||||
|
kout cstring"test6 - FAIL"
|
||||||
|
|
||||||
|
# result: empty
|
||||||
|
proc test6() =
|
||||||
|
kout cstring"test6 started"
|
||||||
|
entries = @[]
|
||||||
|
redraw()
|
||||||
|
timeout = setTimeout(check6, 20)
|
||||||
|
|
||||||
|
proc check7() =
|
||||||
|
let result = checkOrder(@[2])
|
||||||
|
if result:
|
||||||
|
kout cstring"test7 - OK"
|
||||||
|
else:
|
||||||
|
kout cstring"test7 - FAIL"
|
||||||
|
|
||||||
|
# result: 2
|
||||||
|
proc test7() =
|
||||||
|
kout cstring"test7 started"
|
||||||
|
entries = @[cstring("2")]
|
||||||
|
redraw()
|
||||||
|
timeout = setTimeout(check7, 20)
|
||||||
|
|
||||||
|
proc createEntry(id: int): VNode =
|
||||||
|
result = buildHtml():
|
||||||
|
button(id="" & $id):
|
||||||
|
text $id
|
||||||
|
|
||||||
|
proc createDom(): VNode =
|
||||||
|
result = buildHtml(tdiv()):
|
||||||
|
ul(id="ul"):
|
||||||
|
for e in entries:
|
||||||
|
createEntry(parseInt(e))
|
||||||
|
|
||||||
|
setRenderer createDom
|
||||||
|
|
||||||
|
proc onload(session: cstring) {.exportc.} =
|
||||||
|
for i in 0..5: # 0_000:
|
||||||
|
entries.add(cstring($i))
|
||||||
|
init()
|
||||||
|
|
||||||
|
var dtReset = 100
|
||||||
|
var dtTest = 500
|
||||||
|
|
||||||
|
var t = dtReset
|
||||||
|
timeout = setTimeout(test1, t)
|
||||||
|
t += dtTest
|
||||||
|
timeout = setTimeout(reset, t)
|
||||||
|
t += dtReset
|
||||||
|
|
||||||
|
timeout = setTimeout(test2, t)
|
||||||
|
t += dtTest
|
||||||
|
timeout = setTimeout(reset, t)
|
||||||
|
t += dtReset
|
||||||
|
|
||||||
|
timeout = setTimeout(test3, t)
|
||||||
|
t += dtTest
|
||||||
|
timeout = setTimeout(reset, t)
|
||||||
|
t += dtReset
|
||||||
|
|
||||||
|
timeout = setTimeout(test4, t)
|
||||||
|
t += dtTest
|
||||||
|
timeout = setTimeout(reset, t)
|
||||||
|
t += dtReset
|
||||||
|
|
||||||
|
timeout = setTimeout(test5, t)
|
||||||
|
t += dtTest
|
||||||
|
timeout = setTimeout(reset, t)
|
||||||
|
t += dtReset
|
||||||
|
|
||||||
|
timeout = setTimeout(test6, t)
|
||||||
|
t += dtTest
|
||||||
|
timeout = setTimeout(reset, t)
|
||||||
|
t += dtReset
|
||||||
|
|
||||||
|
timeout = setTimeout(test7, t)
|
||||||
67
karax.nim
67
karax.nim
|
|
@ -117,7 +117,7 @@ proc setTimeout*(action: proc(); ms: int): Timeout {.importc, nodecl.}
|
||||||
proc clearTimeout*(t: Timeout) {.importc, nodecl.}
|
proc clearTimeout*(t: Timeout) {.importc, nodecl.}
|
||||||
#proc targetElem*(e: Event): Element = cast[Element](e.target)
|
#proc targetElem*(e: Event): Element = cast[Element](e.target)
|
||||||
|
|
||||||
#proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.}
|
proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.}
|
||||||
|
|
||||||
#proc getElementsByClassName*(cls: cstring): seq[Element] {.importc:
|
#proc getElementsByClassName*(cls: cstring): seq[Element] {.importc:
|
||||||
# "document.getElementsByClassName", nodecl.}
|
# "document.getElementsByClassName", nodecl.}
|
||||||
|
|
@ -147,6 +147,13 @@ proc equals(a, b: VNode): bool =
|
||||||
# --> maybe give nodes a hash?
|
# --> maybe give nodes a hash?
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
proc equalsTree(a, b: VNode): bool =
|
||||||
|
if not a.validHash:
|
||||||
|
a.calcHash()
|
||||||
|
if not b.validHash:
|
||||||
|
b.calcHash()
|
||||||
|
return a.hash == b.hash
|
||||||
|
|
||||||
proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
|
proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
|
||||||
if not equals(newNode, oldNode):
|
if not equals(newNode, oldNode):
|
||||||
let n = vnodeToDom(newNode)
|
let n = vnodeToDom(newNode)
|
||||||
|
|
@ -156,21 +163,49 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
|
||||||
parent.replaceChild(n, current)
|
parent.replaceChild(n, current)
|
||||||
elif newNode.kind != VNodeKind.text:
|
elif newNode.kind != VNodeKind.text:
|
||||||
let newLength = newNode.len
|
let newLength = newNode.len
|
||||||
let oldLength = oldNode.len
|
var oldLength = oldNode.len
|
||||||
|
let minLength = min(newLength, oldLength)
|
||||||
assert oldNode.kind == newNode.kind
|
assert oldNode.kind == newNode.kind
|
||||||
when false:
|
when defined(simpleDiff):
|
||||||
if current.nodeName != toTag[oldNode.kind]:
|
for i in 0..min(newLength, oldLength)-1:
|
||||||
kout current.nodeName
|
updateElement(current, current[i], newNode[i], oldNode[i])
|
||||||
kout toTag[oldNode.kind]
|
if newLength > oldLength:
|
||||||
assert false
|
for i in oldLength..newLength-1:
|
||||||
for i in 0..min(newLength, oldLength)-1:
|
current.appendChild(vnodeToDom(newNode[i]))
|
||||||
updateElement(current, current[i], newNode[i], oldNode[i])
|
elif oldLength > newLength:
|
||||||
if newLength > oldLength:
|
for i in countdown(oldLength-1, newLength):
|
||||||
for i in oldLength..newLength-1:
|
current.removeChild(current.lastChild)
|
||||||
current.appendChild(vnodeToDom(newNode[i]))
|
else:
|
||||||
elif oldLength > newLength:
|
var commonPrefix = 0
|
||||||
for i in countdown(oldLength-1, newLength):
|
while commonPrefix < minLength and equalsTree(newNode[commonPrefix], oldNode[commonPrefix]):
|
||||||
current.removeChild(current.lastChild)
|
inc commonPrefix
|
||||||
|
|
||||||
|
var oldPos = oldLength - 1
|
||||||
|
var newPos = newLength - 1
|
||||||
|
while oldPos >= commonPrefix and newPos >= commonPrefix and equalsTree(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])
|
||||||
|
|
||||||
|
var nextChildPos = oldPos + 1
|
||||||
|
while pos <= newPos:
|
||||||
|
if nextChildPos == oldLength:
|
||||||
|
current.appendChild(vnodeToDom(newNode[pos]))
|
||||||
|
else:
|
||||||
|
current.insertBefore(vnodeToDom(newNode[pos]), current.childNodes[nextChildPos])
|
||||||
|
# added new Node, so old state of VDOM have one more Node
|
||||||
|
inc oldLength
|
||||||
|
inc pos
|
||||||
|
inc nextChildPos
|
||||||
|
|
||||||
|
for i in 0..oldPos-pos:
|
||||||
|
current.removeChild(current.childNodes[pos])
|
||||||
|
|
||||||
|
|
||||||
proc dodraw() =
|
proc dodraw() =
|
||||||
let newtree = dorender()
|
let newtree = dorender()
|
||||||
|
|
@ -187,7 +222,7 @@ proc dodraw() =
|
||||||
currentTree = newtree
|
currentTree = newtree
|
||||||
# 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 toFocus != nil:
|
if toFocus != nil:
|
||||||
toFocus.focus()
|
discard "toFocus.focus()"
|
||||||
|
|
||||||
proc visibleKeys(e: Node; a, b: var VKey; h, count: var int) =
|
proc visibleKeys(e: Node; a, b: var VKey; h, count: var int) =
|
||||||
# we only care about nodes that have a key:
|
# we only care about nodes that have a key:
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,9 @@ proc lazyEntry(args: seq[VNode]): VNode =
|
||||||
proc createEntry(id: int; d: cstring; completed, selected: bool): VNode =
|
proc createEntry(id: int; d: cstring; completed, selected: bool): VNode =
|
||||||
result = thunk(lazyEntry, id, d, completed, selected)
|
result = thunk(lazyEntry, id, d, completed, selected)
|
||||||
|
|
||||||
|
proc myscroll(ev: Event; n: VNode) =
|
||||||
|
kout cstring"scrolling event here"
|
||||||
|
|
||||||
proc createDom(): VNode =
|
proc createDom(): VNode =
|
||||||
result = buildHtml(tdiv(class="todomvc-wrapper")):
|
result = buildHtml(tdiv(class="todomvc-wrapper")):
|
||||||
section(class = "todoapp"):
|
section(class = "todoapp"):
|
||||||
|
|
@ -102,6 +105,11 @@ proc createDom(): VNode =
|
||||||
input(class = "toggle-all", `type` = "checkbox", name = "toggle")
|
input(class = "toggle-all", `type` = "checkbox", name = "toggle")
|
||||||
label(`for` = "toggle-all", onclick = onAllDone):
|
label(`for` = "toggle-all", onclick = onAllDone):
|
||||||
text "Mark all as complete"
|
text "Mark all as complete"
|
||||||
|
tdiv(onscroll = myscroll, style = "overflow: visible; width: 0px; display: block; max-height: 1em"):
|
||||||
|
for i in 0..4:
|
||||||
|
tdiv:
|
||||||
|
text entries[i][0]
|
||||||
|
|
||||||
var entriesCount = 0
|
var entriesCount = 0
|
||||||
var completedCount = 0
|
var completedCount = 0
|
||||||
ul(class = "todo-list"):
|
ul(class = "todo-list"):
|
||||||
|
|
|
||||||
|
|
@ -119,3 +119,6 @@ proc onload(session: cstring) {.exportc.} =
|
||||||
for i in 0..1: # 0_000:
|
for i in 0..1: # 0_000:
|
||||||
entries.add((cstring"Entry " & &i, false))
|
entries.add((cstring"Entry " & &i, false))
|
||||||
init()
|
init()
|
||||||
|
|
||||||
|
for i in 0..10_000:
|
||||||
|
entries.add((cstring"Entry " & &i, false))
|
||||||
30
vdom.nim
30
vdom.nim
|
|
@ -1,6 +1,7 @@
|
||||||
# Virtual DOM implementation
|
# Virtual DOM implementation
|
||||||
|
|
||||||
from dom import Event
|
from dom import Event
|
||||||
|
import shash
|
||||||
|
|
||||||
type
|
type
|
||||||
VNodeKind* {.pure.} = enum
|
VNodeKind* {.pure.} = enum
|
||||||
|
|
@ -43,6 +44,8 @@ type
|
||||||
attrs: seq[cstring]
|
attrs: seq[cstring]
|
||||||
events*: seq[(EventKind, EventHandler)]
|
events*: seq[(EventKind, EventHandler)]
|
||||||
thunk*: proc (args: seq[VNode]): VNode
|
thunk*: proc (args: seq[VNode]): VNode
|
||||||
|
hash*: Hash
|
||||||
|
validHash*: bool
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -146,6 +149,33 @@ proc toString*(n: VNode; result: var string; indent: int) =
|
||||||
for i in 1..indent: result.add ' '
|
for i in 1..indent: result.add ' '
|
||||||
result.add "\L</" & $n.kind & ">"
|
result.add "\L</" & $n.kind & ">"
|
||||||
|
|
||||||
|
proc calcHash*(n: VNode) =
|
||||||
|
if n.validHash: return
|
||||||
|
n.validHash = true
|
||||||
|
var h: Hash = ord n.kind
|
||||||
|
if n.id != nil:
|
||||||
|
h &= "id"
|
||||||
|
h &= n.id
|
||||||
|
if n.class != nil:
|
||||||
|
h &= "class"
|
||||||
|
h &= n.class
|
||||||
|
if n.key >= 0:
|
||||||
|
h &= "k"
|
||||||
|
h &= n.key
|
||||||
|
for k, v in attrs(n):
|
||||||
|
h &= " "
|
||||||
|
h &= k
|
||||||
|
h &= "="
|
||||||
|
h &= v
|
||||||
|
if n.kind == VNodeKind.text or n.text != nil:
|
||||||
|
h &= "t"
|
||||||
|
h &= n.text
|
||||||
|
else:
|
||||||
|
for child in items(n):
|
||||||
|
calcHash(child)
|
||||||
|
h &= child.hash
|
||||||
|
n.hash = h
|
||||||
|
|
||||||
proc `$`*(n: VNode): cstring =
|
proc `$`*(n: VNode): cstring =
|
||||||
var res = ""
|
var res = ""
|
||||||
toString(n, res, 0)
|
toString(n, res, 0)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue