introduce KaraxInstance; 100% backwards compatible
This commit is contained in:
parent
08538f7f26
commit
b3fba429ad
2 changed files with 70 additions and 55 deletions
121
src/karax.nim
121
src/karax.nim
|
|
@ -14,12 +14,26 @@ proc key*(e: Node): VKey =
|
||||||
else: result = -1
|
else: result = -1
|
||||||
proc `key=`*(e: Node; x: VKey) {.importcpp: "#.karaxKey = #", nodecl.}
|
proc `key=`*(e: Node; x: VKey) {.importcpp: "#.karaxKey = #", nodecl.}
|
||||||
|
|
||||||
var
|
type
|
||||||
toFocus: Node
|
KaraxInstance* = ref object ## underlying karax instance. Usually you don't have
|
||||||
toFocusV: VNode
|
## know about this.
|
||||||
|
rootId: cstring not nil
|
||||||
|
renderer: proc (): VNode {.closure.}
|
||||||
|
currentTree: VNode
|
||||||
|
postRenderCallback: proc ()
|
||||||
|
toFocus: Node
|
||||||
|
toFocusV: VNode
|
||||||
|
|
||||||
proc setFocus*(n: VNode) =
|
var
|
||||||
toFocusV = n
|
kxi*: KaraxInstance ## The current Karax instance. This is always used
|
||||||
|
## as the default. **Note**: Within the karax DSL
|
||||||
|
## always a symbol of the name *kxi* is assumed, so
|
||||||
|
## if you have a local karax instance to use instead
|
||||||
|
## in your 'buildHtml' statement, it needs to be named
|
||||||
|
## 'kxi'.
|
||||||
|
|
||||||
|
proc setFocus*(n: VNode; kxi: KaraxInstance = kxi) =
|
||||||
|
kxi.toFocusV = n
|
||||||
|
|
||||||
# ----------------- event wrapping ---------------------------------------
|
# ----------------- event wrapping ---------------------------------------
|
||||||
|
|
||||||
|
|
@ -68,13 +82,13 @@ proc wrapEvent(d: Node; n: VNode; k: EventKind; action: EventHandler) =
|
||||||
template detach(n: VNode) = n.dom = nil
|
template detach(n: VNode) = n.dom = nil
|
||||||
template attach(n: Vnode) = n.dom = result
|
template attach(n: Vnode) = n.dom = result
|
||||||
|
|
||||||
proc vnodeToDom(n: VNode): Node =
|
proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
|
||||||
if n.kind == VNodeKind.text:
|
if n.kind == VNodeKind.text:
|
||||||
result = document.createTextNode(n.text)
|
result = document.createTextNode(n.text)
|
||||||
attach n
|
attach n
|
||||||
elif n.kind == VNodeKind.vthunk:
|
elif n.kind == VNodeKind.vthunk:
|
||||||
let x = callThunk(vcomponents[n.text], n)
|
let x = callThunk(vcomponents[n.text], n)
|
||||||
result = vnodeToDom(x)
|
result = vnodeToDom(x, kxi)
|
||||||
#n.key = result.key
|
#n.key = result.key
|
||||||
attach n
|
attach n
|
||||||
return result
|
return result
|
||||||
|
|
@ -87,7 +101,7 @@ proc vnodeToDom(n: VNode): Node =
|
||||||
result = document.createElement(toTag[n.kind])
|
result = document.createElement(toTag[n.kind])
|
||||||
attach n
|
attach n
|
||||||
for k in n:
|
for k in n:
|
||||||
appendChild(result, vnodeToDom(k))
|
appendChild(result, vnodeToDom(k, kxi))
|
||||||
# text is mapped to 'value':
|
# text is mapped to 'value':
|
||||||
if n.text != nil:
|
if n.text != nil:
|
||||||
result.value = n.text
|
result.value = n.text
|
||||||
|
|
@ -102,8 +116,8 @@ proc vnodeToDom(n: VNode): Node =
|
||||||
result.setAttr(k, v)
|
result.setAttr(k, v)
|
||||||
for e, h in items(n.events):
|
for e, h in items(n.events):
|
||||||
wrapEvent(result, n, e, h)
|
wrapEvent(result, n, e, h)
|
||||||
if n == toFocusV and toFocus.isNil:
|
if n == kxi.toFocusV and kxi.toFocus.isNil:
|
||||||
toFocus = result
|
kxi.toFocus = result
|
||||||
if not n.style.isNil: applyStyle(result, n.style)
|
if not n.style.isNil: applyStyle(result, n.style)
|
||||||
|
|
||||||
proc same(n: VNode, e: Node): bool =
|
proc same(n: VNode, e: Node): bool =
|
||||||
|
|
@ -114,11 +128,6 @@ proc same(n: VNode, e: Node): bool =
|
||||||
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
|
||||||
|
|
||||||
var
|
|
||||||
dorender: proc (): VNode {.closure.}
|
|
||||||
currentTree: VNode
|
|
||||||
postRenderCallback: proc ()
|
|
||||||
|
|
||||||
proc replaceById(id: cstring; newTree: Node) =
|
proc replaceById(id: cstring; newTree: Node) =
|
||||||
let x = document.getElementById(id)
|
let x = document.getElementById(id)
|
||||||
x.parentNode.replaceChild(newTree, x)
|
x.parentNode.replaceChild(newTree, x)
|
||||||
|
|
@ -155,18 +164,19 @@ proc eq(a, b: VNode; deep: bool): EqResult =
|
||||||
# Do not test event listeners here!
|
# Do not test event listeners here!
|
||||||
return result
|
return result
|
||||||
|
|
||||||
proc updateDirtyElements(parent, current: Node, newNode: VNode) =
|
proc updateDirtyElements(parent, current: Node, newNode: VNode,
|
||||||
|
kxi: KaraxInstance) =
|
||||||
if newNode.key >= 0 and isDirty(newNode.key):
|
if newNode.key >= 0 and isDirty(newNode.key):
|
||||||
unmarkDirty(newNode.key)
|
unmarkDirty(newNode.key)
|
||||||
let n = vnodeToDom(newNode)
|
let n = vnodeToDom(newNode, kxi)
|
||||||
if parent == nil:
|
if parent == nil:
|
||||||
replaceById("ROOT", n)
|
replaceById(kxi.rootId, n)
|
||||||
else:
|
else:
|
||||||
parent.replaceChild(n, current)
|
parent.replaceChild(n, current)
|
||||||
elif newNode.kind != VNodeKind.text and newNode.kind != VNodeKind.vthunk and
|
elif newNode.kind != VNodeKind.text and newNode.kind != VNodeKind.vthunk and
|
||||||
newNode.kind != VNodeKind.dthunk:
|
newNode.kind != VNodeKind.dthunk:
|
||||||
for i in 0..newNode.len-1:
|
for i in 0..newNode.len-1:
|
||||||
updateDirtyElements(current, current[i], newNode[i])
|
updateDirtyElements(current, current[i], newNode[i], kxi)
|
||||||
# leave early if we know there cannot be anything left to do:
|
# leave early if we know there cannot be anything left to do:
|
||||||
#if dirtyCount <= 0: return
|
#if dirtyCount <= 0: return
|
||||||
|
|
||||||
|
|
@ -187,13 +197,14 @@ proc updateDom(newNode, oldNode: VNode) =
|
||||||
for i in 0 ..< newNode.len:
|
for i in 0 ..< newNode.len:
|
||||||
updateDom(newNode[i], oldNode[i])
|
updateDom(newNode[i], oldNode[i])
|
||||||
|
|
||||||
proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
|
proc updateElement(parent, current: Node, newNode, oldNode: VNode;
|
||||||
|
kxi: KaraxInstance) =
|
||||||
let res = eq(newNode, oldNode, deep=false)
|
let res = eq(newNode, oldNode, deep=false)
|
||||||
if res == different:
|
if res == different:
|
||||||
detach(oldNode)
|
detach(oldNode)
|
||||||
let n = vnodeToDom(newNode)
|
let n = vnodeToDom(newNode, kxi)
|
||||||
if parent == nil:
|
if parent == nil:
|
||||||
replaceById("ROOT", n)
|
replaceById(kxi.rootId, n)
|
||||||
else:
|
else:
|
||||||
parent.replaceChild(n, current)
|
parent.replaceChild(n, current)
|
||||||
else:
|
else:
|
||||||
|
|
@ -207,7 +218,7 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
|
||||||
assert oldNode.kind == newNode.kind
|
assert oldNode.kind == newNode.kind
|
||||||
when defined(simpleDiff):
|
when defined(simpleDiff):
|
||||||
for i in 0..min(newLength, oldLength)-1:
|
for i in 0..min(newLength, oldLength)-1:
|
||||||
updateElement(current, current[i], newNode[i], oldNode[i])
|
updateElement(current, current[i], newNode[i], oldNode[i], kxi)
|
||||||
if newLength > oldLength:
|
if newLength > oldLength:
|
||||||
for i in oldLength..newLength-1:
|
for i in oldLength..newLength-1:
|
||||||
current.appendChild(vnodeToDom(newNode[i]))
|
current.appendChild(vnodeToDom(newNode[i]))
|
||||||
|
|
@ -243,14 +254,14 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
|
||||||
|
|
||||||
var pos = min(oldPos, newPos) + 1
|
var pos = min(oldPos, newPos) + 1
|
||||||
for i in commonPrefix..pos-1:
|
for i in commonPrefix..pos-1:
|
||||||
updateElement(current, current.childNodes[i], newNode[i], oldNode[i])
|
updateElement(current, current.childNodes[i], newNode[i], oldNode[i], kxi)
|
||||||
|
|
||||||
var nextChildPos = oldPos + 1
|
var nextChildPos = oldPos + 1
|
||||||
while pos <= newPos:
|
while pos <= newPos:
|
||||||
if nextChildPos == oldLength:
|
if nextChildPos == oldLength:
|
||||||
current.appendChild(vnodeToDom(newNode[pos]))
|
current.appendChild(vnodeToDom(newNode[pos], kxi))
|
||||||
else:
|
else:
|
||||||
current.insertBefore(vnodeToDom(newNode[pos]), current.childNodes[nextChildPos])
|
current.insertBefore(vnodeToDom(newNode[pos], kxi), current.childNodes[nextChildPos])
|
||||||
# added new Node, so old state of VDOM have one more Node
|
# added new Node, so old state of VDOM have one more Node
|
||||||
inc oldLength
|
inc oldLength
|
||||||
inc pos
|
inc pos
|
||||||
|
|
@ -263,53 +274,57 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
|
||||||
when false:
|
when false:
|
||||||
var drawTimeout: Timeout
|
var drawTimeout: Timeout
|
||||||
|
|
||||||
proc dodraw() =
|
proc dodraw(kxi: KaraxInstance) =
|
||||||
if dorender.isNil: return
|
if kxi.renderer.isNil: return
|
||||||
let newtree = dorender()
|
let newtree = kxi.renderer()
|
||||||
newtree.id = "ROOT"
|
newtree.id = kxi.rootId
|
||||||
toFocus = nil
|
kxi.toFocus = nil
|
||||||
if currentTree == nil:
|
if kxi.currentTree == nil:
|
||||||
currentTree = newtree
|
kxi.currentTree = newtree
|
||||||
let asdom = vnodeToDom currentTree
|
let asdom = vnodeToDom(kxi.currentTree, kxi)
|
||||||
replaceById("ROOT", asdom)
|
replaceById(kxi.rootId, asdom)
|
||||||
else:
|
else:
|
||||||
let olddom = document.getElementById("ROOT")
|
let olddom = document.getElementById(kxi.rootId)
|
||||||
updateElement(nil, olddom, newtree, currentTree)
|
updateElement(nil, olddom, newtree, kxi.currentTree, kxi)
|
||||||
#assert same(newtree, document.getElementById("ROOT"))
|
#assert same(newtree, document.getElementById("ROOT"))
|
||||||
if someDirty:
|
if someDirty:
|
||||||
updateDirtyElements(nil, olddom, newtree)
|
updateDirtyElements(nil, olddom, newtree, kxi)
|
||||||
someDirty = false
|
someDirty = false
|
||||||
currentTree = newtree
|
kxi.currentTree = newtree
|
||||||
|
|
||||||
if not postRenderCallback.isNil:
|
if not kxi.postRenderCallback.isNil:
|
||||||
postRenderCallback()
|
kxi.postRenderCallback()
|
||||||
|
|
||||||
# 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 kxi.toFocus != nil:
|
||||||
toFocus.focus()
|
kxi.toFocus.focus()
|
||||||
|
|
||||||
proc reqFrame(callback: proc()) {.importc: "window.requestAnimationFrame".}
|
proc reqFrame(callback: proc()) {.importc: "window.requestAnimationFrame".}
|
||||||
|
|
||||||
proc redraw*() =
|
proc redraw*(kxi: KaraxInstance = kxi) =
|
||||||
# we buffer redraw requests:
|
# we buffer redraw requests:
|
||||||
when false:
|
when false:
|
||||||
if drawTimeout != nil:
|
if drawTimeout != nil:
|
||||||
clearTimeout(drawTimeout)
|
clearTimeout(drawTimeout)
|
||||||
drawTimeout = setTimeout(dodraw, 30)
|
drawTimeout = setTimeout(dodraw, 30)
|
||||||
elif true:
|
elif true:
|
||||||
reqFrame(dodraw)
|
reqFrame(proc () = kxi.dodraw)
|
||||||
else:
|
else:
|
||||||
dodraw()
|
dodraw()
|
||||||
|
|
||||||
proc init(ev: Event) =
|
proc init(ev: Event) =
|
||||||
reqFrame(dodraw)
|
reqFrame(proc () = kxi.dodraw)
|
||||||
|
|
||||||
proc setRenderer*(renderer: proc (): VNode, clientPostRenderCallback: proc () = nil) =
|
proc setRenderer*(renderer: proc (): VNode, root: cstring = "ROOT",
|
||||||
dorender = renderer
|
clientPostRenderCallback: proc () = nil): KaraxInstance {.discardable.} =
|
||||||
|
## Setup Karax. Usually the return value can be ignored.
|
||||||
|
result = KaraxInstance(rootId: root, renderer: renderer,
|
||||||
|
postRenderCallback: clientPostRenderCallback)
|
||||||
|
kxi = result
|
||||||
window.onload = init
|
window.onload = init
|
||||||
postRenderCallback = clientPostRenderCallback
|
|
||||||
|
|
||||||
proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler) =
|
proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler;
|
||||||
|
kxi: KaraxInstance = kxi) =
|
||||||
## Implements the foundation of Karax's event management.
|
## Implements the foundation of Karax's event management.
|
||||||
## Karax DSL transforms ``tag(onEvent = handler)`` to
|
## Karax DSL transforms ``tag(onEvent = handler)`` to
|
||||||
## ``tempNode.addEventHandler(tagNode, EventKind.onEvent, wrapper)``
|
## ``tempNode.addEventHandler(tagNode, EventKind.onEvent, wrapper)``
|
||||||
|
|
@ -317,7 +332,7 @@ proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler) =
|
||||||
## a ``redraw``.
|
## a ``redraw``.
|
||||||
proc wrapper(ev: Event; n: VNode) =
|
proc wrapper(ev: Event; n: VNode) =
|
||||||
action(ev, n)
|
action(ev, n)
|
||||||
redraw()
|
redraw(kxi)
|
||||||
addEventListener(n, k, wrapper)
|
addEventListener(n, k, wrapper)
|
||||||
|
|
||||||
proc setOnHashChange*(action: proc (hashPart: cstring)) =
|
proc setOnHashChange*(action: proc (hashPart: cstring)) =
|
||||||
|
|
@ -346,10 +361,10 @@ proc setupErrorHandler*() =
|
||||||
proc prepend(parent, kid: Element) =
|
proc prepend(parent, kid: Element) =
|
||||||
parent.insertBefore(kid, parent.firstChild)
|
parent.insertBefore(kid, parent.firstChild)
|
||||||
|
|
||||||
proc loadScript*(jsfilename: cstring) =
|
proc loadScript*(jsfilename: cstring; kxi: KaraxInstance = kxi) =
|
||||||
let body = getElementById("body")
|
let body = 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
|
||||||
body.prepend(s)
|
body.prepend(s)
|
||||||
redraw()
|
redraw(kxi)
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||||
let key = getName x[0]
|
let key = getName x[0]
|
||||||
if key.startsWith("on"):
|
if key.startsWith("on"):
|
||||||
result.add newCall(bindSym"addEventHandler",
|
result.add newCall(bindSym"addEventHandler",
|
||||||
tmp, newDotExpr(bindSym"EventKind", x[0]), x[1])
|
tmp, newDotExpr(bindSym"EventKind", x[0]), x[1], ident("kxi"))
|
||||||
elif key in SpecialAttrs:
|
elif key in SpecialAttrs:
|
||||||
result.add newDotAsgn(tmp, key, x[1])
|
result.add newDotAsgn(tmp, key, x[1])
|
||||||
else:
|
else:
|
||||||
|
|
@ -84,7 +84,7 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||||
elif ck != ComponentKind.Tag:
|
elif ck != ComponentKind.Tag:
|
||||||
call.add x
|
call.add x
|
||||||
elif eqIdent(x, "setFocus"):
|
elif eqIdent(x, "setFocus"):
|
||||||
result.add newCall(x, tmp)
|
result.add newCall(x, tmp, ident"kxi")
|
||||||
else:
|
else:
|
||||||
result.add tcall2(x, tmp)
|
result.add tcall2(x, tmp)
|
||||||
if tmpContext == nil:
|
if tmpContext == nil:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue