summary refs log tree commit diff stats
path: root/tools/dochack/karax.nim
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2016-09-08 21:57:03 +0200
committerAndreas Rumpf <rumpf_a@web.de>2016-09-08 21:57:27 +0200
commit083b31b47314c1aa70b2726a9b7d0a3c942c2bd8 (patch)
tree206e861b8e13037ec31907db52a50517f0342fcb /tools/dochack/karax.nim
parent07fcce6e631dfc07517e0cc1a2918954cd647f5f (diff)
downloadNim-083b31b47314c1aa70b2726a9b7d0a3c942c2bd8.tar.gz
docgen: group by type feature
Diffstat (limited to 'tools/dochack/karax.nim')
-rw-r--r--tools/dochack/karax.nim338
1 files changed, 338 insertions, 0 deletions
diff --git a/tools/dochack/karax.nim b/tools/dochack/karax.nim
new file mode 100644
index 000000000..d7d9a059a
--- /dev/null
+++ b/tools/dochack/karax.nim
@@ -0,0 +1,338 @@
+# Simple lib to write JS UIs
+
+import dom
+
+export dom.Element, dom.Event, dom.cloneNode, dom
+
+proc kout*[T](x: T) {.importc: "console.log", varargs.}
+  ## the preferred way of debugging karax applications.
+
+proc id*(e: Node): cstring {.importcpp: "#.id", nodecl.}
+proc `id=`*(e: Node; x: cstring) {.importcpp: "#.id = #", nodecl.}
+proc className*(e: Node): cstring {.importcpp: "#.className", nodecl.}
+proc `className=`*(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 getElementsByClass*(e: Element; name: cstring): seq[Element] {.importcpp: "#.getElementsByClassName(#)", nodecl.}
+
+type
+  EventHandler* = proc(ev: Event)
+  EventHandlerId* = proc(ev: Event; id: int)
+
+  Timeout* = ref object
+
+var document* {.importc.}: Document
+
+var
+  dorender: proc (): Element {.closure.}
+  drawTimeout: Timeout
+  currentTree: Element
+
+proc setRenderer*(renderer: proc (): Element) =
+  dorender = renderer
+
+proc setTimeout*(action: proc(); ms: int): Timeout {.importc, nodecl.}
+proc clearTimeout*(t: Timeout) {.importc, nodecl.}
+proc targetElem*(e: Event): Element = cast[Element](e.target)
+
+proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.}
+
+proc getElementsByClassName*(cls: cstring): seq[Element] {.importc:
+  "document.getElementsByClassName", nodecl.}
+
+proc textContent*(e: Element): cstring {.
+  importcpp: "#.textContent", nodecl.}
+
+proc replaceById*(id: cstring; newTree: Node) =
+  let x = getElementById(id)
+  x.parentNode.replaceChild(newTree, x)
+  newTree.id = id
+
+proc equals(a, b: Node): bool =
+  if a.nodeType != b.nodeType: return false
+  if a.id != b.id: return false
+  if a.nodeName != b.nodeName: return false
+  if a.nodeType == TextNode:
+    if a.data != b.data: return false
+  elif a.childNodes.len != b.childNodes.len:
+    return false
+  if a.className != b.className:
+    # style differences are updated in place and we pretend
+    # it's still the same node
+    a.className = b.className
+    #return false
+  return true
+
+proc diffTree(parent, a, b: Node) =
+  if equals(a, b):
+    if a.nodeType != TextNode:
+      # we need to do this correctly in the presence of asyncronous
+      # DOM updates:
+      var i = 0
+      while i < a.childNodes.len and a.childNodes.len == b.childNodes.len:
+        diffTree(a, a.childNodes[i], b.childNodes[i])
+        inc i
+  elif parent == nil:
+    replaceById("ROOT", b)
+  else:
+    parent.replaceChild(b, a)
+
+proc dodraw() =
+  let newtree = dorender()
+  newtree.id = "ROOT"
+  if currentTree == nil:
+    currentTree = newtree
+    replaceById("ROOT", currentTree)
+  else:
+    diffTree(nil, currentTree, newtree)
+
+proc redraw*() =
+  # we buffer redraw requests:
+  if drawTimeout != nil:
+    clearTimeout(drawTimeout)
+  drawTimeout = setTimeout(dodraw, 30)
+
+proc tree*(tag: string; kids: varargs[Element]): Element =
+  result = document.createElement tag
+  for k in kids:
+    result.appendChild k
+
+proc tree*(tag: string; attrs: openarray[(string, string)];
+           kids: varargs[Element]): Element =
+  result = tree(tag, kids)
+  for a in attrs: result.setAttribute(a[0], a[1])
+
+proc text*(s: string): Element = cast[Element](document.createTextNode(s))
+proc text*(s: cstring): Element = cast[Element](document.createTextNode(s))
+proc add*(parent, kid: Element) =
+  if parent.nodeName == "TR" and (kid.nodeName == "TD" or kid.nodeName == "TH"):
+    let k = document.createElement("TD")
+    appendChild(k, kid)
+    appendChild(parent, k)
+  else:
+    appendChild(parent, kid)
+
+proc len*(x: Element): int {.importcpp: "#.childNodes.length".}
+proc `[]`*(x: Element; idx: int): Element {.importcpp: "#.childNodes[#]".}
+
+proc isInt*(s: cstring): bool {.asmNoStackFrame.} =
+  asm """
+    return s.match(/^[0-9]+$/);
+  """
+
+var
+  linkCounter: int
+
+proc link*(id: int): Element =
+  result = document.createElement("a")
+  result.setAttribute("href", "#")
+  inc linkCounter
+  result.setAttribute("id", $linkCounter & ":" & $id)
+
+proc link*(action: EventHandler): Element =
+  result = document.createElement("a")
+  result.setAttribute("href", "#")
+  addEventListener(result, "click", action)
+
+proc parseInt*(s: cstring): int {.importc, nodecl.}
+proc parseFloat*(s: cstring): float {.importc, nodecl.}
+proc split*(s, sep: cstring): seq[cstring] {.importcpp, nodecl.}
+
+proc startsWith*(a, b: cstring): bool {.importcpp: "startsWith", 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, length: int): cstring {.importcpp: "substr", nodecl.}
+
+#proc len*(s: cstring): int {.importcpp: "#.length", nodecl.}
+proc `&`*(a, b: cstring): cstring {.importcpp: "(# + #)", nodecl.}
+proc toCstr*(s: int): cstring {.importcpp: "((#)+'')", nodecl.}
+
+proc suffix*(s, prefix: cstring): cstring =
+  if s.startsWith(prefix):
+    result = s.substr(prefix.len)
+  else:
+    kout(cstring"bug! " & s & cstring" does not start with " & prefix)
+
+proc valueAsInt*(e: Element): int = parseInt(e.value)
+proc suffixAsInt*(s, prefix: cstring): int = parseInt(suffix(s, prefix))
+
+proc scrollTop*(e: Element): int {.importcpp: "#.scrollTop", nodecl.}
+proc offsetHeight*(e: Element): int {.importcpp: "#.offsetHeight", nodecl.}
+proc offsetTop*(e: Element): int {.importcpp: "#.offsetTop", nodecl.}
+
+template onImpl(s) {.dirty} =
+  proc wrapper(ev: Event) =
+    action(ev)
+    redraw()
+  addEventListener(e, s, wrapper)
+
+proc setOnclick*(e: Element; action: proc(ev: Event)) =
+  onImpl "click"
+
+proc setOnclick*(e: Element; action: proc(ev: Event; id: int)) =
+  proc wrapper(ev: Event) =
+    let id = ev.target.id
+    let a = id.split(":")
+    if a.len == 2:
+      action(ev, parseInt(a[1]))
+      redraw()
+    else:
+      kout(cstring("cannot deal with id "), id)
+  addEventListener(e, "click", wrapper)
+
+proc setOnfocuslost*(e: Element; action: EventHandler) =
+  onImpl "blur"
+
+proc setOnchanged*(e: Element; action: EventHandler) =
+  onImpl "change"
+
+proc setOnscroll*(e: Element; action: EventHandler) =
+  onImpl "scroll"
+
+proc select*(choices: openarray[string]): Element =
+  result = document.createElement("select")
+  var i = 0
+  for c in choices:
+    result.add tree("option", [("value", $i)], text(c))
+    inc i
+
+proc select*(choices: openarray[(int, string)]): Element =
+  result = document.createElement("select")
+  for c in choices:
+    result.add tree("option", [("value", $c[0])], text(c[1]))
+
+var radioCounter: int
+
+proc radio*(choices: openarray[(int, string)]): Element =
+  result = document.createElement("fieldset")
+  var i = 0
+  inc radioCounter
+  for c in choices:
+    let id = "radio_" & c[1] & $i
+    var kid = tree("input", [("type", "radio"),
+      ("id", id), ("name", "radio" & $radioCounter),
+      ("value", $c[0])])
+    if i == 0:
+      kid.setAttribute("checked", "checked")
+    var lab = tree("label", [("for", id)], text(c[1]))
+    kid.add lab
+    result.add kid
+    inc i
+
+proc tag*(name: string; id="", class=""): Element =
+  result = document.createElement(name)
+  if id.len > 0:
+    result.setAttribute("id", id)
+  if class.len > 0:
+    result.setAttribute("class", class)
+
+proc tdiv*(id="", class=""): Element = tag("div", id, class)
+proc span*(id="", class=""): Element = tag("span", id, class)
+
+proc th*(s: string): Element =
+  result = tag("th")
+  result.add text(s)
+
+proc td*(s: string): Element =
+  result = tag("td")
+  result.add text(s)
+
+proc td*(s: Element): Element =
+  result = tag("td")
+  result.add s
+
+proc td*(class: string; s: Element): Element =
+  result = tag("td")
+  result.add s
+  result.setAttribute("class", class)
+
+proc table*(class="", kids: varargs[Element]): Element =
+  result = tag("table", "", class)
+  for k in kids: result.add k
+
+proc tr*(kids: varargs[Element]): Element =
+  result = tag("tr")
+  for k in kids:
+    if k.nodeName == "TD" or k.nodeName == "TH":
+      result.add k
+    else:
+      result.add td(k)
+
+proc setClass*(e: Element; value: string) =
+  e.setAttribute("class", value)
+
+proc setAttr*(e: Element; key, value: cstring) =
+  e.setAttribute(key, value)
+
+proc getAttr*(e: Element; key: cstring): cstring {.
+  importcpp: "#.getAttribute(#)", nodecl.}
+
+proc realtimeInput*(id, val: string; changed: proc(value: cstring)): Element =
+  let oldElem = getElementById(id)
+  #if oldElem != nil: return oldElem
+  let newVal = if oldElem.isNil: val else: $oldElem.value
+  var timer: Timeout
+  proc wrapper() =
+    changed(getElementById(id).value)
+    redraw()
+  proc onkeyup(ev: Event) =
+    if timer != nil: clearTimeout(timer)
+    timer = setTimeout(wrapper, 400)
+  result = tree("input", [("type", "text"),
+    ("value", newVal),
+    ("id", id)])
+  result.addEventListener("keyup", onkeyup)
+
+proc ajax(meth, url: cstring; headers: openarray[(string, string)];
+          data: cstring;
+          cont: proc (httpStatus: int; response: cstring)) =
+  proc setRequestHeader(a, b: cstring) {.importc: "ajax.setRequestHeader".}
+  {.emit: """
+  var ajax = new XMLHttpRequest();
+  ajax.open(`meth`,`url`,true);""".}
+  for a, b in items(headers):
+    setRequestHeader(a, b)
+  {.emit: """
+  ajax.onreadystatechange = function(){
+    if(this.readyState == 4){
+      if(this.status == 200){
+        `cont`(this.status, this.responseText);
+      } else {
+        `cont`(this.status, this.statusText);
+      }
+    }
+  }
+  ajax.send(`data`);
+  """.}
+
+proc ajaxPut*(url: string; headers: openarray[(string, string)];
+          data: cstring;
+          cont: proc (httpStatus: int, response: cstring)) =
+  ajax("PUT", url, headers, data, cont)
+
+proc ajaxGet*(url: string; headers: openarray[(string, string)];
+          cont: proc (httpStatus: int, response: cstring)) =
+  ajax("GET", url, headers, nil, cont)
+
+{.push stackTrace:off.}
+
+proc setupErrorHandler*(useAlert=false) =
+  ## Installs an error handler that transforms native JS unhandled
+  ## exceptions into Nim based stack traces. If `useAlert` is false,
+  ## the error message it put into the console, otherwise `alert`
+  ## is called.
+  proc stackTraceAsCstring(): cstring = cstring(getStackTrace())
+  {.emit: """
+  window.onerror = function(msg, url, line, col, error) {
+    var x = "Error: " + msg + "\n" + `stackTraceAsCstring`()
+    if (`useAlert`)
+      alert(x);
+    else
+      console.log(x);
+    var suppressErrorAlert = true;
+    return suppressErrorAlert;
+  };""".}
+
+{.pop.}