DOM API: make compatible with Karax's requirements (#10517)
* DOM API: make compatible with Karax's requirements * make tools\dochack.nim compile again
This commit is contained in:
parent
f4c76bc8e5
commit
17d0ce9c1c
3 changed files with 798 additions and 482 deletions
891
lib/js/dom.nim
891
lib/js/dom.nim
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,38 @@
|
||||||
import karax
|
import dom
|
||||||
import fuzzysearch
|
import fuzzysearch
|
||||||
|
|
||||||
|
proc textContent(e: Element): cstring {.
|
||||||
|
importcpp: "#.textContent", nodecl.}
|
||||||
|
|
||||||
|
proc textContent(e: Node): cstring {.
|
||||||
|
importcpp: "#.textContent", nodecl.}
|
||||||
|
|
||||||
|
proc tree(tag: string; kids: varargs[Element]): Element =
|
||||||
|
result = document.createElement tag
|
||||||
|
for k in kids:
|
||||||
|
result.appendChild k
|
||||||
|
|
||||||
|
proc add(parent, kid: Element) =
|
||||||
|
if parent.nodeName == cstring"TR" and (
|
||||||
|
kid.nodeName == cstring"TD" or kid.nodeName == cstring"TH"):
|
||||||
|
let k = document.createElement("TD")
|
||||||
|
appendChild(k, kid)
|
||||||
|
appendChild(parent, k)
|
||||||
|
else:
|
||||||
|
appendChild(parent, kid)
|
||||||
|
|
||||||
|
proc setClass(e: Element; value: string) =
|
||||||
|
e.setAttribute("class", value)
|
||||||
|
proc text(s: string): Element = cast[Element](document.createTextNode(s))
|
||||||
|
proc text(s: cstring): Element = cast[Element](document.createTextNode(s))
|
||||||
|
|
||||||
|
proc getElementById(id: cstring): Element {.importc: "document.getElementById", nodecl.}
|
||||||
|
|
||||||
|
proc replaceById(id: cstring; newTree: Node) =
|
||||||
|
let x = getElementById(id)
|
||||||
|
x.parentNode.replaceChild(newTree, x)
|
||||||
|
newTree.id = id
|
||||||
|
|
||||||
proc findNodeWith(x: Element; tag, content: cstring): Element =
|
proc findNodeWith(x: Element; tag, content: cstring): Element =
|
||||||
if x.nodeName == tag and x.textContent == content:
|
if x.nodeName == tag and x.textContent == content:
|
||||||
return x
|
return x
|
||||||
|
|
@ -182,7 +214,7 @@ proc buildToc(orig: TocEntry; types, procs: seq[Element]): TocEntry =
|
||||||
t.markElement()
|
t.markElement()
|
||||||
for p in procs:
|
for p in procs:
|
||||||
if not isMarked(p):
|
if not isMarked(p):
|
||||||
let xx = karax.getElementsByClass(p.parent, cstring"attachedType")
|
let xx = getElementsByClass(p.parent, cstring"attachedType")
|
||||||
if xx.len == 1 and xx[0].textContent == t.textContent:
|
if xx.len == 1 and xx[0].textContent == t.textContent:
|
||||||
#kout(cstring"found ", p.nodeName)
|
#kout(cstring"found ", p.nodeName)
|
||||||
let q = tree("A", text(p.title))
|
let q = tree("A", text(p.title))
|
||||||
|
|
@ -230,7 +262,7 @@ proc groupBy*(value: cstring) {.exportc.} =
|
||||||
togglevis(getElementById"toc-list")
|
togglevis(getElementById"toc-list")
|
||||||
|
|
||||||
var
|
var
|
||||||
db: seq[Element]
|
db: seq[Node]
|
||||||
contents: seq[cstring]
|
contents: seq[cstring]
|
||||||
|
|
||||||
template normalize(x: cstring): cstring = x.toLower.replace("_", "")
|
template normalize(x: cstring): cstring = x.toLower.replace("_", "")
|
||||||
|
|
@ -258,7 +290,7 @@ proc dosearch(value: cstring): Element =
|
||||||
let ul = tree("UL")
|
let ul = tree("UL")
|
||||||
result = tree("DIV")
|
result = tree("DIV")
|
||||||
result.setClass"search_results"
|
result.setClass"search_results"
|
||||||
var matches: seq[(Element, int)] = @[]
|
var matches: seq[(Node, int)] = @[]
|
||||||
for i in 0..<db.len:
|
for i in 0..<db.len:
|
||||||
let c = contents[i]
|
let c = contents[i]
|
||||||
if c == cstring"Examples" or c == cstring"PEG construction":
|
if c == cstring"Examples" or c == cstring"PEG construction":
|
||||||
|
|
@ -270,11 +302,10 @@ proc dosearch(value: cstring): Element =
|
||||||
if matched:
|
if matched:
|
||||||
matches.add((db[i], score))
|
matches.add((db[i], score))
|
||||||
|
|
||||||
matches.sort do (a, b: auto) -> int:
|
matches.sort(proc(a, b: auto): int = b[1] - a[1])
|
||||||
b[1] - a[1]
|
|
||||||
for i in 0 ..< min(matches.len, 19):
|
for i in 0 ..< min(matches.len, 19):
|
||||||
matches[i][0].innerHTML = matches[i][0].getAttribute("data-doc-search-tag")
|
matches[i][0].innerHTML = matches[i][0].getAttribute("data-doc-search-tag")
|
||||||
ul.add(tree("LI", matches[i][0]))
|
ul.add(tree("LI", cast[Element](matches[i][0])))
|
||||||
if ul.len == 0:
|
if ul.len == 0:
|
||||||
result.add tree("B", text"no search results")
|
result.add tree("B", text"no search results")
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -1,344 +0,0 @@
|
||||||
# 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.}
|
|
||||||
|
|
||||||
proc toLower*(x: cstring): cstring {.
|
|
||||||
importcpp: "#.toLowerCase()", nodecl.}
|
|
||||||
proc replace*(x: cstring; search, by: cstring): cstring {.
|
|
||||||
importcpp: "#.replace(#, #)", 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 == cstring"TR" and (
|
|
||||||
kid.nodeName == cstring"TD" or kid.nodeName == cstring"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 == cstring"TD" or k.nodeName == cstring"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.}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue