rewritten diff algorithm
This commit is contained in:
parent
cd74cd9d4e
commit
64bc900456
5 changed files with 310 additions and 44 deletions
145
src/karax.nim
145
src/karax.nim
|
|
@ -141,9 +141,13 @@ proc same(n: VNode, e: Node): bool =
|
||||||
elif toTag[n.kind] == e.nodename:
|
elif toTag[n.kind] == e.nodename:
|
||||||
result = true
|
result = true
|
||||||
if n.kind != VNodeKind.text:
|
if n.kind != VNodeKind.text:
|
||||||
if e.len != n.len: return false
|
if e.len != n.len:
|
||||||
|
kout e.len, n.len
|
||||||
|
return false
|
||||||
for i in 0 ..< n.len:
|
for i in 0 ..< n.len:
|
||||||
if not same(n[i], e[i]): return false
|
if not same(n[i], e[i]): return false
|
||||||
|
else:
|
||||||
|
kout toTag[n.kind], e.nodename
|
||||||
|
|
||||||
proc replaceById(id: cstring; newTree: Node) =
|
proc replaceById(id: cstring; newTree: Node) =
|
||||||
let x = document.getElementById(id)
|
let x = document.getElementById(id)
|
||||||
|
|
@ -232,6 +236,134 @@ 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
|
||||||
|
PatchKind = enum
|
||||||
|
pkReplace, pkRemove, pkAppend, pkInsertBefore
|
||||||
|
Patch = object
|
||||||
|
k: PatchKind
|
||||||
|
parent, current, n: Node
|
||||||
|
|
||||||
|
proc addPatch(patches: var seq[Patch]; k: PatchKind; parent, current, n: Node) =
|
||||||
|
patches.add(Patch(k: k, parent: parent, current: current, n: n))
|
||||||
|
|
||||||
|
proc apply(patches: seq[Patch]; kxi: KaraxInstance) =
|
||||||
|
for p in patches:
|
||||||
|
case p.k
|
||||||
|
of pkReplace:
|
||||||
|
if p.parent == nil:
|
||||||
|
replaceById(kxi.rootId, p.n)
|
||||||
|
else:
|
||||||
|
if p.n != p.current:
|
||||||
|
p.parent.replaceChild(p.n, p.current)
|
||||||
|
of pkRemove:
|
||||||
|
p.parent.removeChild(p.current)
|
||||||
|
of pkAppend:
|
||||||
|
p.parent.appendChild(p.n)
|
||||||
|
of pkInsertBefore:
|
||||||
|
p.parent.insertBefore(p.n, p.current)
|
||||||
|
|
||||||
|
proc diff(parent, current: Node; newNode, oldNode: VNode; patches: var seq[Patch];
|
||||||
|
kxi: KaraxInstance): EqResult =
|
||||||
|
result = eq(newNode, oldNode, deep=false)
|
||||||
|
if result <= different:
|
||||||
|
var n: Node
|
||||||
|
if result == changed:
|
||||||
|
assert oldNode.kind == VNodeKind.component
|
||||||
|
let x = VComponent(oldNode)
|
||||||
|
let oldExpanded = x.expanded
|
||||||
|
x.expanded = x.renderImpl(x)
|
||||||
|
x.updatedImpl(x)
|
||||||
|
if oldExpanded == nil:
|
||||||
|
n = vnodeToDom(x.expanded, kxi)
|
||||||
|
else:
|
||||||
|
let res = diff(parent, current, x.expanded, oldExpanded, patches, kxi)
|
||||||
|
if res != different:
|
||||||
|
x.expanded = oldExpanded
|
||||||
|
n = oldExpanded.dom
|
||||||
|
doAssert n != nil, "old expanded.dom is nil"
|
||||||
|
else:
|
||||||
|
n = x.expanded.dom
|
||||||
|
doAssert n != nil, "expanded.dom is nil"
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
detach(oldNode)
|
||||||
|
n = vnodeToDom(newNode, kxi)
|
||||||
|
patches.addPatch(pkReplace, parent, current, n)
|
||||||
|
elif result == similar:
|
||||||
|
updateStyles(newNode, oldNode, false)
|
||||||
|
else:
|
||||||
|
newNode.dom = oldNode.dom
|
||||||
|
let newLength = newNode.len
|
||||||
|
var oldLength = oldNode.len
|
||||||
|
let minLength = min(newLength, oldLength)
|
||||||
|
|
||||||
|
assert oldNode.kind == newNode.kind
|
||||||
|
var commonPrefix = 0
|
||||||
|
|
||||||
|
template eqAndUpdate(a: VNode; i: int; b: VNode; j: int; info, action: untyped) =
|
||||||
|
let oldLen = patches.len
|
||||||
|
if oldNode.kind notin {VNodeKind.component, VNodeKind.vthunk, VNodeKind.dthunk}:
|
||||||
|
assert current != nil
|
||||||
|
assert current.childNodes[j] != nil, $info
|
||||||
|
assert oldNode.len == current.len
|
||||||
|
|
||||||
|
let r = if oldNode.kind in {VNodeKind.component, VNodeKind.vthunk, VNodeKind.dthunk}:
|
||||||
|
diff(parent, current, a[i], b[j], patches, kxi)
|
||||||
|
else:
|
||||||
|
diff(current, current.childNodes[j], a[i], b[j], patches, kxi)
|
||||||
|
case r
|
||||||
|
of identical, changed:
|
||||||
|
a[i] = b[j]
|
||||||
|
action
|
||||||
|
of different:
|
||||||
|
# undo what 'diff' would have done:
|
||||||
|
setLen(patches, oldLen)
|
||||||
|
if result != different: result = r
|
||||||
|
break
|
||||||
|
of similar:
|
||||||
|
updateStyles(a[i], b[j], true)
|
||||||
|
a[i] = b[j]
|
||||||
|
action
|
||||||
|
|
||||||
|
while commonPrefix < minLength:
|
||||||
|
eqAndUpdate(newNode, commonPrefix, oldNode, commonPrefix, cstring"prefix"):
|
||||||
|
inc commonPrefix
|
||||||
|
|
||||||
|
var oldPos = oldLength - 1
|
||||||
|
var newPos = newLength - 1
|
||||||
|
while oldPos >= commonPrefix and newPos >= commonPrefix:
|
||||||
|
eqAndUpdate(newNode, newPos, oldNode, oldPos, cstring"suffix"):
|
||||||
|
dec oldPos
|
||||||
|
dec newPos
|
||||||
|
|
||||||
|
var pos = min(oldPos, newPos) + 1
|
||||||
|
for i in commonPrefix..pos-1:
|
||||||
|
let res = diff(current, current.childNodes[i],
|
||||||
|
newNode[i], oldNode[i], patches, kxi)
|
||||||
|
if res != different:
|
||||||
|
newNode[i] = oldNode[i]
|
||||||
|
else:
|
||||||
|
result = different
|
||||||
|
|
||||||
|
var nextChildPos = oldPos + 1
|
||||||
|
if nextChildPos == oldLength:
|
||||||
|
for i in pos..newPos:
|
||||||
|
patches.addPatch(pkAppend, current, nil, vnodeToDom(newNode[i], kxi))
|
||||||
|
result = different
|
||||||
|
else:
|
||||||
|
let before = current.childNodes[nextChildPos]
|
||||||
|
for i in pos..newPos:
|
||||||
|
patches.addPatch(pkInsertBefore, current, before,
|
||||||
|
vnodeToDom(newNode[i], kxi))
|
||||||
|
result = different
|
||||||
|
# XXX call 'attach' here?
|
||||||
|
for i in pos..oldPos:
|
||||||
|
detach(oldNode[i])
|
||||||
|
doAssert i < current.childNodes.len
|
||||||
|
patches.addPatch(pkRemove, current, current.childNodes[i], nil)
|
||||||
|
result = different
|
||||||
|
|
||||||
|
|
||||||
proc updateElement(parent, current: Node, newNode, oldNode: VNode;
|
proc updateElement(parent, current: Node, newNode, oldNode: VNode;
|
||||||
kxi: KaraxInstance): EqResult =
|
kxi: KaraxInstance): EqResult =
|
||||||
result = eq(newNode, oldNode, deep=false)
|
result = eq(newNode, oldNode, deep=false)
|
||||||
|
|
@ -368,9 +500,14 @@ 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)
|
||||||
|
when false:
|
||||||
discard updateElement(nil, olddom, newtree, kxi.currentTree, kxi)
|
discard updateElement(nil, olddom, newtree, kxi.currentTree, kxi)
|
||||||
|
else:
|
||||||
|
var patches: seq[Patch] = @[]
|
||||||
|
discard diff(nil, olddom, newtree, kxi.currentTree, patches, kxi)
|
||||||
|
patches.apply(kxi)
|
||||||
kxi.currentTree = newtree
|
kxi.currentTree = newtree
|
||||||
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
#doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
||||||
|
|
||||||
if not kxi.postRenderCallback.isNil:
|
if not kxi.postRenderCallback.isNil:
|
||||||
kxi.postRenderCallback()
|
kxi.postRenderCallback()
|
||||||
|
|
@ -387,7 +524,7 @@ proc redraw*(kxi: KaraxInstance = kxi) =
|
||||||
if drawTimeout != nil:
|
if drawTimeout != nil:
|
||||||
clearTimeout(drawTimeout)
|
clearTimeout(drawTimeout)
|
||||||
drawTimeout = setTimeout(dodraw, 30)
|
drawTimeout = setTimeout(dodraw, 30)
|
||||||
elif false:
|
elif true:
|
||||||
reqFrame(proc () = kxi.dodraw)
|
reqFrame(proc () = kxi.dodraw)
|
||||||
else:
|
else:
|
||||||
dodraw(kxi)
|
dodraw(kxi)
|
||||||
|
|
@ -442,7 +579,7 @@ proc prepend(parent, kid: Element) =
|
||||||
parent.insertBefore(kid, parent.firstChild)
|
parent.insertBefore(kid, parent.firstChild)
|
||||||
|
|
||||||
proc loadScript*(jsfilename: cstring; kxi: KaraxInstance = kxi) =
|
proc loadScript*(jsfilename: cstring; kxi: KaraxInstance = kxi) =
|
||||||
let body = getElementById("body")
|
let body = document.getElementById("body")
|
||||||
let s = document.createElement("script")
|
let s = document.createElement("script")
|
||||||
s.setAttr "type", "text/javascript"
|
s.setAttr "type", "text/javascript"
|
||||||
s.setAttr "src", jsfilename
|
s.setAttr "src", jsfilename
|
||||||
|
|
|
||||||
90
src/kdom.nim
90
src/kdom.nim
|
|
@ -94,6 +94,8 @@ type
|
||||||
previousSibling*: Node
|
previousSibling*: Node
|
||||||
innerHTML*: cstring
|
innerHTML*: cstring
|
||||||
style*: Style
|
style*: Style
|
||||||
|
when defined(nodejs):
|
||||||
|
id*: cstring
|
||||||
|
|
||||||
Document* = ref DocumentObj
|
Document* = ref DocumentObj
|
||||||
DocumentObj {.importc.} = object of NodeObj
|
DocumentObj {.importc.} = object of NodeObj
|
||||||
|
|
@ -395,12 +397,87 @@ type
|
||||||
TimeOut* {.importc.} = ref object of RootObj
|
TimeOut* {.importc.} = ref object of RootObj
|
||||||
Interval* {.importc.} = object of RootObj
|
Interval* {.importc.} = object of RootObj
|
||||||
|
|
||||||
|
when defined(nodejs):
|
||||||
|
# we provide a dummy DOM for nodejs for testing purposes
|
||||||
|
proc len*(x: Node): int = x.childNodes.len
|
||||||
|
proc `[]`*(x: Node; idx: int): Element =
|
||||||
|
assert idx >= 0 and idx < x.childNodes.len
|
||||||
|
result = cast[Element](x.childNodes[idx])
|
||||||
|
|
||||||
|
var document* = Document(nodeType: DocumentNode)
|
||||||
|
|
||||||
|
proc getElem(x: Element; id: cstring): Element =
|
||||||
|
if x.id == id: return x
|
||||||
|
for i in 0..<x.len:
|
||||||
|
result = getElem(x[i], id)
|
||||||
|
if result != nil: return result
|
||||||
|
|
||||||
|
proc getElementById*(doc: Document; id: cstring): Element =
|
||||||
|
getElem(doc.body, id)
|
||||||
|
proc getElementById*(id: cstring): Element = document.getElementById(id)
|
||||||
|
|
||||||
|
proc appendChild*(parent, n: Node) =
|
||||||
|
n.parentNode = parent
|
||||||
|
parent.childNodes.add n
|
||||||
|
|
||||||
|
proc replaceChild*(parent, newNode, oldNode: Node) =
|
||||||
|
newNode.parentNode = parent
|
||||||
|
oldNode.parentNode = nil
|
||||||
|
var i = 0
|
||||||
|
while i < parent.len:
|
||||||
|
if Node(parent[i]) == oldNode:
|
||||||
|
parent.childNodes[i] = newNode
|
||||||
|
return
|
||||||
|
inc i
|
||||||
|
doAssert false, "old node not in node list"
|
||||||
|
|
||||||
|
proc removeChild*(parent, child: Node) =
|
||||||
|
child.parentNode = nil
|
||||||
|
var i = 0
|
||||||
|
while i < parent.len:
|
||||||
|
if Node(parent[i]) == child:
|
||||||
|
parent.childNodes.delete(i)
|
||||||
|
return
|
||||||
|
inc i
|
||||||
|
doAssert false, "old node not in node list"
|
||||||
|
|
||||||
|
proc insertBefore*(parent, newNode, before: Node) =
|
||||||
|
appendChild(parent, newNode)
|
||||||
|
var i = 0
|
||||||
|
while i < parent.len-1:
|
||||||
|
if Node(parent[i]) == before:
|
||||||
|
for j in countdown(parent.len-1, i-1):
|
||||||
|
parent.childNodes[j] = parent.childNodes[j-1]
|
||||||
|
parent.childNodes[i-1] = newNode
|
||||||
|
return
|
||||||
|
inc i
|
||||||
|
#doAssert false, "before not in node list"
|
||||||
|
|
||||||
|
proc createElement*(d: Document, identifier: cstring): Element =
|
||||||
|
new(result)
|
||||||
|
result.nodeName = identifier
|
||||||
|
result.nodeType = NodeType.ElementNode
|
||||||
|
|
||||||
|
proc createTextNode*(d: Document, identifier: cstring): Node =
|
||||||
|
new(result)
|
||||||
|
result.nodeName = "#text"
|
||||||
|
result.nodeValue = identifier
|
||||||
|
result.nodeType = NodeType.TextNode
|
||||||
|
|
||||||
|
else:
|
||||||
proc len*(x: Node): int {.importcpp: "#.childNodes.length".}
|
proc len*(x: Node): int {.importcpp: "#.childNodes.length".}
|
||||||
proc `[]`*(x: Node; idx: int): Element {.importcpp: "#.childNodes[#]".}
|
proc `[]`*(x: Node; idx: int): Element {.importcpp: "#.childNodes[#]".}
|
||||||
|
proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.}
|
||||||
|
proc appendChild*(n, child: Node) {.importcpp.}
|
||||||
|
proc removeChild*(n, child: Node) {.importcpp.}
|
||||||
|
proc replaceChild*(n, newNode, oldNode: Node) {.importcpp.}
|
||||||
|
proc insertBefore*(n, newNode, before: Node) {.importcpp.}
|
||||||
|
proc getElementById*(d: Document, id: cstring): Element {.importcpp.}
|
||||||
|
proc createElement*(d: Document, identifier: cstring): Element {.importcpp.}
|
||||||
|
proc createTextNode*(d: Document, identifier: cstring): Node {.importcpp.}
|
||||||
|
|
||||||
proc setTimeout*(action: proc(); ms: int): Timeout {.importc, nodecl.}
|
proc setTimeout*(action: proc(); ms: int): Timeout {.importc, nodecl.}
|
||||||
proc clearTimeout*(t: Timeout) {.importc, nodecl.}
|
proc clearTimeout*(t: Timeout) {.importc, nodecl.}
|
||||||
proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.}
|
|
||||||
|
|
||||||
{.push importcpp.}
|
{.push importcpp.}
|
||||||
|
|
||||||
|
|
@ -445,19 +522,15 @@ proc requestAnimationFrame*(w: Window, function: proc (time: float)): int
|
||||||
proc cancelAnimationFrame*(w: Window, id: int)
|
proc cancelAnimationFrame*(w: Window, id: int)
|
||||||
|
|
||||||
# Node "methods"
|
# Node "methods"
|
||||||
proc appendChild*(n, child: Node)
|
|
||||||
proc appendData*(n: Node, data: cstring)
|
proc appendData*(n: Node, data: cstring)
|
||||||
proc cloneNode*(n: Node, copyContent: bool): Node
|
proc cloneNode*(n: Node, copyContent: bool): Node
|
||||||
proc deleteData*(n: Node, start, len: int)
|
proc deleteData*(n: Node, start, len: int)
|
||||||
proc getAttribute*(n: Node, attr: cstring): cstring
|
proc getAttribute*(n: Node, attr: cstring): cstring
|
||||||
proc getAttributeNode*(n: Node, attr: cstring): Node
|
proc getAttributeNode*(n: Node, attr: cstring): Node
|
||||||
proc hasChildNodes*(n: Node): bool
|
proc hasChildNodes*(n: Node): bool
|
||||||
proc insertBefore*(n, newNode, before: Node)
|
|
||||||
proc insertData*(n: Node, position: int, data: cstring)
|
proc insertData*(n: Node, position: int, data: cstring)
|
||||||
proc removeAttribute*(n: Node, attr: cstring)
|
proc removeAttribute*(n: Node, attr: cstring)
|
||||||
proc removeAttributeNode*(n, attr: Node)
|
proc removeAttributeNode*(n, attr: Node)
|
||||||
proc removeChild*(n, child: Node)
|
|
||||||
proc replaceChild*(n, newNode, oldNode: Node)
|
|
||||||
proc replaceData*(n: Node, start, len: int, text: cstring)
|
proc replaceData*(n: Node, start, len: int, text: cstring)
|
||||||
proc scrollIntoView*(n: Node)
|
proc scrollIntoView*(n: Node)
|
||||||
proc setAttribute*(n: Node, name, value: cstring)
|
proc setAttribute*(n: Node, name, value: cstring)
|
||||||
|
|
@ -466,9 +539,6 @@ proc setAttributeNode*(n: Node, attr: Node)
|
||||||
# Document "methods"
|
# Document "methods"
|
||||||
proc captureEvents*(d: Document, eventMask: int) {.deprecated.}
|
proc captureEvents*(d: Document, eventMask: int) {.deprecated.}
|
||||||
proc createAttribute*(d: Document, identifier: cstring): Node
|
proc createAttribute*(d: Document, identifier: cstring): Node
|
||||||
proc createElement*(d: Document, identifier: cstring): Element
|
|
||||||
proc createTextNode*(d: Document, identifier: cstring): Node
|
|
||||||
proc getElementById*(d: Document, id: cstring): Element
|
|
||||||
proc getElementsByName*(d: Document, name: cstring): seq[Element]
|
proc getElementsByName*(d: Document, name: cstring): seq[Element]
|
||||||
proc getElementsByTagName*(d: Document, name: cstring): seq[Element]
|
proc getElementsByTagName*(d: Document, name: cstring): seq[Element]
|
||||||
proc getElementsByClassName*(d: Document, name: cstring): seq[Element]
|
proc getElementsByClassName*(d: Document, name: cstring): seq[Element]
|
||||||
|
|
@ -534,10 +604,12 @@ proc setAttr*(n: Node; key, val: cstring) {.importcpp: "#.setAttribute(@)".}
|
||||||
|
|
||||||
var
|
var
|
||||||
window* {.importc, nodecl.}: Window
|
window* {.importc, nodecl.}: Window
|
||||||
document* {.importc, nodecl.}: Document
|
|
||||||
navigator* {.importc, nodecl.}: Navigator
|
navigator* {.importc, nodecl.}: Navigator
|
||||||
screen* {.importc, nodecl.}: Screen
|
screen* {.importc, nodecl.}: Screen
|
||||||
|
|
||||||
|
when not defined(nodejs):
|
||||||
|
var document* {.importc, nodecl.}: Document
|
||||||
|
|
||||||
proc decodeURI*(uri: cstring): cstring {.importc, nodecl.}
|
proc decodeURI*(uri: cstring): cstring {.importc, nodecl.}
|
||||||
proc encodeURI*(uri: cstring): cstring {.importc, nodecl.}
|
proc encodeURI*(uri: cstring): cstring {.importc, nodecl.}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ type
|
||||||
# even index: key, odd index: value; done this way for memory efficiency:
|
# even index: key, odd index: value; done this way for memory efficiency:
|
||||||
attrs: seq[cstring]
|
attrs: seq[cstring]
|
||||||
events*: seq[(EventKind, EventHandler)]
|
events*: seq[(EventKind, EventHandler)]
|
||||||
|
when false:
|
||||||
hash*: Hash
|
hash*: Hash
|
||||||
validHash*: bool
|
validHash*: bool
|
||||||
style*: VStyle ## the style that should be applied to the virtual node.
|
style*: VStyle ## the style that should be applied to the virtual node.
|
||||||
|
|
@ -248,6 +249,7 @@ 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 & ">"
|
||||||
|
|
||||||
|
when false:
|
||||||
proc calcHash*(n: VNode) =
|
proc calcHash*(n: VNode) =
|
||||||
if n.validHash: return
|
if n.validHash: return
|
||||||
n.validHash = true
|
n.validHash = true
|
||||||
|
|
|
||||||
37
tests/difftest.nim
Normal file
37
tests/difftest.nim
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
include "../src/karax"
|
||||||
|
import "../src/karaxdsl"
|
||||||
|
|
||||||
|
proc doDiff(a, b: VNode) =
|
||||||
|
var patches: seq[Patch] = @[]
|
||||||
|
echo diff(nil, vnodeToDom(a, kxi), b, a, patches, kxi)
|
||||||
|
echo patches
|
||||||
|
|
||||||
|
proc testAppend() =
|
||||||
|
let a = buildHtml(tdiv):
|
||||||
|
ul:
|
||||||
|
li: text "A"
|
||||||
|
li: text "B"
|
||||||
|
let b = buildHtml(tdiv):
|
||||||
|
ul:
|
||||||
|
li: text "A"
|
||||||
|
li: text "B"
|
||||||
|
li: text "C"
|
||||||
|
doDiff(a, b)
|
||||||
|
|
||||||
|
proc testInsert() =
|
||||||
|
let a = buildHtml(tdiv):
|
||||||
|
ul:
|
||||||
|
li: text "A"
|
||||||
|
li: text "C"
|
||||||
|
let b = buildHtml(tdiv):
|
||||||
|
ul:
|
||||||
|
li: text "A"
|
||||||
|
li: text "B"
|
||||||
|
li: text "C"
|
||||||
|
doDiff(a, b)
|
||||||
|
|
||||||
|
kxi = KaraxInstance(rootId: cstring"ROOT", renderer: proc (): VNode = discard)
|
||||||
|
|
||||||
|
#testAppend()
|
||||||
|
testInsert()
|
||||||
18
tests/lists.nim
Normal file
18
tests/lists.nim
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils
|
||||||
|
|
||||||
|
var contents: seq[cstring] = @[]
|
||||||
|
|
||||||
|
proc onTodoEnter(e: Event; n: VNode) =
|
||||||
|
contents.insert(n.value & "BBBB")
|
||||||
|
contents.insert(n.value)
|
||||||
|
n.value = ""
|
||||||
|
|
||||||
|
proc createDom(): VNode =
|
||||||
|
result = buildHtml(tdiv):
|
||||||
|
input(class = "new-todo", placeholder="What needs to be done?", name = "newTodo",
|
||||||
|
onkeyupenter = onTodoEnter, setFocus)
|
||||||
|
for c in contents:
|
||||||
|
tdiv:
|
||||||
|
text c
|
||||||
|
|
||||||
|
setRenderer createDom
|
||||||
Loading…
Add table
Add a link
Reference in a new issue