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 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:
|
||||
|
|
|
|||
|
|
@ -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"):
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
30
vdom.nim
30
vdom.nim
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue