From d538b7bc938f8c70cf72980b9e5a7ae8905192e3 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 2 Jul 2017 09:34:58 +0200 Subject: [PATCH 1/2] try to make components aware of the previous state --- src/karax.nim | 10 ++++++--- src/vdom.nim | 17 ++++++++------- tests/blur.nim | 56 +++++++++++++++++++++++++++++++------------------- 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/src/karax.nim b/src/karax.nim index 3c41d7e..5edda89 100644 --- a/src/karax.nim +++ b/src/karax.nim @@ -132,7 +132,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 @@ -197,7 +198,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 @@ -282,6 +283,7 @@ proc diff(newNode, oldNode: VNode; parent, current: Node; kxi: KaraxInstance): E if newNode.events.len != 0 or oldNode.events.len != 0: mergeEvents(newNode, oldNode, kxi) if oldNode.kind == VNodeKind.input or oldNode.kind == VNodeKind.textarea: + echo "comparing ", oldNode.text != newNode.text if oldNode.text != newNode.text: oldNode.text = newNode.text oldNode.dom.value = newNode.text @@ -359,9 +361,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.version = VComponent(newNode).version + #x.updatedImpl(x) if oldExpanded == nil: detach(oldNode) kxi.addPatch(pkReplace, parent, current, x.expanded) diff --git a/src/vdom.nim b/src/vdom.nim index b0f7293..bbb4eac 100644 --- a/src/vdom.nim +++ b/src/vdom.nim @@ -124,14 +124,13 @@ 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. expanded*: VNode ## Do not touch. Used by karax. The VDOM the component ## expanded to. @@ -153,19 +152,19 @@ 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 + result = v.version != newInstance.version -proc defaultUpdatedImpl*(v: VComponent) = - v.renderedVersion = v.version +proc defaultUpdatedImpl*(v, newInstance: VComponent) = + discard 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, diff --git a/tests/blur.nim b/tests/blur.nim index 844ab8b..3823f93 100644 --- a/tests/blur.nim +++ b/tests/blur.nim @@ -41,18 +41,33 @@ proc render(x: VComponent): VNode = proc onchanged(ev: Event; n: VNode) = if self.onchange != nil and self.value != n.value: self.onchange n.value - self.value = 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 setValue(x: TextInput; value: cstring) = +# x.value = value +# markDirty(x) + +proc update(current, next: VComponent) = + let current = TextInput(current) + let next = TextInput(next) + if not current.isActive: + current.value = next.value + echo "updated! ", current.value + else: + echo "not updated! ", current.value + #markDirty(current) + +proc changed(current, next: VComponent): bool = + let current = TextInput(current) + let next = TextInput(next) + result = next.value != current.value or true proc newTextInput*(style: VStyle = VStyle(); 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 @@ -76,32 +91,25 @@ proc renderComb(self: VComponent): VNode = 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) +when false: + 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 +121,13 @@ proc createDom(): VNode = for index, text in persons.pairs: renderPerson(text, index) tdiv: - ti + newTextInput(VStyle(), 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 From a93e739081fbb3572777665165343854c394e5b5 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Wed, 5 Jul 2017 16:07:52 +0200 Subject: [PATCH 2/2] new component system: blur.nim example now works --- src/karax.nim | 5 ++-- src/vdom.nim | 13 +++++++-- tests/blur.nim | 75 ++++++++++++++++++++++++-------------------------- 3 files changed, 49 insertions(+), 44 deletions(-) diff --git a/src/karax.nim b/src/karax.nim index 5edda89..3fb0fc4 100644 --- a/src/karax.nim +++ b/src/karax.nim @@ -283,7 +283,6 @@ proc diff(newNode, oldNode: VNode; parent, current: Node; kxi: KaraxInstance): E if newNode.events.len != 0 or oldNode.events.len != 0: mergeEvents(newNode, oldNode, kxi) if oldNode.kind == VNodeKind.input or oldNode.kind == VNodeKind.textarea: - echo "comparing ", oldNode.text != newNode.text if oldNode.text != newNode.text: oldNode.text = newNode.text oldNode.dom.value = newNode.text @@ -364,8 +363,8 @@ proc diff(newNode, oldNode: VNode; parent, current: Node; kxi: KaraxInstance): E x.updatedImpl(x, VComponent newNode) let oldExpanded = x.expanded x.expanded = x.renderImpl(x) - x.version = VComponent(newNode).version - #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 bbb4eac..997b059 100644 --- a/src/vdom.nim +++ b/src/vdom.nim @@ -131,8 +131,11 @@ type 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. 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 @@ -154,11 +157,16 @@ proc dthunk*(name: cstring; args: varargs[VNode, vn]): VNode = proc defaultChangedImpl*(v, newInstance: VComponent): bool = ## The default implementation of 'changed'. - result = v.version != newInstance.version + result = v.version != v.renderedVersion 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, @@ -169,7 +177,8 @@ template newComponent*[T](t: typeDesc[T]; 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 3823f93..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,63 +35,60 @@ 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 + 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 update(current, next: VComponent) = let current = TextInput(current) let next = TextInput(next) - if not current.isActive: - current.value = next.value - echo "updated! ", current.value - else: - echo "not updated! ", current.value - #markDirty(current) + current.value = next.value + current.guid = next.guid + next.isActive = current.isActive -proc changed(current, next: VComponent): bool = - let current = TextInput(current) - let next = TextInput(next) - result = next.value != current.value or true - -proc newTextInput*(style: VStyle = VStyle(); value: cstring = cstring"", +proc newTextInput*(style: VStyle = VStyle(); guid: cstring; value: cstring = cstring"", onchange: proc(v: cstring) = nil): TextInput = result = newComponent(TextInput, render, changed=changed, updated=update) result.style = style result.value = value result.onchange = onchange - -type - Combined = ref object of VComponent - a, b: TextInput - -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) - - result = buildHtml(tdiv(style=self.style)): - self.a - self.b - button(onclick=bu): - text "reset" + result.guid = guid when false: + type + Combined = ref object of VComponent + a, b: TextInput + + 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) + + 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) @@ -121,7 +118,7 @@ proc createDom(): VNode = for index, text in persons.pairs: renderPerson(text, index) tdiv: - newTextInput(VStyle(), if selected >= 0: persons[selected] else: "", proc (v: cstring) = + newTextInput(VStyle(), &selected, if selected >= 0: persons[selected] else: "", proc (v: cstring) = if v.len > 0: if selected >= 0: persons[selected] = v errmsg = ""