diff --git a/example.nim b/example.nim index ab2e48d..83e2c58 100644 --- a/example.nim +++ b/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 diff --git a/jstrutils.nim b/jstrutils.nim index 1c0b7a0..99dbcbb 100644 --- a/jstrutils.nim +++ b/jstrutils.nim @@ -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.} diff --git a/karax.nim b/karax.nim index 060f95d..775ddf1 100644 --- a/karax.nim +++ b/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 diff --git a/karaxdsl.nim b/karaxdsl.nim index 4318fe1..5afe3a2 100644 --- a/karaxdsl.nim +++ b/karaxdsl.nim @@ -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 diff --git a/vdom.nim b/vdom.nim index af5a95c..b25cc55 100644 --- a/vdom.nim +++ b/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))