todo app: more features

This commit is contained in:
Andreas Rumpf 2017-03-31 13:20:21 +02:00
commit 44c7015a26
5 changed files with 90 additions and 36 deletions

View file

@ -1,5 +1,5 @@
import vdom, karax, karaxdsl, jdict import vdom, karax, karaxdsl, jdict, jstrutils
when false: when false:
var plugins {.exportc.}: seq[(string, proc())] = @[] var plugins {.exportc.}: seq[(string, proc())] = @[]
@ -14,30 +14,49 @@ when false:
if plugins.len > 0: if plugins.len > 0:
plugins[0][1]() plugins[0][1]()
var entries: seq[cstring] var
entries: seq[cstring]
selectedEntry = -1
proc onTodoEnter(val: cstring) = proc onTodoEnter(val: cstring) =
entries.add val entries.add val
proc onclickHandler(ev: Event; n: VNode) = proc removeHandler(ev: Event; n: VNode) =
let id = suffixAsInt(n.id, "remove:") let id = suffixAsInt(n.id, "remove:")
#entries.delete(id) #entries.delete(id)
entries[id] = nil entries[id] = nil
var entryCache = newJDict[int, VNode]() proc editHandler(ev: Event; n: VNode) =
let id = suffixAsInt(n.id, "edit:")
selectedEntry = id
proc createEntry(i: int; d: cstring): VNode = when defined(usecache):
var entryCache = newJDict[int, VNode]()
proc editInput(i: int; d: cstring): VNode =
proc onTodoChange(val: cstring) =
entries[i] = val
selectedEntry = -1
result = enterInput("todo-edit", d, onTodoChange)
#result.setOnfocuslost(proc (ev: Event; n: VNode) = selectedEntry = -1)
proc createEntry(i: int; d: cstring; selected: bool): VNode =
# implement caching: # implement caching:
when defined(usecache):
if entryCache.contains(i): if entryCache.contains(i):
let old = entryCache[i] let old = entryCache[i]
return old return old
result = buildHtml(tr) do: result = buildHtml(tr) do:
td: td(id="edit:" & $i, onclick=editHandler):
if selected:
editInput(i, d)
else:
text d text d
td: td:
span(id="remove:" & $i, onclick=onclickHandler): span(id="remove:" & $i, onclick=removeHandler):
text "[remove]" text "[remove]"
when defined(usecache):
entryCache[i] = result entryCache[i] = result
proc createDom(): VNode = proc createDom(): VNode =
@ -47,12 +66,20 @@ proc createDom(): VNode =
#realtimeInput("by-name", "", onInput) #realtimeInput("by-name", "", onInput)
#br() #br()
text "todo" text cstring"todo"
enterInput("todo-input", "", onTodoEnter) enterInput("todo-input", "", onTodoEnter)
var entriesCount = 0
table(class = "wl"): table(class = "wl"):
for i, d in entries: for i, d in entries:
if d != nil: if d != nil:
createEntry(i, d) createEntry(i, d, i == selectedEntry)
inc entriesCount
tdiv(id = "footer"):
text cstring"Entries: " & &entriesCount
proc onAllDone(ev: Event; n: VNode) =
entries = @[]
selectedEntry = -1
button "All done!", onAllDone, entriesCount == 0 or selectedEntry >= 0
setRenderer createDom setRenderer createDom

View file

@ -10,3 +10,4 @@ proc substr*(s: cstring; start, length: int): cstring {.importcpp: "substr", nod
#proc len*(s: cstring): int {.importcpp: "#.length", nodecl.} #proc len*(s: cstring): int {.importcpp: "#.length", nodecl.}
proc `&`*(a, b: cstring): cstring {.importcpp: "(# + #)", nodecl.} proc `&`*(a, b: cstring): cstring {.importcpp: "(# + #)", nodecl.}
proc toCstr*(s: int): cstring {.importcpp: "((#)+'')", nodecl.} proc toCstr*(s: int): cstring {.importcpp: "((#)+'')", nodecl.}
proc `&`*(s: int): cstring {.importcpp: "((#)+'')", nodecl.}

View file

@ -15,13 +15,17 @@ proc `class=`*(e: Node; v: cstring) {.importcpp: "#.className = #", nodecl.}
proc value*(e: Element): cstring {.importcpp: "#.value", nodecl.} proc value*(e: Element): cstring {.importcpp: "#.value", nodecl.}
proc `value=`*(e: Element; v: cstring) {.importcpp: "#.value = #", nodecl.} proc `value=`*(e: Element; v: cstring) {.importcpp: "#.value = #", nodecl.}
proc `disabled=`*(e: Element; v: bool) {.importcpp: "#.disabled = #", nodecl.}
proc getElementsByClass*(e: Element; name: cstring): seq[Element] {. proc getElementsByClass*(e: Element; name: cstring): seq[Element] {.
importcpp: "#.getElementsByClassName(#)", nodecl.} importcpp: "#.getElementsByClassName(#)", nodecl.}
type type
Timeout* = ref object Timeout* = ref object
var document* {.importc.}: Document var
document* {.importc.}: Document
toFocus: Element
proc vnodeToDom(n: VNode): Element = proc vnodeToDom(n: VNode): Element =
if n.kind == VNodeKind.text: if n.kind == VNodeKind.text:
@ -37,7 +41,8 @@ proc vnodeToDom(n: VNode): Element =
result.id = n.id result.id = n.id
if n.class != nil: if n.class != nil:
result.class = n.class result.class = n.class
for k, v in attrs(n): result.setAttribute(k, v) for k, v in attrs(n):
result.setAttribute(k, v)
let myn = n let myn = n
for e, h in items(n.events): for e, h in items(n.events):
proc wrapper(): proc (ev: Event) = proc wrapper(): proc (ev: Event) =
@ -45,6 +50,8 @@ proc vnodeToDom(n: VNode): Element =
assert myn != nil assert myn != nil
h(ev, myn) h(ev, myn)
result.addEventListener(toEventName[e], wrapper()) result.addEventListener(toEventName[e], wrapper())
if n.kind == VNodeKind.input:
toFocus = result
proc same(n: VNode, e: Element): bool = proc same(n: VNode, e: Element): bool =
if toTag[n.kind] == e.nodename: if toTag[n.kind] == e.nodename:
@ -84,8 +91,7 @@ proc equals(a, b: VNode): bool =
if a.id != b.id: return false if a.id != b.id: return false
if a.kind == VNodeKind.text: if a.kind == VNodeKind.text:
if a.text != b.text: return false if a.text != b.text: return false
#elif a.childNodes.len != b.childNodes.len: if not sameAttrs(a, b): return false
# return false
# XXX test event listeners here? # XXX test event listeners here?
# --> maybe give nodes a hash? # --> maybe give nodes a hash?
when false: when false:
@ -126,6 +132,7 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
proc dodraw() = proc dodraw() =
let newtree = dorender() let newtree = dorender()
newtree.id = "ROOT" newtree.id = "ROOT"
toFocus = nil
if currentTree == nil: if currentTree == nil:
currentTree = newtree currentTree = newtree
let asdom = vnodeToDom currentTree let asdom = vnodeToDom currentTree
@ -135,6 +142,9 @@ proc dodraw() =
updateElement(nil, olddom, newtree, currentTree) updateElement(nil, olddom, newtree, currentTree)
assert same(newtree, document.getElementById("ROOT")) assert same(newtree, document.getElementById("ROOT"))
currentTree = newtree currentTree = newtree
# now that it's part of the DOM, give it the focus:
if toFocus != nil:
toFocus.focus()
proc redraw*() = proc redraw*() =
# we buffer redraw requests: # we buffer redraw requests:
@ -157,20 +167,6 @@ proc isInt*(s: cstring): bool {.asmNoStackFrame.} =
return s.match(/^[0-9]+$/); return s.match(/^[0-9]+$/);
""" """
var
linkCounter: int
proc link*(id: int): VNode =
result = newVNode(VNodeKind.anchor)
result.setAttr("href", "#")
inc linkCounter
result.setAttr("id", $linkCounter & ":" & $id)
proc link*(action: EventHandler): VNode =
result = newVNode(VNodeKind.anchor)
result.setAttr("href", "#")
addEventListener(result, EventKind.onclick, action)
proc suffix*(s, prefix: cstring): cstring = proc suffix*(s, prefix: cstring): cstring =
if s.startsWith(prefix): if s.startsWith(prefix):
result = s.substr(prefix.len) result = s.substr(prefix.len)
@ -201,6 +197,28 @@ proc setOnchanged*(e: VNode; action: EventHandler) =
proc setOnscroll*(e: VNode; action: EventHandler) = proc setOnscroll*(e: VNode; action: EventHandler) =
onImpl EventKind.onscroll onImpl EventKind.onscroll
var
linkCounter: int
proc link*(id: int): VNode =
result = newVNode(VNodeKind.anchor)
result.setAttr("href", "#")
inc linkCounter
result.setAttr("id", $linkCounter & ":" & $id)
proc link*(action: EventHandler): VNode =
result = newVNode(VNodeKind.anchor)
result.setAttr("href", "#")
result.setOnclick action
proc button*(caption: cstring; action: EventHandler; disabled=false): VNode =
result = newVNode(VNodeKind.button)
result.add text(caption)
if action != nil:
result.setOnClick action
if disabled:
result.setAttr("disabled", "true")
proc select*(choices: openarray[cstring]): VNode = proc select*(choices: openarray[cstring]): VNode =
result = newVNode(VNodeKind.select) result = newVNode(VNodeKind.select)
var i = 0 var i = 0

View file

@ -7,6 +7,7 @@ const
"table", "tr", "td", "th", "thead", "tbody", "table", "tr", "td", "th", "thead", "tbody",
"link", "span", "label", "br" "link", "span", "label", "br"
] ]
StmtContext = ["kout", "inc", "echo", "dec"]
proc getName(n: NimNode): string = proc getName(n: NimNode): string =
case n.kind case n.kind
@ -52,12 +53,13 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
if L > 0: if L > 0:
result[L-1] = tcall2(result[L-1], tmpContext) result[L-1] = tcall2(result[L-1], tmpContext)
of nnkStmtList, nnkStmtListExpr, nnkWhenStmt, nnkIfStmt, nnkCaseStmt, of nnkStmtList, nnkStmtListExpr, nnkWhenStmt, nnkIfStmt, nnkCaseStmt,
nnkVarSection, nnkLetSection, nnkConstSection,
nnkTryStmt, nnkFinally: nnkTryStmt, nnkFinally:
# recurse for every child: # recurse for every child:
result = copyNimNode(n) result = copyNimNode(n)
for x in n: for x in n:
result.add tcall2(x, tmpContext) result.add tcall2(x, tmpContext)
of nnkVarSection, nnkLetSection, nnkConstSection:
result = n
of nnkCallKinds: of nnkCallKinds:
let op = getName(n[0]) let op = getName(n[0])
let idx = find(SupportedTags, op) let idx = find(SupportedTags, op)
@ -87,7 +89,7 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
result.add tmp result.add tmp
else: else:
result.add newCall(bindSym"add", tmpContext, tmp) result.add newCall(bindSym"add", tmpContext, tmp)
elif tmpContext != nil and op != "kout": elif tmpContext != nil and op notin StmtContext:
result = newCall(bindSym"add", tmpContext, n) result = newCall(bindSym"add", tmpContext, n)
else: else:
result = n result = n

View file

@ -9,12 +9,12 @@ type
tdiv, tdiv,
table, tr, td, th, thead, tbody, table, tr, td, th, thead, tbody,
link, span, label, br, select, option, link, span, label, br, select, option,
fieldset, input fieldset, input, button
const const
toTag*: array[VNodeKind, cstring] = [ toTag*: array[VNodeKind, cstring] = [
cstring"#text", "A", "DIV", "TABLE", "TR", "TD", "TH", "THEAD", "TBODY", "LINK", cstring"#text", "A", "DIV", "TABLE", "TR", "TD", "TH", "THEAD", "TBODY", "LINK",
"SPAN", "LABEL", "BR", "SELECT", "OPTION", "FIELDSET", "INPUT" "SPAN", "LABEL", "BR", "SELECT", "OPTION", "FIELDSET", "INPUT", "BUTTON"
] ]
type type
@ -93,6 +93,12 @@ iterator attrs*(n: VNode): (cstring, cstring) =
for i in countup(0, n.attrs.len-2, 2): for i in countup(0, n.attrs.len-2, 2):
yield (n.attrs[i], n.attrs[i+1]) yield (n.attrs[i], n.attrs[i+1])
proc sameAttrs*(a, b: VNode): bool =
if a.attrs.len == b.attrs.len:
result = true
for i in 0 ..< a.attrs.len:
if a.attrs[i] != b.attrs[i]: return false
proc addEventListener*(n: VNode; event: EventKind; handler: EventHandler) = proc addEventListener*(n: VNode; event: EventKind; handler: EventHandler) =
n.events.add((event, handler)) n.events.add((event, handler))