Karax: better support for routing; easier to use; todoapp bugfix: misapplied .compact macro

This commit is contained in:
Araq 2018-02-17 14:13:26 +01:00
commit 5576c4809d
3 changed files with 51 additions and 33 deletions

View file

@ -89,7 +89,7 @@ proc createEntry(id: int; d: cstring; completed, selected: bool): VNode {.compac
onblur = focusLost, onblur = focusLost,
onkeyupenter = editEntry, value = d, setFocus=true) onkeyupenter = editEntry, value = d, setFocus=true)
proc makeFooter(entriesCount, completedCount: int): VNode {.compact.} = proc makeFooter(entriesCount, completedCount: int): VNode =
result = buildHtml(footer(class = "footer")): result = buildHtml(footer(class = "footer")):
span(class = "todo-count"): span(class = "todo-count"):
strong: strong:
@ -115,7 +115,10 @@ proc makeHeader(): VNode {.compact.} =
input(class = "new-todo", placeholder="What needs to be done?", name = "newTodo", input(class = "new-todo", placeholder="What needs to be done?", name = "newTodo",
onkeyupenter = onTodoEnter, setFocus) onkeyupenter = onTodoEnter, setFocus)
proc createDom(): VNode = proc createDom(data: RouterData): VNode =
if data.hashPart == "#/": filter = all
elif data.hashPart == "#/completed": filter = completed
elif data.hashPart == "#/active": filter = active
result = buildHtml(tdiv(class="todomvc-wrapper")): result = buildHtml(tdiv(class="todomvc-wrapper")):
section(class = "todoapp"): section(class = "todoapp"):
makeHeader() makeHeader()
@ -141,12 +144,6 @@ proc createDom(): VNode =
inc entriesCount inc entriesCount
makeFooter(entriesCount, completedCount) makeFooter(entriesCount, completedCount)
setOnHashChange(proc(hash: cstring) =
if hash == "#/": filter = all
elif hash == "#/completed": filter = completed
elif hash == "#/active": filter = active
)
if hasItem(lenSuffix): if hasItem(lenSuffix):
entriesLen = parseInt getItem(lenSuffix) entriesLen = parseInt getItem(lenSuffix)
else: else:

View file

