## Raw DOM manipulation. No DOM diff'ing, no cry. import macros, tables from strutils import startsWith, toLowerAscii, toUpperAscii when defined(js): type kstring* = cstring else: type kstring* = string type Tag* {.pure.} = enum text = "#text", html, head, title, base, link, meta, style, script, noscript, body, section, nav, article, aside, h1, h2, h3, h4, h5, h6, header, footer, address, main p, hr, pre, blockquote, ol, ul, li, dl, dt, dd, figure, figcaption, tdiv = "div", a, em, strong, small, strikethrough = "s", cite, quote, dfn, abbr, data, time, code, `var` = "var", samp, kdb, sub, sup, italic = "i", bold = "b", underlined = "u", mark, ruby, rt, rp, bdi, dbo, span, br, wbr, ins, del, img, iframe, embed, `object` = "object", param, video, audio, source, track, canvas, map, area, svg, math, table, caption, colgroup, col, tbody, thead, tfoot, tr, td, th, form, fieldset, legend, label, input, button, select, datalist, optgroup, option, textarea, keygen, output, progress, meter, details, summary, command, menu type EventKind* {.pure.} = enum ## The events supported by the virtual DOM. onclick, ## An element is clicked. oncontextmenu, ## An element is right-clicked. ondblclick, ## An element is double clicked. onkeyup, ## A key was released. onkeydown, ## A key is pressed. onkeypressed, # A key was pressed. onfocus, ## An element got the focus. onblur, ## An element lost the focus. onchange, ## The selected value of an element was changed. onscroll, ## The user scrolled within an element. onmousedown, ## A pointing device button (usually a mouse) is pressed ## on an element. onmouseenter, ## A pointing device is moved onto the element that ## has the listener attached. onmouseleave, ## A pointing device is moved off the element that ## has the listener attached. onmousemove, ## A pointing device is moved over an element. onmouseout, ## A pointing device is moved off the element that ## has the listener attached or off one of its children. onmouseover, ## A pointing device is moved onto the element that has ## the listener attached or onto one of its children. onmouseup, ## A pointing device button is released over an element. ondrag, ## An element or text selection is being dragged (every 350ms). ondragend, ## A drag operation is being ended (by releasing a mouse button ## or hitting the escape key). ondragenter, ## A dragged element or text selection enters a valid drop target. ondragleave, ## A dragged element or text selection leaves a valid drop target. ondragover, ## An element or text selection is being dragged over a valid ## drop target (every 350ms). ondragstart, ## The user starts dragging an element or text selection. ondrop, ## An element is dropped on a valid drop target. onsubmit, ## A form is submitted oninput, ## An input value changes onkeyupenter, ## vdom extension: an input field received the ENTER key press onkeyuplater ## vdom extension: a key was pressed and some time ## passed (useful for on-the-fly text completions) macro buildLookupTables(): untyped = var a = newTree(nnkBracket) for i in low(Tag)..high(Tag): let x = $i let y = if x[0] == '#': x else: toUpperAscii(x) a.add(newCall("kstring", newLit(y))) var e = newTree(nnkBracket) for i in low(EventKind)..high(EventKind): e.add(newCall("kstring", newLit(substr($i, 2)))) template tmpl(a, e) {.dirty.} = const toTagName*: array[Tag, kstring] = a toEventName*: array[EventKind, kstring] = e result = getAst tmpl(a, e) buildLookupTables() # ---------------- CSS ---------------------------------------------- type StyleAttr* {.pure.} = enum ## The style attributes supported by the virtual DOM. ## Reference: https://www.w3schools.com/jsref/dom_obj_style.asp alignContent alignItems alignSelf animation animationDelay animationDirection animationDuration animationFillMode animationIterationCount animationName animationTimingFunction animationPlayState background backgroundAttachment backgroundColor backgroundImage backgroundPosition backgroundRepeat backgroundClip backgroundOrigin backgroundSize backfaceVisibility border borderBottom borderBottomColor borderBottomLeftRadius borderBottomRightRadius borderBottomStyle borderBottomWidth borderCollapse borderColor borderImage borderImageOutset borderImageRepeat borderImageSlice borderImageSource borderImageWidth borderLeft borderLeftColor borderLeftStyle borderLeftWidth borderRadius borderRight borderRightColor borderRightStyle borderRightWidth borderSpacing borderStyle borderTop borderTopColor borderTopLeftRadius borderTopRightRadius borderTopStyle borderTopWidth borderWidth bottom boxDecorationBreak boxShadow boxSizing captionSide clear clip color columnCount columnFill columnGap columnRule columnRuleColor columnRuleStyle columnRuleWidth columns columnSpan columnWidth content counterIncrement counterReset cursor direction display emptyCells filter flex flexBasis flexDirection flexFlow flexGrow flexShrink flexWrap cssFloat font fontFamily fontSize fontSizeAdjust fontStretch fontStyle fontVariant fontWeight hangingPunctuation height hyphens icon imageOrientation justifyContent left letterSpacing lineHeight listStyle listStyleImage listStylePosition listStyleType margin marginBottom marginLeft marginRight marginTop maxHeight maxWidth minHeight minWidth navDown navIndex navLeft navRight navUp opacity order orphans outline outlineColor outlineOffset outlineStyle outlineWidth overflow overflowX overflowY padding paddingBottom paddingLeft paddingRight paddingTop pageBreakAfter pageBreakBefore pageBreakInside perspective perspectiveOrigin pointerEvents position quotes resize right scrollbar3dLightColor # note: missing in w3schools reference scrollbarArrowColor # note: missing in w3schools reference scrollbarBaseColor # note: missing in w3schools reference scrollbarDarkshadowColor # note: missing in w3schools reference scrollbarFaceColor # note: missing in w3schools reference scrollbarHighlightColor # note: missing in w3schools reference scrollbarShadowColor # note: missing in w3schools reference scrollbarTrackColor # note: missing in w3schools reference tableLayout tabSize textAlign textAlignLast textDecoration textDecorationColor textDecorationLine textDecorationStyle textIndent textJustify textOverflow textShadow textTransform top transform transformOrigin transformStyle transition transitionDelay transitionDuration transitionProperty transitionTimingFunction unicodeBidi userSelect verticalAlign visibility whiteSpace width wordBreak wordSpacing wordWrap widows zIndex macro buildStyleLookupTable(): untyped = var e = newTree(nnkBracket) for i in low(StyleAttr)..high(StyleAttr): e.add(newCall("kstring", newLit($i))) template tmpl(e) {.dirty.} = const toStyleAttrName: array[StyleAttr, kstring] = e result = getAst tmpl(e) buildStyleLookupTable() type StyleObj {.importc.} = object background*: cstring backgroundAttachment*: cstring backgroundColor*: cstring backgroundImage*: cstring backgroundPosition*: cstring backgroundRepeat*: cstring border*: cstring borderBottom*: cstring borderBottomColor*: cstring borderBottomStyle*: cstring borderBottomWidth*: cstring borderColor*: cstring borderLeft*: cstring borderLeftColor*: cstring borderLeftStyle*: cstring borderLeftWidth*: cstring borderRight*: cstring borderRightColor*: cstring borderRightStyle*: cstring borderRightWidth*: cstring borderStyle*: cstring borderTop*: cstring borderTopColor*: cstring borderTopStyle*: cstring borderTopWidth*: cstring borderWidth*: cstring bottom*: cstring captionSide*: cstring clear*: cstring clip*: cstring color*: cstring cursor*: cstring direction*: cstring display*: cstring emptyCells*: cstring cssFloat*: cstring font*: cstring fontFamily*: cstring fontSize*: cstring fontStretch*: cstring fontStyle*: cstring fontVariant*: cstring fontWeight*: cstring height*: cstring left*: cstring letterSpacing*: cstring lineHeight*: cstring listStyle*: cstring listStyleImage*: cstring listStylePosition*: cstring listStyleType*: cstring margin*: cstring marginBottom*: cstring marginLeft*: cstring marginRight*: cstring marginTop*: cstring maxHeight*: cstring maxWidth*: cstring minHeight*: cstring minWidth*: cstring overflow*: cstring padding*: cstring paddingBottom*: cstring paddingLeft*: cstring paddingRight*: cstring paddingTop*: cstring pageBreakAfter*: cstring pageBreakBefore*: cstring pointerEvents*: cstring position*: cstring right*: cstring scrollbar3dLightColor*: cstring scrollbarArrowColor*: cstring scrollbarBaseColor*: cstring scrollbarDarkshadowColor*: cstring scrollbarFaceColor*: cstring scrollbarHighlightColor*: cstring scrollbarShadowColor*: cstring scrollbarTrackColor*: cstring tableLayout*: cstring textAlign*: cstring textDecoration*: cstring textIndent*: cstring textTransform*: cstring transform*: cstring top*: cstring verticalAlign*: cstring visibility*: cstring width*: cstring wordSpacing*: cstring zIndex*: int Style* = ref StyleObj ElementObj {.importc.} = object id*, class*, value*: cstring disabled*: bool style*: Style Element* = ref ElementObj DocumentObj {.importc.} = object Document = ref DocumentObj proc setStyle(s: Style; key, val: cstring) {.importcpp: "#[#] = #", noSideEffect.} proc applyStyles*(e: Element; pairs: openArray[(StyleAttr, kstring)]) = for x in pairs: e.style.setStyle(toStyleAttrName[x[0]], x[1]) # ------- Tree manipulation --------------------------- var document {.importc.}: Document proc parent*(x: Element): Element {.importcpp: "#.parentNode".} proc up*(x: Element; className: cstring): Element = result = x while result != nil and result.class != className: result = result.parent proc len*(x: Element): int {.importcpp: "#.childNodes.length".} proc `[]`*(x: Element; idx: int): Element {.importcpp: "#.childNodes[#]".} proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.} proc add*(n, child: Element) {.importcpp: "appendChild".} proc removeChild(n, child: Element) {.importcpp.} proc replaceChild(n, newNode, oldNode: Element) {.importcpp.} proc replace*(self, by: Element) = replaceChild(self.parent, by, self) proc delete*(self: Element) = removeChild(self.parent, self) proc insertBefore(n, newNode, before: Element) {.importcpp.} proc insert*(before, newNode: Element) = insertBefore(before.parent, newNode, before) proc createTextNode(d: Document, text: cstring): Element {.importcpp.} proc text*(s: kstring): Element = createTextNode(document, s) iterator items*(n: Element): Element = for i in 0.. 0: result[L-1] = tcall2(result[L-1], tmpContext) of nnkStmtList, nnkStmtListExpr, nnkWhenStmt, nnkIfStmt, nnkTryStmt, nnkFinally: # recurse for every child: result = copyNimNode(n) for x in n: result.add tcall2(x, tmpContext) of nnkCaseStmt: # recurse for children, but don't add call for case ident result = copyNimNode(n) result.add n[0] for i in 1 ..< n.len: result.add tcall2(n[i], tmpContext) of nnkProcDef: let name = getName n[0] if name.startsWith"on": # turn it into an anon proc: let anon = copyNimTree(n) anon[0] = newEmptyNode() if tmpContext == nil: error "no Element to attach the event handler to" else: result = newCall(evHandler(), tmpContext, newDotExpr(bindSym"EventKind", n[0]), anon, ident("kxi")) else: result = n of nnkVarSection, nnkLetSection, nnkConstSection: result = n of nnkCallKinds - {nnkInfix}: let op = getName(n[0]) if isComponent(op) == ComponentKind.Tag: let tmp = genSym(nskLet, "tmp") let call = newCall(bindSym"tree", newDotExpr(bindSym"Tag", n[0])) result = newTree( if tmpContext == nil: nnkStmtListExpr else: nnkStmtList, newLetStmt(tmp, call)) for i in 1 ..< n.len: # named parameters are transformed into attributes or events: let x = n[i] if x.kind == nnkExprEqExpr: let key = getName x[0] if key.startsWith("on"): result.add newCall(evHandler(), tmp, newDotExpr(bindSym"EventKind", x[0]), x[1]) elif key in SpecialAttrs: result.add newDotAsgn(tmp, key, x[1]) elif eqIdent(key, "style"): result.add newCall(bindSym"applyStyles", tmp, toKstring x[1]) elif eqIdent(key, "setFocus"): result.add newCall(bindSym"focus", tmp) else: result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1]) elif eqIdent(x, "setFocus"): result.add newCall(bindSym"focus", tmp) else: result.add tcall2(x, tmp) if tmpContext == nil: result.add tmp else: result.add newCall(bindSym"add", tmpContext, tmp) elif tmpContext != nil and op notin StmtContext: var hasEventHandlers = false for i in 1..