diff --git a/src/karax.nim b/src/karax.nim index b83f8c0..a2ef499 100644 --- a/src/karax.nim +++ b/src/karax.nim @@ -140,7 +140,8 @@ proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node = assert x.renderImpl != nil if x.expanded == nil: x.expanded = x.renderImpl(x) - x.updatedImpl(x) + # x.updatedImpl(x, nil) + assert x.expanded != nil result = vnodeToDom(x.expanded, kxi) attach n return result @@ -208,7 +209,7 @@ proc eq(a, b: VNode): EqResult = if a.text != b.text: return different let x = VComponent(b) assert x.changedImpl != nil - return if x.changedImpl(x): changed else: identical + return if x.changedImpl(x, VComponent(a)): changed else: identical if not sameAttrs(a, b): return different if a.class != b.class: return different if not eq(a.style, b.style): return similar @@ -398,9 +399,11 @@ proc diff(newNode, oldNode: VNode; parent, current: Node; kxi: KaraxInstance): E of changed: assert oldNode.kind == VNodeKind.component let x = VComponent(oldNode) + x.updatedImpl(x, VComponent newNode) let oldExpanded = x.expanded x.expanded = x.renderImpl(x) - x.updatedImpl(x) + x.renderedVersion = x.version + #echo "expanding ", x.debugId if oldExpanded == nil: detach(oldNode) kxi.addPatch(pkReplace, parent, current, x.expanded) diff --git a/src/vdom.nim b/src/vdom.nim index b0f7293..997b059 100644 --- a/src/vdom.nim +++ b/src/vdom.nim @@ -124,16 +124,18 @@ type VComponent* = ref object of VNode ## The abstract class for every karax component. renderImpl*: proc(self: VComponent): VNode - changedImpl*: proc(self: VComponent): bool - updatedImpl*: proc(self: VComponent) + changedImpl*: proc(self, newInstance: VComponent): bool + updatedImpl*: proc(self, newInstance: VComponent) onAttachImpl*: proc(self: VComponent) onDetachImpl*: proc(self: VComponent) version*: int ## Update this to trigger a redraw by karax. Usually you ## should call 'markDirty' instead which is an alias for ## 'inc version'. - renderedVersion*: int ## Do not touch. Used by karax. + renderedVersion*: int ## Do not touch. Used by karax. The last version of the + ## component we rendered. expanded*: VNode ## Do not touch. Used by karax. The VDOM the component ## expanded to. + debugId*: int proc value*(n: VNode): cstring = n.text proc `value=`*(n: VNode; v: cstring) = n.text = v @@ -153,24 +155,30 @@ proc vthunk*(name: cstring; args: varargs[VNode, vn]): VNode = proc dthunk*(name: cstring; args: varargs[VNode, vn]): VNode = VNode(kind: VNodeKind.dthunk, text: name, key: -1, kids: @args) -proc defaultChangedImpl*(v: VComponent): bool = +proc defaultChangedImpl*(v, newInstance: VComponent): bool = ## The default implementation of 'changed'. result = v.version != v.renderedVersion -proc defaultUpdatedImpl*(v: VComponent) = - v.renderedVersion = v.version +proc defaultUpdatedImpl*(v, newInstance: VComponent) = + discard + +var gid = 0 +proc getDebugId(): int = + inc(gid) + gid template newComponent*[T](t: typeDesc[T]; render: (proc(self: VComponent): VNode) not nil, onAttach: proc(self: VComponent) = nil, onDetach: proc(self: VComponent) = nil, - changed: (proc(self: VComponent): bool) = defaultChangedImpl, - updated: proc(self: VComponent) = defaultUpdatedImpl): T = + changed: (proc(self, newInstance: VComponent): bool) = defaultChangedImpl, + updated: proc(self, newInstance: VComponent) = defaultUpdatedImpl): T = ## Use this template to create new components. T(kind: VNodeKind.component, key: -1, text: cstring(astToStr(t)), renderImpl: render, changedImpl: changed, updatedImpl: updated, - onAttachImpl: onAttach, onDetachImpl: onDetach) + onAttachImpl: onAttach, onDetachImpl: onDetach, + debugId: getDebugId()) template markDirty*(c: VComponent) = ## mark the component as dirty so that it is re-rendered. diff --git a/tests/blur.nim b/tests/blur.nim index 844ab8b..25c0b24 100644 --- a/tests/blur.nim +++ b/tests/blur.nim @@ -1,7 +1,7 @@ import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils type TextInput* = ref object of VComponent - value: cstring + value, guid: cstring isActive: bool onchange: proc (value: cstring) @@ -35,73 +35,78 @@ proc render(x: VComponent): VNode = proc flip(ev: Event; n: VNode) = self.isActive = not self.isActive - kout cstring"onflip", n.value + echo "flip! ", self.isActive, " id: ", self.debugId, " version ", self.version markDirty(self) proc onchanged(ev: Event; n: VNode) = - if self.onchange != nil and self.value != n.value: + if self.onchange != nil: self.onchange n.value self.value = n.value result = buildHtml(tdiv(style=style)): input(style=inputStyle, value=self.value, onblur=flip, onfocus=flip, onkeyup=onchanged) -proc setValue(x: TextInput; value: cstring) = - x.value = value - markDirty(x) +proc changed(current, next: VComponent): bool = + let current = TextInput(current) + let next = TextInput(next) + if current.guid != next.guid: + result = true + else: + result = defaultChangedImpl(current, next) -proc newTextInput*(style: VStyle = VStyle(); value: cstring = cstring"", +proc update(current, next: VComponent) = + let current = TextInput(current) + let next = TextInput(next) + current.value = next.value + current.guid = next.guid + next.isActive = current.isActive + +proc newTextInput*(style: VStyle = VStyle(); guid: cstring; value: cstring = cstring"", onchange: proc(v: cstring) = nil): TextInput = - result = newComponent(TextInput, render) + result = newComponent(TextInput, render, changed=changed, updated=update) result.style = style result.value = value result.onchange = onchange + result.guid = guid -type - Combined = ref object of VComponent - a, b: TextInput +when false: + type + Combined = ref object of VComponent + a, b: TextInput -proc renderComb(self: VComponent): VNode = - let self = Combined(self) + proc renderComb(self: VComponent): VNode = + let self = Combined(self) - proc bu(ev: Event; n: VNode) = - self.a.value = "" - self.b.value = "" - markDirty(self.a) - markDirty(self.b) + proc bu(ev: Event; n: VNode) = + self.a.value = "" + self.b.value = "" + markDirty(self.a) + markDirty(self.b) - result = buildHtml(tdiv(style=self.style)): - self.a - self.b - button(onclick=bu): - text "reset" + result = buildHtml(tdiv(style=self.style)): + self.a + self.b + button(onclick=bu): + text "reset" -proc changed(self: VComponent): bool = - let self = Combined(self) - result = self.a.changedImpl(self.a) or self.b.changedImpl(self.b) + proc changed(self: VComponent): bool = + let self = Combined(self) + result = self.a.changedImpl(self.a) or self.b.changedImpl(self.b) -proc newCombined*(style: VStyle = VStyle()): Combined = - result = newComponent(Combined, renderComb, changed=changed) - result.a = newTextInput(style, "AAA") - result.b = newTextInput(style, "BBB") + proc newCombined*(style: VStyle = VStyle()): Combined = + result = newComponent(Combined, renderComb, changed=changed) + result.a = newTextInput(style, "AAA") + result.b = newTextInput(style, "BBB") var persons: seq[cstring] = @[cstring"Karax", "Abathur", "Fenix"] selected = -1 errmsg = cstring"" - ti = newTextInput(VStyle(), "", proc (v: cstring) = - if v.len > 0: - if selected >= 0: persons[selected] = v - errmsg = "" - else: - errmsg = "name must not be empty" - ) proc renderPerson(text: cstring, index: int): VNode = proc select(ev: Event, n: VNode) = selected = index - ti.setValue(persons[selected]) result = buildHtml(): tdiv(onClick=select): @@ -113,7 +118,13 @@ proc createDom(): VNode = for index, text in persons.pairs: renderPerson(text, index) tdiv: - ti + newTextInput(VStyle(), &selected, if selected >= 0: persons[selected] else: "", proc (v: cstring) = + if v.len > 0: + if selected >= 0: persons[selected] = v + errmsg = "" + else: + errmsg = "name must not be empty" + ) tdiv: text errmsg