@ -23,12 +23,15 @@ type
parent, current: Node parent, current: Node
type type
RouterData* = ref object ## information that is passed to the 'renderer' callback
hashPart*: cstring ## the hash part of the URL for routing.
KaraxInstance* = ref object ## underlying karax instance. Usually you don't have KaraxInstance* = ref object ## underlying karax instance. Usually you don't have
## know about this. ## know about this.
rootId: cstring not nil rootId: cstring not nil
renderer: proc (): VNode {.closure.} renderer: proc (data: RouterData): VNode {.closure.}
currentTree: VNode currentTree: VNode
postRenderCallback: proc () postRenderCallback: proc (data: RouterData)
toFocus: Node toFocus: Node
toFocusV: VNode toFocusV: VNode
renderId: int renderId: int
@ -287,17 +290,18 @@ proc mergeEvents(newNode, oldNode: VNode; kxi: KaraxInstance) =
shallowCopy(oldNode.events, newNode.events) shallowCopy(oldNode.events, newNode.events)
applyEvents(oldNode, kxi) applyEvents(oldNode, kxi)
proc printV(n: VNode; depth: cstring = "") = when false:
kout depth, cstring($n.kind), cstring"key ", n.index proc printV(n: VNode; depth: cstring = "") =
#for k, v in pairs(n.style): kout depth, cstring($n.kind), cstring"key ", n.index
# kout depth, "style: ", k, v #for k, v in pairs(n.style):
if n.kind == VNodeKind.component: # kout depth, "style: ", k, v
let nn = VComponent(n) if n.kind == VNodeKind.component:
if nn.expanded != nil: printV(nn.expanded, ">>" & depth) let nn = VComponent(n)
elif n.kind == VNodeKind.text: if nn.expanded != nil: printV(nn.expanded, ">>" & depth)
kout depth, n.text elif n.kind == VNodeKind.text:
for i in 0 ..< n.len: kout depth, n.text
printV(n[i], depth & " ") for i in 0 ..< n.len:
printV(n[i], depth & " ")
proc addPatch(kxi: KaraxInstance; ka: PatchKind; parenta, currenta: Node; proc addPatch(kxi: KaraxInstance; ka: PatchKind; parenta, currenta: Node;
na: VNode) = na: VNode) =
@ -559,9 +563,13 @@ proc runDiff*(kxi: KaraxInstance; oldNode, newNode: VNode) =
kxi.currentTree = newNode kxi.currentTree = newNode
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId)) doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
var onhashChange {.importc: "window.onhashchange".}: proc()
var hashPart {.importc: "window.location.hash".}: cstring
proc dodraw(kxi: KaraxInstance) = proc dodraw(kxi: KaraxInstance) =
if kxi.renderer.isNil: return if kxi.renderer.isNil: return
let newtree = kxi.renderer() let rdata = RouterData(hashPart: hashPart)
let newtree = kxi.renderer(rdata)
inc kxi.runCount inc kxi.runCount
newtree.id = kxi.rootId newtree.id = kxi.rootId
kxi.toFocus = nil kxi.toFocus = nil
@ -586,7 +594,7 @@ proc dodraw(kxi: KaraxInstance) =
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(rdata)
# 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 kxi.toFocus != nil: if kxi.toFocus != nil:
@ -598,7 +606,8 @@ proc dodraw(kxi: KaraxInstance) =
echo "depth ", depth(kxi.currentTree, total), " total ", total echo "depth ", depth(kxi.currentTree, total), " total ", total
proc reqFrame(callback: proc()): int {.importc: "window.requestAnimationFrame".} proc reqFrame(callback: proc()): int {.importc: "window.requestAnimationFrame".}
proc cancelFrame(id: int) {.importc: "window.cancelAnimationFrame".} when false:
proc cancelFrame(id: int) {.importc: "window.cancelAnimationFrame".}
proc redraw*(kxi: KaraxInstance = kxi) = proc redraw*(kxi: KaraxInstance = kxi) =
# we buffer redraw requests: # we buffer redraw requests:
@ -617,8 +626,11 @@ proc redrawSync*(kxi: KaraxInstance = kxi) = dodraw(kxi)
proc init(ev: Event) = proc init(ev: Event) =
kxi.renderId = reqFrame(proc () = kxi.dodraw) kxi.renderId = reqFrame(proc () = kxi.dodraw)
proc setRenderer*(renderer: proc (): VNode, root: cstring = "ROOT", proc setRenderer*(renderer: proc (data: RouterData): VNode,
clientPostRenderCallback: proc () = nil): KaraxInstance {.discardable.} = root: cstring = "ROOT",
clientPostRenderCallback:
proc (data: RouterData) = nil): KaraxInstance {.
discardable.} =
## Setup Karax. Usually the return value can be ignored. ## Setup Karax. Usually the return value can be ignored.
result = KaraxInstance(rootId: root, renderer: renderer, result = KaraxInstance(rootId: root, renderer: renderer,
postRenderCallback: clientPostRenderCallback, postRenderCallback: clientPostRenderCallback,
@ -630,9 +642,18 @@ proc setRenderer*(renderer: proc (): VNode, root: cstring = "ROOT",
orphans: newJDict[cstring, bool]()) orphans: newJDict[cstring, bool]())
kxi = result kxi = result
window.onload = init window.onload = init
onhashChange = proc() = redraw()
proc setInitializer*(renderer: proc (): VNode, root: cstring = "ROOT", proc setRenderer*(renderer: proc (): VNode, root: cstring = "ROOT",
clientPostRenderCallback: proc () = nil): KaraxInstance {.discardable.} = clientPostRenderCallback: proc () = nil): KaraxInstance {.discardable.} =
## Setup Karax. Usually the return value can be ignored.
proc wrapRenderer(data: RouterData): VNode = result = renderer()
proc wrapPostRender(data: RouterData) = clientPostRenderCallback()
setRenderer(wrapRenderer, root, wrapPostRender)
proc setInitializer*(renderer: proc (data: RouterData): VNode, root: cstring = "ROOT",
clientPostRenderCallback:
proc (data: RouterData) = nil): KaraxInstance {.discardable.} =
## Setup Karax. Usually the return value can be ignored. ## Setup Karax. Usually the return value can be ignored.
result = KaraxInstance(rootId: root, renderer: renderer, result = KaraxInstance(rootId: root, renderer: renderer,
postRenderCallback: clientPostRenderCallback, postRenderCallback: clientPostRenderCallback,
@ -669,9 +690,9 @@ proc addEventHandler*(n: VNode; k: EventKind; action: proc();
if not kxi.surpressRedraws: redraw(kxi) if not kxi.surpressRedraws: redraw(kxi)
addEventListener(n, k, wrapper) addEventListener(n, k, wrapper)
proc setOnHashChange*(action: proc (hashPart: cstring)) = proc setOnHashChange*(action: proc (hashPart: cstring)) {.deprecated.} =
var onhashChange {.importc: "window.onhashchange".}: proc() ## Now deprecated, instead pass a callback to ``setRenderer`` that receives
var hashPart {.importc: "window.location.hash".}: cstring ## a ``data: RouterData`` parameter.
proc wrapper() = proc wrapper() =
action(hashPart) action(hashPart)
redraw() redraw()
@ -693,7 +714,7 @@ proc setupErrorHandler*() =
var onerror {.importc: "window.onerror", used.} = var onerror {.importc: "window.onerror", used.} =
proc (msg, url: cstring, line, col: int, error: cstring): bool = proc (msg, url: cstring, line, col: int, error: cstring): bool =
var x = cstring"Error: " & msg & "\n" & stackTraceAsCstring() var x = cstring"Error: " & msg & "\n" & stackTraceAsCstring()
kout(x) echo(x)
return true # suppressErrorAlert return true # suppressErrorAlert
{.pop.} {.pop.}

View file

@ -165,7 +165,7 @@ proc testWildInsert() =
let b = createEntries(entries) let b = createEntries(entries)
doDiff(a, b, "pkInsertBefore button 7", "pkAppend div 5") doDiff(a, b, "pkInsertBefore button 7", "pkAppend div 5")
kxi = KaraxInstance(rootId: cstring"ROOT", renderer: proc (): VNode = discard, kxi = KaraxInstance(rootId: cstring"ROOT", renderer: proc (data: RouterData): VNode = discard,
byId: newJDict[cstring, VNode]()) byId: newJDict[cstring, VNode]())
testAppend() testAppend()