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