Merge branch 'stateful-components3' into devel
This commit is contained in:
commit
dc7409e52e
3 changed files with 73 additions and 51 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
26
src/vdom.nim
26
src/vdom.nim
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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,73 +35,78 @@ 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
|
||||||
|
|
||||||
type
|
when false:
|
||||||
Combined = ref object of VComponent
|
type
|
||||||
a, b: TextInput
|
Combined = ref object of VComponent
|
||||||
|
a, b: TextInput
|
||||||
|
|
||||||
proc renderComb(self: VComponent): VNode =
|
proc renderComb(self: VComponent): VNode =
|
||||||
let self = Combined(self)
|
let self = Combined(self)
|
||||||
|
|
||||||
proc bu(ev: Event; n: VNode) =
|
proc bu(ev: Event; n: VNode) =
|
||||||
self.a.value = ""
|
self.a.value = ""
|
||||||
self.b.value = ""
|
self.b.value = ""
|
||||||
markDirty(self.a)
|
markDirty(self.a)
|
||||||
markDirty(self.b)
|
markDirty(self.b)
|
||||||
|
|
||||||
result = buildHtml(tdiv(style=self.style)):
|
result = buildHtml(tdiv(style=self.style)):
|
||||||
self.a
|
self.a
|
||||||
self.b
|
self.b
|
||||||
button(onclick=bu):
|
button(onclick=bu):
|
||||||
text "reset"
|
text "reset"
|
||||||
|
|
||||||
proc changed(self: VComponent): bool =
|
proc changed(self: VComponent): bool =
|
||||||
let self = Combined(self)
|
let self = Combined(self)
|
||||||
result = self.a.changedImpl(self.a) or self.b.changedImpl(self.b)
|
result = self.a.changedImpl(self.a) or self.b.changedImpl(self.b)
|
||||||
|
|
||||||
proc newCombined*(style: VStyle = VStyle()): Combined =
|
proc newCombined*(style: VStyle = VStyle()): Combined =
|
||||||
result = newComponent(Combined, renderComb, changed=changed)
|
result = newComponent(Combined, renderComb, changed=changed)
|
||||||
result.a = newTextInput(style, "AAA")
|
result.a = newTextInput(style, "AAA")
|
||||||
result.b = newTextInput(style, "BBB")
|
result.b = newTextInput(style, "BBB")
|
||||||
|
|
||||||
|
|
||||||
var
|
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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue