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:
var plugins {.exportc.}: seq[(string, proc())] = @[]
@ -14,31 +14,50 @@ when false:
if plugins.len > 0:
plugins[0][1]()
var entries: seq[cstring]
var
entries: seq[cstring]
selectedEntry = -1
proc onTodoEnter(val: cstring) =
entries.add val
proc onclickHandler(ev: Event; n: VNode) =
proc removeHandler(ev: Event; n: VNode) =
let id = suffixAsInt(n.id, "remove:")
#entries.delete(id)
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:
if entryCache.contains(i):
let old = entryCache[i]
return old
when defined(usecache):
if entryCache.contains(i):
let old = entryCache[i]
return old
result = buildHtml(tr) do:
td(id="edit:" & $i, onclick=editHandler):
if selected:
editInput(i, d)
else:
text d
td:
text d
td:
span(id="remove:" & $i, onclick=onclickHandler):
span(id="remove:" & $i, onclick=removeHandler):
text "[remove]"
entryCache[i] = result
when defined(usecache):
entryCache[i] = result
proc createDom(): VNode =
result = buildHtml(tdiv) do:
@ -47,12 +66,20 @@ proc createDom(): VNode =
#realtimeInput("by-name", "", onInput)
#br()
text "todo"
text cstring"todo"
enterInput("todo-input", "", onTodoEnter)
var entriesCount = 0
table(class = "wl"):
for i, d in entries:
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

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 `&`*(a, b: cstring): 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; v: cstring) {.importcpp: "#.value = #", nodecl.}
proc `disabled=`*(e: Element; v: bool) {.importcpp: "#.disabled = #", nodecl.}
proc getElementsByClass*(e: Element; name: cstring): seq[Element] {.
importcpp: "#.getElementsByClassName(#)", nodecl.}
type
Timeout* = ref object
var document* {.importc.}: Document
var
document* {.importc.}: Document
toFocus: Element
proc vnodeToDom(n: VNode): Element =
if n.kind == VNodeKind.text:
@ -37,7 +41,8 @@ proc vnodeToDom(n: VNode): Element =
result.id = n.id
if n.class != nil:
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
for e, h in items(n.events):
proc wrapper(): proc (ev: Event) =
@ -45,6 +50,8 @@ proc vnodeToDom(n: VNode): Element =
assert myn != nil
h(ev, myn)
result.addEventListener(toEventName[e], wrapper())
if n.kind == VNodeKind.input:
toFocus = result
proc same(n: VNode, e: Element): bool =
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.kind == VNodeKind.text:
if a.text != b.text: return false
#elif a.childNodes.len != b.childNodes.len:
# return false
if not sameAttrs(a, b): return false
# XXX test event listeners here?
# --> maybe give nodes a hash?
when false:
@ -126,6 +132,7 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
proc dodraw() =
let newtree = dorender()
newtree.id = "ROOT"
toFocus = nil
if currentTree == nil:
currentTree = newtree
let asdom = vnodeToDom currentTree
@ -135,6 +142,9 @@ proc dodraw() =
updateElement(nil, olddom, newtree, currentTree)
assert same(newtree, document.getElementById("ROOT"))
currentTree = newtree
# now that it's part of the DOM, give it the focus:
if toFocus != nil:
toFocus.focus()
proc redraw*() =
# we buffer redraw requests:
@ -157,20 +167,6 @@ proc isInt*(s: cstring): bool {.asmNoStackFrame.} =
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 =
if s.startsWith(prefix):
result = s.substr(prefix.len)
@ -201,6 +197,28 @@ proc setOnchanged*(e: VNode; action: EventHandler) =
proc setOnscroll*(e: VNode; action: EventHandler) =
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 =
result = newVNode(VNodeKind.select)
var i = 0

View file

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

View file

@ -9,12 +9,12 @@ type
tdiv,
table, tr, td, th, thead, tbody,
link, span, label, br, select, option,
fieldset, input
fieldset, input, button
const
toTag*: array[VNodeKind, cstring] = [
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
@ -93,6 +93,12 @@ iterator attrs*(n: VNode): (cstring, cstring) =
for i in countup(0, n.attrs.len-2, 2):
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) =
n.events.add((event, handler))