diff --git a/diffDomTests.html b/diffDomTests.html
new file mode 100644
index 0000000..5ecf130
--- /dev/null
+++ b/diffDomTests.html
@@ -0,0 +1,19 @@
+
+
+
+
+ Diff dom tests
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/diffDomTests.nim b/diffDomTests.nim
new file mode 100644
index 0000000..fe808f6
--- /dev/null
+++ b/diffDomTests.nim
@@ -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)
diff --git a/karax.nim b/karax.nim
index 5858f36..fb3003e 100644
--- a/karax.nim
+++ b/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:
diff --git a/scrollapp.nim b/scrollapp.nim
index f3b70b2..7b7ce8e 100644
--- a/scrollapp.nim
+++ b/scrollapp.nim
@@ -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"):
diff --git a/todoapp.nim b/todoapp.nim
index 63875a6..ea71c66 100644
--- a/todoapp.nim
+++ b/todoapp.nim
@@ -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))
\ No newline at end of file
diff --git a/vdom.nim b/vdom.nim
index ba1202d..0ddfc3a 100644
--- a/vdom.nim
+++ b/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)