introduce KaraxInstance; 100% backwards compatible

This commit is contained in:
Araq 2017-06-16 18:02:35 +02:00
commit b3fba429ad
2 changed files with 70 additions and 55 deletions

View file

@ -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)

View file

@ -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: