diff --git a/karax.nim b/karax.nim index 888a2e9..ce216f0 100644 --- a/karax.nim +++ b/karax.nim @@ -2,7 +2,10 @@ import dom, vdom, jstrutils -export dom.Element, dom.Event, dom.cloneNode, dom +export dom.Event, dom.cloneNode, dom + +proc len(x: Node): int {.importcpp: "#.childNodes.length".} +proc `[]`(x: Node; idx: int): Element {.importcpp: "#.childNodes[#]".} proc kout*[T](x: T) {.importc: "console.log", varargs.} ## the preferred way of debugging karax applications. @@ -12,28 +15,52 @@ proc `id=`*(e: Node; x: cstring) {.importcpp: "#.id = #", nodecl.} proc class*(e: Node): cstring {.importcpp: "#.className", nodecl.} 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 value*(e: Node): cstring {.importcpp: "#.value", nodecl.} +proc `value=`*(e: Node; v: cstring) {.importcpp: "#.value = #", nodecl.} -proc `disabled=`*(e: Element; v: bool) {.importcpp: "#.disabled = #", nodecl.} +proc `disabled=`*(e: Node; v: bool) {.importcpp: "#.disabled = #", nodecl.} -proc getElementsByClass*(e: Element; name: cstring): seq[Element] {. +proc getElementsByClass*(e: Node; name: cstring): seq[Node] {. importcpp: "#.getElementsByClassName(#)", nodecl.} +proc hasProp(e: Node; prop: cstring): bool {.importcpp: "(#.hasOwnProperty(#))".} +proc rawkey(e: Node): VKey {.importcpp: "#.karaxKey", nodecl.} +proc key*(e: Node): VKey = + if e.hasProp"karaxKey": result = e.rawkey +proc `key=`*(e: Node; x: VKey) {.importcpp: "#.karaxKey = #", nodecl.} + +type + BoundingRect {.importc.} = object + top, bottom, left, right: int + +proc getBoundingClientRect(e: Node): BoundingRect {. + importcpp: "getBoundingClientRect", nodecl.} +proc clientHeight(): int {. + importcpp: "(window.innerHeight || document.documentElement.clientHeight)@", nodecl} +proc clientWidth(): int {. + importcpp: "(window.innerWidth || document.documentElement.clientWidth)@", nodecl} + type Timeout* = ref object var document* {.importc.}: Document - toFocus: Element + toFocus: Node toFocusV: VNode proc setFocus*(n: VNode) = toFocusV = n -proc vnodeToDom(n: VNode): Element = +proc isElementInViewport(el: Node; h: var int): bool = + let rect = el.getBoundingClientRect() + h = rect.bottom - rect.top + result = rect.top >= 0 and rect.left >= 0 and + rect.bottom <= clientHeight() and + rect.right <= clientWidth() + +proc vnodeToDom(n: VNode): Node = if n.kind == VNodeKind.text: - result = cast[Element](document.createTextNode(n.text)) + result = document.createTextNode(n.text) else: result = document.createElement(toTag[n.kind]) for k in n: @@ -45,6 +72,8 @@ proc vnodeToDom(n: VNode): Element = result.id = n.id if n.class != nil: result.class = n.class + if n.key != 0: + result.key = n.key for k, v in attrs(n): if v != nil: result.setAttribute(k, v) @@ -59,13 +88,13 @@ proc vnodeToDom(n: VNode): Element = if n == toFocusV and toFocus.isNil: toFocus = result -proc same(n: VNode, e: Element): bool = +proc same(n: VNode, e: Node): bool = if toTag[n.kind] == e.nodename: result = true if n.kind != VNodeKind.text: - if e.childNodes.len != n.len: return false + if e.len != n.len: return false for i in 0 ..< n.len: - if not same(n[i], cast[Element](e.childNodes[i])): return false + if not same(n[i], e[i]): return false var dorender: proc (): VNode {.closure.} @@ -84,7 +113,7 @@ proc clearTimeout*(t: Timeout) {.importc, nodecl.} #proc getElementsByClassName*(cls: cstring): seq[Element] {.importc: # "document.getElementsByClassName", nodecl.} -proc textContent(e: Element): cstring {. +proc textContent(e: Node): cstring {. importcpp: "#.textContent", nodecl.} proc replaceById(id: cstring; newTree: Node) = @@ -95,6 +124,7 @@ proc replaceById(id: cstring; newTree: Node) = proc equals(a, b: VNode): bool = if a.kind != b.kind: return false if a.id != b.id: return false + if a.key != b.key: return false if a.kind == VNodeKind.text: if a.text != b.text: return false if not sameAttrs(a, b): return false @@ -126,9 +156,7 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = kout toTag[oldNode.kind] assert false for i in 0..min(newLength, oldLength)-1: - updateElement(current, current.childNodes[i], - newNode[i], - oldNode[i]) + updateElement(current, current[i], newNode[i], oldNode[i]) if newLength > oldLength: for i in oldLength..newLength-1: current.appendChild(vnodeToDom(newNode[i])) @@ -153,6 +181,20 @@ proc dodraw() = if toFocus != nil: toFocus.focus() +proc visibleKeys(e: Node; a, b: var VKey; h, count: var int) = + # we only care about nodes that have a key: + var hh = 0 + # do not recurse if there is a 'key' field already: + if e.key != 0: + if isElementInViewport(e, hh): + inc count + inc h, hh + a = min(a, e.key) + b = max(b, e.key) + else: + for i in 0.. + + + + Todo app + + + + + +
+ + + + + + diff --git a/scrollapp.nim b/scrollapp.nim new file mode 100644 index 0000000..25f8998 --- /dev/null +++ b/scrollapp.nim @@ -0,0 +1,146 @@ + +import vdom, karax, karaxdsl, jdict, jstrutils + +type + Filter = enum + all, active, completed + +const + WindowSize = 50 # overestimating it does not harm + +var + entries: seq[(cstring, bool)] + visibleA = 0 + visibleB = WindowSize-1 + middle = WindowSize div 2 + 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.. 0: + #visibleA = a #max(a-diff, 0) + visibleB = min(b+diff+overshoot, entries.len-1) + visibleA = max(a-overshoot, 0) + else: + visibleA = max(a-diff-overshoot, 0) + visibleB = min(b+overshoot, entries.len-1) + # visibleB = min(entries.len-1, visibleB + diff) + # visibleA = max(0, visibleB - WindowSize*2) + #else: + # visibleA = max(0, visibleA + diff) + # visibleB = min(entries.len-1, visibleA + WindowSize - 1) + kout cstring"scrolling ", a, b, diff, visibleA, visibleB +) +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..1000: + entries.add((cstring"Entry " & &i, false)) + init()