todo app: more features
This commit is contained in:
parent
8da1ac07c0
commit
44c7015a26
5 changed files with 90 additions and 36 deletions
55
example.nim
55
example.nim
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.}
|
||||
|
|
|
|||
54
karax.nim
54
karax.nim
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
10
vdom.nim
10
vdom.nim
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue