clean todoapp example
This commit is contained in:
parent
c633e72a8d
commit
d22a1833cc
11 changed files with 713 additions and 95 deletions
|
|
@ -1,8 +1,9 @@
|
||||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
<!DOCTYPE html>
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Todo app</title>
|
<title>Todo app</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body id="body">
|
<body id="body">
|
||||||
|
|
|
||||||
150
example.nim
150
example.nim
|
|
@ -1,30 +1,22 @@
|
||||||
|
|
||||||
import vdom, karax, karaxdsl, jdict, jstrutils
|
import vdom, karax, karaxdsl, jdict, jstrutils
|
||||||
|
|
||||||
when false:
|
type
|
||||||
var plugins {.exportc.}: seq[(string, proc())] = @[]
|
Filter = enum
|
||||||
|
all, active, completed
|
||||||
proc onInput(val: cstring) =
|
|
||||||
kout val
|
|
||||||
if val == "dyn":
|
|
||||||
let body = getElementById("body")
|
|
||||||
body.prepend(tree("script", [("type", "text/javascript"), ("src", "nimcache/dyn.js")]))
|
|
||||||
redraw()
|
|
||||||
kout(plugins.len)
|
|
||||||
if plugins.len > 0:
|
|
||||||
plugins[0][1]()
|
|
||||||
|
|
||||||
var
|
var
|
||||||
entries: seq[cstring]
|
entries: seq[(cstring, bool)]
|
||||||
selectedEntry = -1
|
selectedEntry = -1
|
||||||
|
filter: Filter
|
||||||
|
|
||||||
proc onTodoEnter(val: cstring) =
|
proc onTodoEnter(ev: Event; n: VNode) =
|
||||||
entries.add val
|
entries.add((n.value, false))
|
||||||
|
n.value = ""
|
||||||
|
|
||||||
proc removeHandler(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[id] = (cstring(nil), false)
|
||||||
entries[id] = nil
|
|
||||||
|
|
||||||
proc editHandler(ev: Event; n: VNode) =
|
proc editHandler(ev: Event; n: VNode) =
|
||||||
let id = suffixAsInt(n.id, "edit:")
|
let id = suffixAsInt(n.id, "edit:")
|
||||||
|
|
@ -33,57 +25,111 @@ proc editHandler(ev: Event; n: VNode) =
|
||||||
when defined(usecache):
|
when defined(usecache):
|
||||||
var entryCache = newJDict[int, VNode]()
|
var entryCache = newJDict[int, VNode]()
|
||||||
|
|
||||||
proc editInput(i: int; d: cstring): VNode =
|
proc focusLost(ev: Event; n: VNode) = selectedEntry = -1
|
||||||
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 =
|
proc editEntry(ev: Event; n: VNode) =
|
||||||
|
let id = suffixAsInt(n.id, "todo-edit:")
|
||||||
|
entries[id][0] = n.value
|
||||||
|
selectedEntry = -1
|
||||||
|
|
||||||
|
proc toggleEntry(ev: Event; n: VNode) =
|
||||||
|
let id = suffixAsInt(n.id, "toggle:")
|
||||||
|
entries[id][1] = not entries[id][1]
|
||||||
|
|
||||||
|
proc onAllDone(ev: Event; n: VNode) =
|
||||||
|
entries = @[]
|
||||||
|
selectedEntry = -1
|
||||||
|
|
||||||
|
proc clearCompleted(ev: Event, n: VNode) =
|
||||||
|
for i in 0..<entries.len:
|
||||||
|
if entries[i][1]: entries[i][0] = nil
|
||||||
|
|
||||||
|
proc toClass(completed: bool): cstring =
|
||||||
|
(if completed: cstring"completed" else: cstring"")
|
||||||
|
|
||||||
|
proc toVis(visible: bool): cstring =
|
||||||
|
(if visible: cstring"visible" else: "hidden")
|
||||||
|
|
||||||
|
proc toChecked(checked: bool): cstring =
|
||||||
|
(if checked: cstring"checked" else: cstring(nil))
|
||||||
|
|
||||||
|
proc selected(v: Filter): cstring =
|
||||||
|
(if filter == v: cstring"selected" else: cstring(nil))
|
||||||
|
|
||||||
|
proc createEntry(id: int; d: cstring; completed, selected: bool): VNode =
|
||||||
# implement caching:
|
# implement caching:
|
||||||
when defined(usecache):
|
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):
|
||||||
td(id="edit:" & $i, onclick=editHandler):
|
li(class=toClass(completed)):
|
||||||
if selected:
|
if not selected:
|
||||||
editInput(i, d)
|
tdiv(class = "view"):
|
||||||
else:
|
input(class = "toggle", `type` = "checkbox", checked = toChecked(completed),
|
||||||
|
onclick=toggleEntry, id="toggle:" & &id)
|
||||||
|
label(onDblClick=editHandler, id="edit:" & &id):
|
||||||
text d
|
text d
|
||||||
td:
|
button(class = "destroy", id="remove:" & $id, onclick=removeHandler)
|
||||||
span(id="remove:" & $i, onclick=removeHandler):
|
else:
|
||||||
text "[remove]"
|
input(class = "edit", name = "title", id = "todo-edit:" & &id,
|
||||||
|
onfocusLost = focusLost,
|
||||||
|
onenter = editEntry, value = d)
|
||||||
when defined(usecache):
|
when defined(usecache):
|
||||||
entryCache[i] = result
|
entryCache[i] = result
|
||||||
|
|
||||||
proc createDom(): VNode =
|
proc createDom(): VNode =
|
||||||
result = buildHtml(tdiv) do:
|
result = buildHtml(tdiv(class="todomvc-wrapper")):
|
||||||
tdiv(id = "sheader"):
|
section(class = "todoapp"):
|
||||||
#text "plugin"
|
header(class = "header"):
|
||||||
#realtimeInput("by-name", "", onInput)
|
h1:
|
||||||
#br()
|
text "todos"
|
||||||
|
input(class = "new-todo", placeholder="What needs to be done?", name = "newTodo",
|
||||||
text cstring"todo"
|
onenter = onTodoEnter)
|
||||||
enterInput("todo-input", "", onTodoEnter)
|
section(class = "main"):
|
||||||
|
input(class = "toggle-all", `type` = "checkbox", name = "toggle")
|
||||||
|
label(`for` = "toggle-all", onclick = onAllDone):
|
||||||
|
text "Mark all as complete"
|
||||||
var entriesCount = 0
|
var entriesCount = 0
|
||||||
table(class = "wl"):
|
var completedCount = 0
|
||||||
for i, d in entries:
|
ul(class = "todo-list"):
|
||||||
if d != nil:
|
for i, d in pairs(entries):
|
||||||
createEntry(i, d, i == selectedEntry)
|
if d[0] != nil:
|
||||||
|
let b = case filter
|
||||||
|
of all: true
|
||||||
|
of active: not d[1]
|
||||||
|
of completed: d[1]
|
||||||
|
if b:
|
||||||
|
createEntry(i, d[0], d[1], i == selectedEntry)
|
||||||
|
inc completedCount, ord(d[1])
|
||||||
inc entriesCount
|
inc entriesCount
|
||||||
tdiv(id = "footer"):
|
footer(class = "footer"):
|
||||||
text cstring"Entries: " & &entriesCount
|
span(class = "todo-count"):
|
||||||
proc onAllDone(ev: Event; n: VNode) =
|
strong:
|
||||||
entries = @[]
|
text(&entriesCount)
|
||||||
selectedEntry = -1
|
text cstring" item" & &(if entriesCount != 1: "s left" else: " left")
|
||||||
button "All done!", onAllDone, entriesCount == 0 or selectedEntry >= 0
|
ul(class = "filters"):
|
||||||
|
li:
|
||||||
|
anchor(class = selected(all), href = "#/"):
|
||||||
|
text "All"
|
||||||
|
li:
|
||||||
|
anchor(class = selected(active), href = "#/active"):
|
||||||
|
text "Active"
|
||||||
|
li:
|
||||||
|
anchor(class = selected(completed), href = "#/completed"):
|
||||||
|
text "Completed"
|
||||||
|
button(class = "clear-completed", onclick = clearCompleted):
|
||||||
|
text "Clear completed (" & &completedCount & ")"
|
||||||
|
|
||||||
|
setOnHashChange(proc(hash: cstring) =
|
||||||
|
if hash == cstring"#/": filter = all
|
||||||
|
elif hash == cstring"#/completed": filter = completed
|
||||||
|
elif hash == cstring"#/active": filter = active
|
||||||
|
)
|
||||||
setRenderer createDom
|
setRenderer createDom
|
||||||
|
|
||||||
proc onload(session: cstring) {.exportc.} =
|
proc onload(session: cstring) {.exportc.} =
|
||||||
for i in 0..10_000:
|
for i in 0..1: # 0_000:
|
||||||
entries.add("Entry " & $i)
|
entries.add((cstring"Entry " & &i, false))
|
||||||
init()
|
init()
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,5 @@ proc newJDict*[K, V](): JDict[K, V] {.importcpp: "{@}".}
|
||||||
|
|
||||||
proc contains*[K, V](d: JDict[K, V], k: K): bool {.importcpp: "#.hasOwnProperty(#)".}
|
proc contains*[K, V](d: JDict[K, V], k: K): bool {.importcpp: "#.hasOwnProperty(#)".}
|
||||||
|
|
||||||
|
proc del*[K, V](d: JDict[K, V], k: K) {.importcpp: "delete #[#]".}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
proc split*(s, sep: cstring): seq[cstring] {.importcpp, nodecl.}
|
proc split*(s, sep: cstring): seq[cstring] {.importcpp, nodecl.}
|
||||||
|
|
||||||
|
proc split*(s, sep: cstring; max: int): seq[cstring] {.importcpp, nodecl.}
|
||||||
|
|
||||||
proc startsWith*(a, b: cstring): bool {.importcpp: "startsWith", nodecl.}
|
proc startsWith*(a, b: cstring): bool {.importcpp: "startsWith", nodecl.}
|
||||||
proc contains*(a, b: cstring): bool {.importcpp: "(#.indexOf(#)>=0)", nodecl.}
|
proc contains*(a, b: cstring): bool {.importcpp: "(#.indexOf(#)>=0)", nodecl.}
|
||||||
proc substr*(s: cstring; start: int): cstring {.importcpp: "substr", nodecl.}
|
proc substr*(s: cstring; start: int): cstring {.importcpp: "substr", nodecl.}
|
||||||
|
|
@ -11,3 +13,6 @@ proc substr*(s: cstring; start, length: int): cstring {.importcpp: "substr", nod
|
||||||
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.}
|
proc `&`*(s: int): cstring {.importcpp: "((#)+'')", nodecl.}
|
||||||
|
proc `&`*(s: bool): cstring {.importcpp: "((#)+'')", nodecl.}
|
||||||
|
|
||||||
|
proc `&`*(s: cstring): cstring {.importcpp: "(#)", nodecl.}
|
||||||
|
|
|
||||||
61
karax.nim
61
karax.nim
|
|
@ -42,6 +42,7 @@ proc vnodeToDom(n: VNode): Element =
|
||||||
if n.class != nil:
|
if n.class != nil:
|
||||||
result.class = n.class
|
result.class = n.class
|
||||||
for k, v in attrs(n):
|
for k, v in attrs(n):
|
||||||
|
if v != nil:
|
||||||
result.setAttribute(k, v)
|
result.setAttribute(k, v)
|
||||||
let myn = n
|
let myn = n
|
||||||
for e, h in items(n.events):
|
for e, h in items(n.events):
|
||||||
|
|
@ -90,9 +91,10 @@ proc replaceById(id: cstring; newTree: Node) =
|
||||||
proc equals(a, b: VNode): bool =
|
proc equals(a, b: VNode): bool =
|
||||||
if a.kind != b.kind: return false
|
if a.kind != b.kind: return false
|
||||||
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
|
||||||
if not sameAttrs(a, b): return false
|
if not sameAttrs(a, b): return false
|
||||||
|
if a.class != b.class: 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:
|
||||||
|
|
@ -196,6 +198,9 @@ template onImpl(s) {.dirty.} =
|
||||||
proc setOnclick*(e: VNode; action: EventHandler) =
|
proc setOnclick*(e: VNode; action: EventHandler) =
|
||||||
onImpl EventKind.onclick
|
onImpl EventKind.onclick
|
||||||
|
|
||||||
|
proc setOnDblclick*(e: VNode; action: EventHandler) =
|
||||||
|
onImpl EventKind.onclick
|
||||||
|
|
||||||
proc setOnfocuslost*(e: VNode; action: EventHandler) =
|
proc setOnfocuslost*(e: VNode; action: EventHandler) =
|
||||||
onImpl EventKind.onblur
|
onImpl EventKind.onblur
|
||||||
|
|
||||||
|
|
@ -205,6 +210,14 @@ proc setOnchanged*(e: VNode; action: EventHandler) =
|
||||||
proc setOnscroll*(e: VNode; action: EventHandler) =
|
proc setOnscroll*(e: VNode; action: EventHandler) =
|
||||||
onImpl EventKind.onscroll
|
onImpl EventKind.onscroll
|
||||||
|
|
||||||
|
proc setOnHashChange*(action: proc (hashPart: cstring)) =
|
||||||
|
var onhashChange {.importc: "window.onhashchange".}: proc()
|
||||||
|
var hashPart {.importc: "window.location.hash".}: cstring
|
||||||
|
proc wrapper() =
|
||||||
|
action(hashPart)
|
||||||
|
redraw()
|
||||||
|
onhashchange = wrapper
|
||||||
|
|
||||||
var
|
var
|
||||||
linkCounter: int
|
linkCounter: int
|
||||||
|
|
||||||
|
|
@ -219,6 +232,7 @@ proc link*(action: EventHandler): VNode =
|
||||||
result.setAttr("href", "#")
|
result.setAttr("href", "#")
|
||||||
result.setOnclick action
|
result.setOnclick action
|
||||||
|
|
||||||
|
when false:
|
||||||
proc button*(caption: cstring; action: EventHandler; disabled=false): VNode =
|
proc button*(caption: cstring; action: EventHandler; disabled=false): VNode =
|
||||||
result = newVNode(VNodeKind.button)
|
result = newVNode(VNodeKind.button)
|
||||||
result.add text(caption)
|
result.add text(caption)
|
||||||
|
|
@ -299,29 +313,36 @@ proc tr*(kids: varargs[VNode]): VNode =
|
||||||
proc getAttr(e: Element; key: cstring): cstring {.
|
proc getAttr(e: Element; key: cstring): cstring {.
|
||||||
importcpp: "#.getAttribute(#)", nodecl.}
|
importcpp: "#.getAttribute(#)", nodecl.}
|
||||||
|
|
||||||
proc realtimeInput*(id, val: cstring; changed: proc(value: cstring)): VNode =
|
template nativeValue(ev): cstring = cast[Element](ev.target).value
|
||||||
|
template setNativeValue(ev, val) = cast[Element](ev.target).value = val
|
||||||
|
|
||||||
|
proc realtimeInput*(val: cstring; changed: EventHandler): VNode =
|
||||||
#let oldElem = getElementById(id)
|
#let oldElem = getElementById(id)
|
||||||
#if oldElem != nil: return oldElem
|
#if oldElem != nil: return oldElem
|
||||||
#let newVal = if oldElem.isNil: val else: $oldElem.value
|
#let newVal = if oldElem.isNil: val else: $oldElem.value
|
||||||
var timer: Timeout
|
var timer: Timeout
|
||||||
proc wrapper() =
|
|
||||||
changed(document.getElementById(id).value)
|
|
||||||
redraw()
|
|
||||||
proc onkeyup(ev: Event; n: VNode) =
|
proc onkeyup(ev: Event; n: VNode) =
|
||||||
|
proc wrapper() =
|
||||||
|
n.value = nativeValue(ev)
|
||||||
|
changed(ev, n)
|
||||||
|
setNativeValue(ev, n.value)
|
||||||
|
redraw()
|
||||||
|
|
||||||
if timer != nil: clearTimeout(timer)
|
if timer != nil: clearTimeout(timer)
|
||||||
timer = setTimeout(wrapper, 400)
|
timer = setTimeout(wrapper, 400)
|
||||||
result = tree(VNodeKind.input, [(cstring"type", cstring"text")])
|
result = tree(VNodeKind.input, [(cstring"type", cstring"text")])
|
||||||
result.id = id
|
|
||||||
result.value = val
|
result.value = val
|
||||||
result.addEventListener(EventKind.onkeyup, onkeyup)
|
result.addEventListener(EventKind.onkeyup, onkeyup)
|
||||||
|
|
||||||
proc enterInput*(id, val: cstring; onenter: proc(value: cstring)): VNode =
|
proc enterInput*(id, val: cstring; onenter: EventHandler): VNode =
|
||||||
#let oldElem = getElementById(id)
|
#let oldElem = getElementById(id)
|
||||||
#if oldElem != nil: return oldElem
|
#if oldElem != nil: return oldElem
|
||||||
#let newVal = if oldElem.isNil: val else: $oldElem.value
|
#let newVal = if oldElem.isNil: val else: $oldElem.value
|
||||||
proc onkeyup(ev: Event; n: VNode) =
|
proc onkeyup(ev: Event; n: VNode) =
|
||||||
if ev.keyCode == 13:
|
if ev.keyCode == 13:
|
||||||
onenter(document.getElementById(id).value)
|
n.value = nativeValue(ev)
|
||||||
|
onenter(ev, n)
|
||||||
|
setNativeValue(ev, n.value)
|
||||||
redraw()
|
redraw()
|
||||||
|
|
||||||
result = tree(VNodeKind.input, [(cstring"type", cstring"text")])
|
result = tree(VNodeKind.input, [(cstring"type", cstring"text")])
|
||||||
|
|
@ -329,6 +350,14 @@ proc enterInput*(id, val: cstring; onenter: proc(value: cstring)): VNode =
|
||||||
result.value = val
|
result.value = val
|
||||||
result.addEventListener(EventKind.onkeyup, onkeyup)
|
result.addEventListener(EventKind.onkeyup, onkeyup)
|
||||||
|
|
||||||
|
proc setOnEnter*(n: VNode; onenter: EventHandler) =
|
||||||
|
proc onkeyup(ev: Event; n: VNode) =
|
||||||
|
if ev.keyCode == 13:
|
||||||
|
n.value = nativeValue(ev)
|
||||||
|
onenter(ev, n)
|
||||||
|
setNativeValue(ev, n.value)
|
||||||
|
redraw()
|
||||||
|
n.addEventListener(EventKind.onkeyup, onkeyup)
|
||||||
|
|
||||||
proc ajax(meth, url: cstring; headers: openarray[(cstring, cstring)];
|
proc ajax(meth, url: cstring; headers: openarray[(cstring, cstring)];
|
||||||
data: cstring;
|
data: cstring;
|
||||||
|
|
@ -366,7 +395,7 @@ proc ajaxGet*(url: string; headers: openarray[(cstring, cstring)];
|
||||||
proc setupErrorHandler*(useAlert=false) =
|
proc setupErrorHandler*(useAlert=false) =
|
||||||
## Installs an error handler that transforms native JS unhandled
|
## Installs an error handler that transforms native JS unhandled
|
||||||
## exceptions into Nim based stack traces. If `useAlert` is false,
|
## exceptions into Nim based stack traces. If `useAlert` is false,
|
||||||
## the error message it put into the console, otherwise `alert`
|
## the error message is put into the console, otherwise `alert`
|
||||||
## is called.
|
## is called.
|
||||||
proc stackTraceAsCstring(): cstring = cstring(getStackTrace())
|
proc stackTraceAsCstring(): cstring = cstring(getStackTrace())
|
||||||
{.emit: """
|
{.emit: """
|
||||||
|
|
@ -381,3 +410,17 @@ proc setupErrorHandler*(useAlert=false) =
|
||||||
};""".}
|
};""".}
|
||||||
|
|
||||||
{.pop.}
|
{.pop.}
|
||||||
|
|
||||||
|
when false:
|
||||||
|
var plugins {.exportc.}: seq[(string, proc())] = @[]
|
||||||
|
|
||||||
|
proc onInput(val: cstring) =
|
||||||
|
kout val
|
||||||
|
if val == "dyn":
|
||||||
|
let body = getElementById("body")
|
||||||
|
body.prepend(tree("script", [("type", "text/javascript"), ("src", "nimcache/dyn.js")]))
|
||||||
|
redraw()
|
||||||
|
kout(plugins.len)
|
||||||
|
if plugins.len > 0:
|
||||||
|
plugins[0][1]()
|
||||||
|
|
||||||
|
|
|
||||||
32
karaxdsl.nim
32
karaxdsl.nim
|
|
@ -1,12 +1,8 @@
|
||||||
|
|
||||||
import macros, karax, vdom
|
import macros, karax, vdom
|
||||||
from strutils import startsWith
|
from strutils import startsWith, toLowerAscii
|
||||||
|
|
||||||
const
|
const
|
||||||
SupportedTags* = ["tdiv", "div",
|
|
||||||
"table", "tr", "td", "th", "thead", "tbody",
|
|
||||||
"link", "span", "label", "br"
|
|
||||||
]
|
|
||||||
StmtContext = ["kout", "inc", "echo", "dec"]
|
StmtContext = ["kout", "inc", "echo", "dec"]
|
||||||
|
|
||||||
proc getName(n: NimNode): string =
|
proc getName(n: NimNode): string =
|
||||||
|
|
@ -23,11 +19,13 @@ proc getName(n: NimNode): string =
|
||||||
#echo repr n
|
#echo repr n
|
||||||
expectKind(n, nnkIdent)
|
expectKind(n, nnkIdent)
|
||||||
|
|
||||||
var gid {.compileTime.}: int
|
|
||||||
|
|
||||||
proc newDotAsgn(tmp: NimNode, key: string, x: NimNode): NimNode =
|
proc newDotAsgn(tmp: NimNode, key: string, x: NimNode): NimNode =
|
||||||
result = newTree(nnkAsgn, newDotExpr(tmp, newIdentNode key), x)
|
result = newTree(nnkAsgn, newDotExpr(tmp, newIdentNode key), x)
|
||||||
|
|
||||||
|
proc isTag(s: string): bool =
|
||||||
|
for i in VNodeKind.low.succ..VNodeKind.high:
|
||||||
|
if $i == s: return true
|
||||||
|
|
||||||
proc tcall2(n, tmpContext: NimNode): NimNode =
|
proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||||
# we need to distinguish statement and expression contexts:
|
# we need to distinguish statement and expression contexts:
|
||||||
# every call statement 's' needs to be transformed to 'dest.add s'.
|
# every call statement 's' needs to be transformed to 'dest.add s'.
|
||||||
|
|
@ -62,11 +60,8 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||||
result = n
|
result = n
|
||||||
of nnkCallKinds:
|
of nnkCallKinds:
|
||||||
let op = getName(n[0])
|
let op = getName(n[0])
|
||||||
let idx = find(SupportedTags, op)
|
if isTag(op):
|
||||||
if idx >= 0:
|
let tmp = genSym(nskLet, "tmp")
|
||||||
let tag = SupportedTags[idx]
|
|
||||||
inc gid
|
|
||||||
let tmp = genSym(nskLet, "tmp" & $gid)
|
|
||||||
result = newTree(
|
result = newTree(
|
||||||
if tmpContext == nil: nnkStmtListExpr else: nnkStmtList,
|
if tmpContext == nil: nnkStmtListExpr else: nnkStmtList,
|
||||||
newLetStmt(tmp, newCall(bindSym"tree", newDotExpr(bindSym"VNodeKind", n[0]))))
|
newLetStmt(tmp, newCall(bindSym"tree", newDotExpr(bindSym"VNodeKind", n[0]))))
|
||||||
|
|
@ -77,14 +72,14 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||||
let key = getName x[0]
|
let key = getName x[0]
|
||||||
if key.startsWith("on"):
|
if key.startsWith("on"):
|
||||||
result.add newCall(!("set" & key), tmp, x[1])
|
result.add newCall(!("set" & key), tmp, x[1])
|
||||||
elif key == "id" or key == "class":
|
elif key == "id" or key == "class" or key == "value":
|
||||||
result.add newDotAsgn(tmp, key, x[1])
|
result.add newDotAsgn(tmp, key, x[1])
|
||||||
else:
|
else:
|
||||||
result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1])
|
result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1])
|
||||||
else:
|
else:
|
||||||
result.add tcall2(x, tmp)
|
result.add tcall2(x, tmp)
|
||||||
if tag == "link":
|
#if tag == "link":
|
||||||
result.add newCall(bindSym"setAttr", tmp, newLit"href", newLit"#")
|
# result.add newCall(bindSym"setAttr", tmp, newLit"href", newLit"#")
|
||||||
if tmpContext == nil:
|
if tmpContext == nil:
|
||||||
result.add tmp
|
result.add tmp
|
||||||
else:
|
else:
|
||||||
|
|
@ -98,7 +93,12 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||||
|
|
||||||
macro buildHtml*(tag, children: untyped): VNode =
|
macro buildHtml*(tag, children: untyped): VNode =
|
||||||
expectKind children, nnkDo
|
expectKind children, nnkDo
|
||||||
let call = newCall(tag, body(children))
|
var call: NimNode
|
||||||
|
if tag.kind in nnkCallKinds:
|
||||||
|
call = tag
|
||||||
|
else:
|
||||||
|
call = newCall(tag)
|
||||||
|
call.add body(children)
|
||||||
result = tcall2(call, nil)
|
result = tcall2(call, nil)
|
||||||
when defined(debugKaraxDsl):
|
when defined(debugKaraxDsl):
|
||||||
echo repr result
|
echo repr result
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,5 @@ Karax is a prototype for developing single page applications in Nim.
|
||||||
|
|
||||||
To try it out, run::
|
To try it out, run::
|
||||||
|
|
||||||
nim js example.nim
|
nim js todoapp.nim
|
||||||
open example.html
|
open todoapp.html
|
||||||
|
|
|
||||||
378
style.css
Normal file
378
style.css
Normal file
|
|
@ -0,0 +1,378 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todomvc-wrapper {
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: none;
|
||||||
|
font-size: 100%;
|
||||||
|
vertical-align: baseline;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
color: inherit;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-font-smoothing: antialiased;
|
||||||
|
font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.4em;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #4d4d4d;
|
||||||
|
min-width: 230px;
|
||||||
|
max-width: 550px;
|
||||||
|
margin: 0 auto;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-font-smoothing: antialiased;
|
||||||
|
font-smoothing: antialiased;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input[type="checkbox"] {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp {
|
||||||
|
background: #fff;
|
||||||
|
margin: 130px 0 40px 0;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
|
||||||
|
0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp input::-webkit-input-placeholder {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp input::-moz-placeholder {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp input::input-placeholder {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp h1 {
|
||||||
|
position: absolute;
|
||||||
|
top: -155px;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 100px;
|
||||||
|
font-weight: 100;
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(175, 47, 47, 0.15);
|
||||||
|
-webkit-text-rendering: optimizeLegibility;
|
||||||
|
-moz-text-rendering: optimizeLegibility;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-todo,
|
||||||
|
.edit {
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 24px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
line-height: 1.4em;
|
||||||
|
border: 0;
|
||||||
|
outline: none;
|
||||||
|
color: inherit;
|
||||||
|
padding: 6px;
|
||||||
|
border: 1px solid #999;
|
||||||
|
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-font-smoothing: antialiased;
|
||||||
|
font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-todo {
|
||||||
|
padding: 16px 16px 16px 60px;
|
||||||
|
border: none;
|
||||||
|
background: rgba(0, 0, 0, 0.003);
|
||||||
|
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
border-top: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
label[for='toggle-all'] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-all {
|
||||||
|
position: absolute;
|
||||||
|
top: -55px;
|
||||||
|
left: -12px;
|
||||||
|
width: 60px;
|
||||||
|
height: 34px;
|
||||||
|
text-align: center;
|
||||||
|
border: none; /* Mobile Safari */
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-all:before {
|
||||||
|
content: '>';
|
||||||
|
font-size: 22px;
|
||||||
|
color: #e6e6e6;
|
||||||
|
padding: 10px 27px 10px 27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-all:checked:before {
|
||||||
|
color: #737373;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li {
|
||||||
|
position: relative;
|
||||||
|
font-size: 24px;
|
||||||
|
border-bottom: 1px solid #ededed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.editing {
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.editing .edit {
|
||||||
|
display: block;
|
||||||
|
width: 506px;
|
||||||
|
padding: 13px 17px 12px 17px;
|
||||||
|
margin: 0 0 0 43px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.editing .view {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .toggle {
|
||||||
|
text-align: center;
|
||||||
|
width: 40px;
|
||||||
|
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||||
|
height: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto 0;
|
||||||
|
border: none; /* Mobile Safari */
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .toggle:after {
|
||||||
|
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .toggle:checked:after {
|
||||||
|
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li label {
|
||||||
|
white-space: pre-line;
|
||||||
|
word-break: break-all;
|
||||||
|
padding: 15px 60px 15px 15px;
|
||||||
|
margin-left: 45px;
|
||||||
|
display: block;
|
||||||
|
line-height: 1.2;
|
||||||
|
transition: color 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.completed label {
|
||||||
|
color: #d9d9d9;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .destroy {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin: auto 0;
|
||||||
|
font-size: 30px;
|
||||||
|
color: #cc9a9a;
|
||||||
|
margin-bottom: 11px;
|
||||||
|
transition: color 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .destroy:hover {
|
||||||
|
color: #af5b5e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .destroy:after {
|
||||||
|
content: 'x';
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li:hover .destroy {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.editing:last-child {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
color: #777;
|
||||||
|
padding: 10px 15px;
|
||||||
|
height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-top: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 50px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
|
||||||
|
0 8px 0 -3px #f6f6f6,
|
||||||
|
0 9px 1px -3px rgba(0, 0, 0, 0.2),
|
||||||
|
0 16px 0 -6px #f6f6f6,
|
||||||
|
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-count {
|
||||||
|
float: left;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-count strong {
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters li {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters li a {
|
||||||
|
color: inherit;
|
||||||
|
margin: 3px;
|
||||||
|
padding: 3px 7px;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters li a.selected,
|
||||||
|
.filters li a:hover {
|
||||||
|
border-color: rgba(175, 47, 47, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters li a.selected {
|
||||||
|
border-color: rgba(175, 47, 47, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-completed,
|
||||||
|
html .clear-completed:active {
|
||||||
|
float: right;
|
||||||
|
position: relative;
|
||||||
|
line-height: 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-completed:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
margin: 65px auto 0;
|
||||||
|
color: #bfbfbf;
|
||||||
|
/* font-size: 10px; */
|
||||||
|
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info p {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Hack to remove background from Mobile Safari.
|
||||||
|
Can't use it globally since it destroys checkboxes in Firefox
|
||||||
|
*/
|
||||||
|
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||||
|
.toggle-all,
|
||||||
|
.todo-list li .toggle {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .toggle {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-all {
|
||||||
|
-webkit-transform: rotate(90deg);
|
||||||
|
transform: rotate(90deg);
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 430px) {
|
||||||
|
.footer {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
todoapp.html
Normal file
19
todoapp.html
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Todo app</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="body">
|
||||||
|
|
||||||
|
<div id="ROOT" />
|
||||||
|
|
||||||
|
<script type="text/javascript" src="nimcache/todoapp.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
onload(window.location.pathname.split('/')[1])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
121
todoapp.nim
Normal file
121
todoapp.nim
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
|
||||||
|
import vdom, karax, karaxdsl, jdict, jstrutils
|
||||||
|
|
||||||
|
type
|
||||||
|
Filter = enum
|
||||||
|
all, active, completed
|
||||||
|
|
||||||
|
var
|
||||||
|
entries: seq[(cstring, bool)]
|
||||||
|
selectedEntry = -1
|
||||||
|
filter: Filter
|
||||||
|
|
||||||
|
proc onTodoEnter(ev: Event; n: VNode) =
|
||||||
|
entries.add((n.value, false))
|
||||||
|
n.value = ""
|
||||||
|
|
||||||
|
proc removeHandler(ev: Event; n: VNode) =
|
||||||
|
let id = suffixAsInt(n.id, "remove:")
|
||||||
|
entries[id] = (cstring(nil), false)
|
||||||
|
|
||||||
|
proc editHandler(ev: Event; n: VNode) =
|
||||||
|
let id = suffixAsInt(n.id, "edit:")
|
||||||
|
selectedEntry = id
|
||||||
|
|
||||||
|
proc focusLost(ev: Event; n: VNode) = selectedEntry = -1
|
||||||
|
|
||||||
|
proc editEntry(ev: Event; n: VNode) =
|
||||||
|
let id = suffixAsInt(n.id, "todo-edit:")
|
||||||
|
entries[id][0] = n.value
|
||||||
|
selectedEntry = -1
|
||||||
|
|
||||||
|
proc toggleEntry(ev: Event; n: VNode) =
|
||||||
|
let id = suffixAsInt(n.id, "toggle:")
|
||||||
|
entries[id][1] = not entries[id][1]
|
||||||
|
|
||||||
|
proc onAllDone(ev: Event; n: VNode) =
|
||||||
|
entries = @[]
|
||||||
|
selectedEntry = -1
|
||||||
|
|
||||||
|
proc clearCompleted(ev: Event, n: VNode) =
|
||||||
|
for i in 0..<entries.len:
|
||||||
|
if entries[i][1]: entries[i][0] = nil
|
||||||
|
|
||||||
|
proc toClass(completed: bool): cstring =
|
||||||
|
(if completed: cstring"completed" else: cstring(nil))
|
||||||
|
|
||||||
|
proc toChecked(checked: bool): cstring =
|
||||||
|
(if checked: cstring"checked" else: cstring(nil))
|
||||||
|
|
||||||
|
proc selected(v: Filter): cstring =
|
||||||
|
(if filter == v: cstring"selected" else: cstring(nil))
|
||||||
|
|
||||||
|
proc createEntry(id: int; d: cstring; completed, selected: bool): VNode =
|
||||||
|
result = buildHtml(tr):
|
||||||
|
li(class=toClass(completed)):
|
||||||
|
if not selected:
|
||||||
|
tdiv(class = "view"):
|
||||||
|
input(class = "toggle", `type` = "checkbox", checked = toChecked(completed),
|
||||||
|
onclick=toggleEntry, id="toggle:" & &id)
|
||||||
|
label(onDblClick=editHandler, id="edit:" & &id):
|
||||||
|
text d
|
||||||
|
button(class = "destroy", id="remove:" & $id, onclick=removeHandler)
|
||||||
|
else:
|
||||||
|
input(class = "edit", name = "title", id = "todo-edit:" & &id,
|
||||||
|
onfocusLost = focusLost,
|
||||||
|
onenter = editEntry, value = d)
|
||||||
|
|
||||||
|
proc createDom(): VNode =
|
||||||
|
result = buildHtml(tdiv(class="todomvc-wrapper")):
|
||||||
|
section(class = "todoapp"):
|
||||||
|
header(class = "header"):
|
||||||
|
h1:
|
||||||
|
text "todos"
|
||||||
|
input(class = "new-todo", placeholder="What needs to be done?", name = "newTodo",
|
||||||
|
onenter = onTodoEnter)
|
||||||
|
section(class = "main"):
|
||||||
|
input(class = "toggle-all", `type` = "checkbox", name = "toggle")
|
||||||
|
label(`for` = "toggle-all", onclick = onAllDone):
|
||||||
|
text "Mark all as complete"
|
||||||
|
var entriesCount = 0
|
||||||
|
var completedCount = 0
|
||||||
|
ul(class = "todo-list"):
|
||||||
|
for i, d in pairs(entries):
|
||||||
|
if d[0] != nil:
|
||||||
|
let b = case filter
|
||||||
|
of all: true
|
||||||
|
of active: not d[1]
|
||||||
|
of completed: d[1]
|
||||||
|
if b:
|
||||||
|
createEntry(i, d[0], d[1], i == selectedEntry)
|
||||||
|
inc completedCount, ord(d[1])
|
||||||
|
inc entriesCount
|
||||||
|
footer(class = "footer"):
|
||||||
|
span(class = "todo-count"):
|
||||||
|
strong:
|
||||||
|
text(&entriesCount)
|
||||||
|
text cstring" item" & &(if entriesCount != 1: "s left" else: " left")
|
||||||
|
ul(class = "filters"):
|
||||||
|
li:
|
||||||
|
anchor(class = selected(all), href = "#/"):
|
||||||
|
text "All"
|
||||||
|
li:
|
||||||
|
anchor(class = selected(active), href = "#/active"):
|
||||||
|
text "Active"
|
||||||
|
li:
|
||||||
|
anchor(class = selected(completed), href = "#/completed"):
|
||||||
|
text "Completed"
|
||||||
|
button(class = "clear-completed", onclick = clearCompleted):
|
||||||
|
text "Clear completed (" & &completedCount & ")"
|
||||||
|
|
||||||
|
setOnHashChange(proc(hash: cstring) =
|
||||||
|
if hash == cstring"#/": filter = all
|
||||||
|
elif hash == cstring"#/completed": filter = completed
|
||||||
|
elif hash == cstring"#/active": filter = active
|
||||||
|
)
|
||||||
|
setRenderer createDom
|
||||||
|
|
||||||
|
proc onload(session: cstring) {.exportc.} =
|
||||||
|
for i in 0..1: # 0_000:
|
||||||
|
entries.add((cstring"Entry " & &i, false))
|
||||||
|
init()
|
||||||
11
vdom.nim
11
vdom.nim
|
|
@ -9,21 +9,24 @@ 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, button
|
fieldset, input, button, ul, li, section, header, footer,
|
||||||
|
h1, h2, h3, h4, h5, strong
|
||||||
|
|
||||||
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", "BUTTON"
|
"SPAN", "LABEL", "BR", "SELECT", "OPTION", "FIELDSET", "INPUT", "BUTTON", "UL",
|
||||||
|
"LI", "SECTION", "HEADER", "FOOTER", "H1", "H2", "H3", "H4", "H5", "STRONG"
|
||||||
]
|
]
|
||||||
|
|
||||||
type
|
type
|
||||||
EventKind* {.pure.} = enum
|
EventKind* {.pure.} = enum
|
||||||
onclick, onkeyup, onkeydown, onkeypressed, onblur, onchange, onscroll
|
onclick, ondblclick, onkeyup, onkeydown, onkeypressed, onblur, onchange, onscroll
|
||||||
|
|
||||||
const
|
const
|
||||||
toEventName*: array[EventKind, cstring] = [
|
toEventName*: array[EventKind, cstring] = [
|
||||||
cstring"click", "keyup", "keydown", "keypressed", "blur", "change", "scroll"
|
cstring"click", "dblclick", "keyup", "keydown", "keypressed", "blur",
|
||||||
|
"change", "scroll"
|
||||||
]
|
]
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue