Merge branch 'dom_diff_problem' of github.com:xored/karax into devel

This commit is contained in:
Andreas Rumpf 2017-04-11 10:27:21 +02:00
commit 0cc5e85c82
6 changed files with 289 additions and 16 deletions

19
diffDomTests.html Normal file
View 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
View 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)

View file

@ -117,7 +117,7 @@ proc setTimeout*(action: proc(); ms: int): Timeout {.importc, nodecl.}
proc clearTimeout*(t: Timeout) {.importc, nodecl.}
#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:
# "document.getElementsByClassName", nodecl.}
@ -147,6 +147,13 @@ proc equals(a, b: VNode): bool =
# --> maybe give nodes a hash?
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) =
if not equals(newNode, oldNode):
let n = vnodeToDom(newNode)
@ -156,21 +163,49 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
parent.replaceChild(n, current)
elif newNode.kind != VNodeKind.text:
let newLength = newNode.len
let oldLength = oldNode.len
var oldLength = oldNode.len
let minLength = min(newLength, oldLength)
assert oldNode.kind == newNode.kind
when false:
if current.nodeName != toTag[oldNode.kind]:
kout current.nodeName
kout toTag[oldNode.kind]
assert false
for i in 0..min(newLength, oldLength)-1:
updateElement(current, current[i], newNode[i], oldNode[i])
if newLength > oldLength:
for i in oldLength..newLength-1:
current.appendChild(vnodeToDom(newNode[i]))
elif oldLength > newLength:
for i in countdown(oldLength-1, newLength):
current.removeChild(current.lastChild)
when defined(simpleDiff):
for i in 0..min(newLength, oldLength)-1:
updateElement(current, current[i], newNode[i], oldNode[i])
if newLength > oldLength:
for i in oldLength..newLength-1:
current.appendChild(vnodeToDom(newNode[i]))
elif oldLength > newLength:
for i in countdown(oldLength-1, newLength):
current.removeChild(current.lastChild)
else:
var commonPrefix = 0
while commonPrefix < minLength and equalsTree(newNode[commonPrefix], oldNode[commonPrefix]):
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() =
let newtree = dorender()
@ -187,7 +222,7 @@ proc dodraw() =
currentTree = newtree
# now that it's part of the DOM, give it the focus:
if toFocus != nil:
toFocus.focus()
discard "toFocus.focus()"
proc visibleKeys(e: Node; a, b: var VKey; h, count: var int) =
# we only care about nodes that have a key:

View file

@ -90,6 +90,9 @@ proc lazyEntry(args: seq[VNode]): VNode =
proc createEntry(id: int; d: cstring; completed, selected: bool): VNode =
result = thunk(lazyEntry, id, d, completed, selected)
proc myscroll(ev: Event; n: VNode) =
kout cstring"scrolling event here"
proc createDom(): VNode =
result = buildHtml(tdiv(class="todomvc-wrapper")):
section(class = "todoapp"):
@ -102,6 +105,11 @@ proc createDom(): VNode =
input(class = "toggle-all", `type` = "checkbox", name = "toggle")
label(`for` = "toggle-all", onclick = onAllDone):
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 completedCount = 0
ul(class = "todo-list"):

View file

@ -119,3 +119,6 @@ proc onload(session: cstring) {.exportc.} =
for i in 0..1: # 0_000:
entries.add((cstring"Entry " & &i, false))
init()
for i in 0..10_000:
entries.add((cstring"Entry " & &i, false))

View file

@ -1,6 +1,7 @@
# Virtual DOM implementation
from dom import Event
import shash
type
VNodeKind* {.pure.} = enum
@ -43,6 +44,8 @@ type
attrs: seq[cstring]
events*: seq[(EventKind, EventHandler)]
thunk*: proc (args: seq[VNode]): VNode
hash*: Hash
validHash*: bool
proc value*(n: VNode): cstring = n.text
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 ' '
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 =
var res = ""
toString(n, res, 0)