Merge branch 'stateful-components3' into devel

This commit is contained in:
Andreas Rumpf 2017-07-06 20:26:51 +02:00
commit dc7409e52e
3 changed files with 73 additions and 51 deletions

View file

@ -140,7 +140,8 @@ proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
assert x.renderImpl != nil assert x.renderImpl != nil
if x.expanded == nil: if x.expanded == nil:
x.expanded = x.renderImpl(x) x.expanded = x.renderImpl(x)
x.updatedImpl(x) # x.updatedImpl(x, nil)
assert x.expanded != nil
result = vnodeToDom(x.expanded, kxi) result = vnodeToDom(x.expanded, kxi)
attach n attach n
return result return result
@ -208,7 +209,7 @@ proc eq(a, b: VNode): EqResult =
if a.text != b.text: return different if a.text != b.text: return different
let x = VComponent(b) let x = VComponent(b)
assert x.changedImpl != nil 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 not sameAttrs(a, b): return different
if a.class != b.class: return different if a.class != b.class: return different
if not eq(a.style, b.style): return similar 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: of changed:
assert oldNode.kind == VNodeKind.component assert oldNode.kind == VNodeKind.component
let x = VComponent(oldNode) let x = VComponent(oldNode)
x.updatedImpl(x, VComponent newNode)
let oldExpanded = x.expanded let oldExpanded = x.expanded
x.expanded = x.renderImpl(x) x.expanded = x.renderImpl(x)
x.updatedImpl(x) x.renderedVersion = x.version
#echo "expanding ", x.debugId
if oldExpanded == nil: if oldExpanded == nil:
detach(oldNode) detach(oldNode)
kxi.addPatch(pkReplace, parent, current, x.expanded) kxi.addPatch(pkReplace, parent, current, x.expanded)

View file

@ -124,16 +124,18 @@ type
VComponent* = ref object of VNode ## The abstract class for every karax component. VComponent* = ref object of VNode ## The abstract class for every karax component.
renderImpl*: proc(self: VComponent): VNode renderImpl*: proc(self: VComponent): VNode
changedImpl*: proc(self: VComponent): bool changedImpl*: proc(self, newInstance: VComponent): bool
updatedImpl*: proc(self: VComponent) updatedImpl*: proc(self, newInstance: VComponent)
onAttachImpl*: proc(self: VComponent) onAttachImpl*: proc(self: VComponent)
onDetachImpl*: proc(self: VComponent) onDetachImpl*: proc(self: VComponent)
version*: int ## Update this to trigger a redraw by karax. Usually you version*: int ## Update this to trigger a redraw by karax. Usually you
## should call 'markDirty' instead which is an alias for ## should call 'markDirty' instead which is an alias for
## 'inc version'. ## '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*: VNode ## Do not touch. Used by karax. The VDOM the component
## expanded to. ## expanded to.
debugId*: int
proc value*(n: VNode): cstring = n.text proc value*(n: VNode): cstring = n.text
proc `value=`*(n: VNode; v: cstring) = n.text = v 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 = proc dthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
VNode(kind: VNodeKind.dthunk, text: name, key: -1, kids: @args) 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'. ## The default implementation of 'changed'.
result = v.version != v.renderedVersion result = v.version != v.renderedVersion
proc defaultUpdatedImpl*(v: VComponent) = proc defaultUpdatedImpl*(v, newInstance: VComponent) =
v.renderedVersion = v.version discard
var gid = 0
proc getDebugId(): int =
inc(gid)
gid
template newComponent*[T](t: typeDesc[T]; template newComponent*[T](t: typeDesc[T];
render: (proc(self: VComponent): VNode) not nil, render: (proc(self: VComponent): VNode) not nil,
onAttach: proc(self: VComponent) = nil, onAttach: proc(self: VComponent) = nil,
onDetach: proc(self: VComponent) = nil, onDetach: proc(self: VComponent) = nil,
changed: (proc(self: VComponent): bool) = defaultChangedImpl, changed: (proc(self, newInstance: VComponent): bool) = defaultChangedImpl,
updated: proc(self: VComponent) = defaultUpdatedImpl): T = updated: proc(self, newInstance: VComponent) = defaultUpdatedImpl): T =
## Use this template to create new components. ## Use this template to create new components.
T(kind: VNodeKind.component, key: -1, T(kind: VNodeKind.component, key: -1,
text: cstring(astToStr(t)), renderImpl: render, text: cstring(astToStr(t)), renderImpl: render,
changedImpl: changed, updatedImpl: updated, changedImpl: changed, updatedImpl: updated,
onAttachImpl: onAttach, onDetachImpl: onDetach) onAttachImpl: onAttach, onDetachImpl: onDetach,
debugId: getDebugId())
template markDirty*(c: VComponent) = template markDirty*(c: VComponent) =
## mark the component as dirty so that it is re-rendered. ## mark the component as dirty so that it is re-rendered.

View file

@ -1,7 +1,7 @@
import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils
type TextInput* = ref object of VComponent type TextInput* = ref object of VComponent
value: cstring value, guid: cstring
isActive: bool isActive: bool
onchange: proc (value: cstring) onchange: proc (value: cstring)
@ -35,28 +35,41 @@ proc render(x: VComponent): VNode =
proc flip(ev: Event; n: VNode) = proc flip(ev: Event; n: VNode) =
self.isActive = not self.isActive self.isActive = not self.isActive
kout cstring"onflip", n.value echo "flip! ", self.isActive, " id: ", self.debugId, " version ", self.version
markDirty(self) markDirty(self)
proc onchanged(ev: Event; n: VNode) = proc onchanged(ev: Event; n: VNode) =
if self.onchange != nil and self.value != n.value: if self.onchange != nil:
self.onchange n.value self.onchange n.value
self.value = n.value self.value = n.value
result = buildHtml(tdiv(style=style)): result = buildHtml(tdiv(style=style)):
input(style=inputStyle, value=self.value, onblur=flip, onfocus=flip, onkeyup=onchanged) input(style=inputStyle, value=self.value, onblur=flip, onfocus=flip, onkeyup=onchanged)
proc setValue(x: TextInput; value: cstring) = proc changed(current, next: VComponent): bool =
x.value = value let current = TextInput(current)
markDirty(x) 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 = onchange: proc(v: cstring) = nil): TextInput =
result = newComponent(TextInput, render) result = newComponent(TextInput, render, changed=changed, updated=update)
result.style = style result.style = style
result.value = value result.value = value
result.onchange = onchange result.onchange = onchange
result.guid = guid
when false:
type type
Combined = ref object of VComponent Combined = ref object of VComponent
a, b: TextInput a, b: TextInput
@ -90,18 +103,10 @@ var
persons: seq[cstring] = @[cstring"Karax", "Abathur", "Fenix"] persons: seq[cstring] = @[cstring"Karax", "Abathur", "Fenix"]
selected = -1 selected = -1
errmsg = cstring"" 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 renderPerson(text: cstring, index: int): VNode =
proc select(ev: Event, n: VNode) = proc select(ev: Event, n: VNode) =
selected = index selected = index
ti.setValue(persons[selected])
result = buildHtml(): result = buildHtml():
tdiv(onClick=select): tdiv(onClick=select):
@ -113,7 +118,13 @@ proc createDom(): VNode =
for index, text in persons.pairs: for index, text in persons.pairs:
renderPerson(text, index) renderPerson(text, index)
tdiv: 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: tdiv:
text errmsg text errmsg