diff --git a/src/karax.nim b/src/karax.nim index c03ddbe..01e5c0f 100644 --- a/src/karax.nim +++ b/src/karax.nim @@ -106,7 +106,10 @@ proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node = let x = VComponent(n) if x.onAttachImpl != nil: x.onAttachImpl(x) assert x.renderImpl != nil - result = vnodeToDom(x.renderImpl(x), kxi) + if x.expanded == nil: + x.expanded = x.renderImpl(x) + x.updatedImpl(x) + result = vnodeToDom(x.expanded, kxi) attach n return result else: @@ -133,7 +136,9 @@ proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node = if not n.style.isNil: applyStyle(result, n.style) proc same(n: VNode, e: Node): bool = - if toTag[n.kind] == e.nodename: + if n.kind == VNodeKind.component: + result = same(VComponent(n).expanded, e) + elif toTag[n.kind] == e.nodename: result = true if n.kind != VNodeKind.text: if e.len != n.len: return false @@ -177,7 +182,9 @@ proc eq(a, b: VNode; deep: bool): EqResult = result = similar if not sameAttrs(a, b): return different if a.class != b.class: return different - if a.style != b.style: return similar + if not eq(a.style, b.style): + kout cstring"yes, styles differ" + return similar # Do not test event listeners here! return result @@ -218,11 +225,22 @@ proc updateDom(newNode, oldNode: VNode) = for i in 0 ..< newNode.len: updateDom(newNode[i], oldNode[i]) +proc printV(n: VNode; depth: cstring = "") = + kout depth, cstring($n.kind), n.myid, cstring"key ", n.key + #for k, v in pairs(n.style): + # kout depth, "style: ", k, v + if n.kind == VNodeKind.component: + let nn = VComponent(n) + if nn.expanded != nil: printV(nn.expanded, ">>" & depth) + for i in 0 ..< n.len: + printV(n[i], depth & " ") + proc updateElement(parent, current: Node, newNode, oldNode: VNode; kxi: KaraxInstance): EqResult = result = eq(newNode, oldNode, deep=false) if result <= different: var n: Node + var state = 0 if result == changed: assert oldNode.kind == VNodeKind.component let x = VComponent(oldNode) @@ -231,7 +249,11 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode; x.updatedImpl(x) if oldExpanded == nil: n = vnodeToDom(x.expanded, kxi) + state = 1 else: + kout cstring"now comparing components" + printV(oldExpanded) + printV(x.expanded) if updateElement(parent, current, x.expanded, oldExpanded, kxi) >= similar: x.expanded = oldExpanded n = oldExpanded.dom @@ -239,12 +261,16 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode; else: n = x.expanded.dom doAssert n != nil, "expanded.dom is nil" + state = 2 + return else: detach(oldNode) n = vnodeToDom(newNode, kxi) + state = 3 if parent == nil: replaceById(kxi.rootId, n) else: + kout cstring"state ", state, parent, current parent.replaceChild(n, current) elif result == similar: updateStyles(newNode, oldNode, false) @@ -295,8 +321,9 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode; var pos = min(oldPos, newPos) + 1 for i in commonPrefix..pos-1: - if updateElement(current, current.childNodes[i], - newNode[i], oldNode[i], kxi) >= similar: + let res = updateElement(current, current.childNodes[i], + newNode[i], oldNode[i], kxi) + if res != different: newNode[i] = oldNode[i] var nextChildPos = oldPos + 1 @@ -323,6 +350,9 @@ proc dodraw(kxi: KaraxInstance) = let newtree = kxi.renderer() newtree.id = kxi.rootId kxi.toFocus = nil + #if kxi.currentTree != nil: + # kout cstring"same? ", same(kxi.currentTree, document.getElementById(kxi.rootId)) + kout cstring"dodraw -----------------------------" if kxi.currentTree == nil: kxi.currentTree = newtree let asdom = vnodeToDom(kxi.currentTree, kxi) @@ -330,8 +360,8 @@ proc dodraw(kxi: KaraxInstance) = else: let olddom = document.getElementById(kxi.rootId) discard updateElement(nil, olddom, newtree, kxi.currentTree, kxi) - #assert same(newtree, document.getElementById("ROOT")) kxi.currentTree = newtree + #kout cstring"same? ", same(kxi.currentTree, document.getElementById(kxi.rootId)) if not kxi.postRenderCallback.isNil: kxi.postRenderCallback() @@ -348,10 +378,10 @@ proc redraw*(kxi: KaraxInstance = kxi) = if drawTimeout != nil: clearTimeout(drawTimeout) drawTimeout = setTimeout(dodraw, 30) - elif true: + elif false: reqFrame(proc () = kxi.dodraw) else: - dodraw() + dodraw(kxi) proc init(ev: Event) = reqFrame(proc () = kxi.dodraw) diff --git a/src/vdom.nim b/src/vdom.nim index f022b7f..16b0f36 100644 --- a/src/vdom.nim +++ b/src/vdom.nim @@ -119,6 +119,7 @@ type style*: VStyle ## the style that should be applied to the virtual node. dom*: Node ## the attached real DOM node. Can be 'nil' if the virtual node ## is not part of the virtual DOM anymore. + myid*: int VComponent* = ref object of VNode ## The abstract class for every karax component. renderImpl*: proc(self: VComponent): VNode diff --git a/src/vstyles.nim b/src/vstyles.nim index 252dda4..a6f1996 100644 --- a/src/vstyles.nim +++ b/src/vstyles.nim @@ -213,7 +213,9 @@ type attrs: array[StyleAttr, cstring] mask: set[StyleAttr] -proc `==`*(a, b: VStyle): bool = +proc kout[T](x: T) {.importc: "console.log", varargs.} + +proc eq*(a, b: VStyle): bool = if a.isNil: if b.isNil: return true else: return false @@ -247,9 +249,10 @@ proc setStyle(d: Style; key, val: cstring) {.importcpp: "#[#] = #".} proc merge*(a, b: VStyle): VStyle = ## merges two styles. ``b`` takes precedence over ``a``. - result = VStyle(mask: a.mask + b.mask) + result = VStyle(mask: {}) for i in low(StyleAttr)..high(StyleAttr): result.attrs[i] = if b.attrs[i].isNil: a.attrs[i] else: b.attrs[i] + if result.attrs[i] != nil: incl(result.mask, i) proc applyStyle*(n: Node; s: VStyle) = ## apply the style to the real DOM node ``n``. @@ -257,6 +260,11 @@ proc applyStyle*(n: Node; s: VStyle) = for x in s.mask: n.style.setStyle(toStyleAttrName[x], s.attrs[x]) +iterator pairs*(v: VStyle): (cstring, cstring) = + if v != nil: + for x in v.mask: + yield (toStyleAttrName[x], v.attrs[x]) + import jstrutils proc rgb*(r, g, b: range[0..255]): cstring = diff --git a/tests/blur.nim b/tests/blur.nim new file mode 100644 index 0000000..062c47f --- /dev/null +++ b/tests/blur.nim @@ -0,0 +1,58 @@ +import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils + +type TextInput* = ref object of VComponent + value: cstring + isActive: bool + +var renderId: int +proc render(x: VComponent): VNode = + let self = TextInput(x) + + let style = style( + (StyleAttr.position, cstring"relative"), + (StyleAttr.paddingLeft, cstring"10px"), + (StyleAttr.paddingRight, cstring"5px"), + (StyleAttr.height, cstring"30px"), + (StyleAttr.lineHeight, cstring"30px"), + (StyleAttr.border, cstring"solid 1px " & (if self.isActive: cstring"red" else: cstring"black")), + (StyleAttr.fontSize, cstring"12px"), + (StyleAttr.fontWeight, cstring"600") + ).merge(self.style) + + let inputStyle = style.merge(style( + (StyleAttr.color, cstring"inherit"), + (StyleAttr.fontSize, cstring"inherit"), + (StyleAttr.fontWeight, cstring"inherit"), + (StyleAttr.fontFamily, cstring"inherit"), + (StyleAttr.position, cstring"absolute"), + (StyleAttr.top, cstring"0"), + (StyleAttr.left, cstring"0"), + (StyleAttr.height, cstring"100%"), + (StyleAttr.width, cstring"100%"), + (StyleAttr.border, cstring"none"), + (StyleAttr.backgroundColor, cstring"transparent"), + )) + + proc flip(ev: Event; n: VNode) = + self.isActive = not self.isActive + markDirty(self) + + kout cstring"rendering ", self.myid + inc renderId + result = buildHtml(tdiv(style=style, key=renderId)): + input(style=inputStyle, value=self.value, onblur=flip, onfocus=flip, + key=renderId) + +var gid = 0 +proc newTextInput*(style: VStyle = VStyle(); value: cstring = cstring""): TextInput = + result = newComponent(TextInput, render) + result.style = style + result.value = value + inc gid + result.myid = gid + +proc createDom(): VNode = + result = buildHtml(tdiv): + newTextInput(value=cstring"test") + +setRenderer createDom diff --git a/tools/karun.nim b/tools/karun.nim index dd498ea..a67b9f5 100644 --- a/tools/karun.nim +++ b/tools/karun.nim @@ -21,12 +21,20 @@ proc exec(cmd: string) = proc main = var op = initOptParser() - let rest = op.cmdLineRest + var rest = op.cmdLineRest var file = "" + var run = false while true: op.next() case op.kind - of cmdLongOption, cmdShortOption: discard + of cmdLongOption: + if op.key == "run": + run = true + rest = rest.replace("--run ") + of cmdShortOption: + if op.key == "r": + run = true + rest = rest.replace("-r ") of cmdArgument: file = op.key of cmdEnd: break @@ -36,6 +44,6 @@ proc main = exec("nim js --out:nimcache/" & name & ".js " & rest) let dest = "nimcache" / name & ".html" writeFile(dest, html % name) - openDefaultBrowser(dest) + if run: openDefaultBrowser(dest) main()