sane markDirty() mechanism for components

This commit is contained in:
Andreas Rumpf 2017-06-21 17:20:31 +02:00
commit d9648be60c
6 changed files with 41 additions and 27 deletions

15
experiments/carousel.html Normal file
View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<title>Carousel app</title>
</head>
<body id="body">
<div id="ROOT" />
<script type="text/javascript" src="nimcache/carousel.js"></script>
</body>
</html>

View file

@ -1,3 +1,4 @@
## This demo shows how you can develop your own stateful components with Karax.
import vdom, vstyles, karax, karaxdsl, jdict, jstrutils, kdom import vdom, vstyles, karax, karaxdsl, jdict, jstrutils, kdom
@ -7,7 +8,6 @@ type
cntdown, myid: int cntdown, myid: int
timer: TimeOut timer: TimeOut
list: seq[cstring] list: seq[cstring]
change: bool
const ticksUntilChange = 5 const ticksUntilChange = 5
@ -24,11 +24,10 @@ proc render(x: VComponent): VNode =
self.cntdown = ticksUntilChange self.cntdown = ticksUntilChange
else: else:
self.timer = setTimeout(docount, 30) self.timer = setTimeout(docount, 30)
self.change = true markDirty(self)
redraw() redraw()
proc onclick(ev: Event; n: VNode) = proc onclick(ev: Event; n: VNode) =
self.change = false
if self.timer != nil: if self.timer != nil:
clearTimeout(self.timer) clearTimeout(self.timer)
self.timer = setTimeout(docount, 30) self.timer = setTimeout(docount, 30)
@ -51,12 +50,8 @@ proc render(x: VComponent): VNode =
var gid: int var gid: int
proc changed(c: VComponent): bool =
let x = Carousel(c)
result = x.change
proc carousel(): Carousel = proc carousel(): Carousel =
result = newComponent(Carousel, render, changed) result = newComponent(Carousel, render)
result.list = images result.list = images
result.cntdown = ticksUntilChange result.cntdown = ticksUntilChange
result.myid = gid result.myid = gid

View file

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Todo app</title>
</head>
<body id="body">
<div id="ROOT" />
<script type="text/javascript" src="nimcache/example.js"></script>
</body>
</html>

View file

@ -225,6 +225,7 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode;
assert oldNode.kind == VNodeKind.component assert oldNode.kind == VNodeKind.component
let x = VComponent(oldNode) let x = VComponent(oldNode)
n = vnodeToDom(x.renderImpl(x), kxi) n = vnodeToDom(x.renderImpl(x), kxi)
x.updatedImpl(x)
result = true result = true
else: else:
detach(oldNode) detach(oldNode)

View file

@ -119,11 +119,16 @@ type
dom*: Node ## the attached real DOM node. Can be 'nil' if 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. ## is not part of the virtual DOM anymore.
VComponent* = ref object of VNode 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: VComponent): bool
updatedImpl*: proc(self: 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
## should call 'markDirty' instead which is an alias for
## 'inc version'.
renderedVersion*: int ## Do not touch. Used by karax.
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
@ -143,15 +148,28 @@ 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 =
## The default implementation of 'changed'.
result = v.version != v.renderedVersion
proc defaultUpdatedImpl*(v: VComponent) =
v.renderedVersion = v.version
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,
changed: (proc(self: VComponent): bool) not nil,
onAttach: proc(self: VComponent) = nil, onAttach: proc(self: VComponent) = nil,
onDetach: proc(self: VComponent) = nil): T = onDetach: proc(self: VComponent) = nil,
changed: (proc(self: VComponent): bool) = defaultChangedImpl,
updated: proc(self: 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, onAttachImpl: onAttach, onDetachImpl: onDetach) changedImpl: changed, updatedImpl: updated,
onAttachImpl: onAttach, onDetachImpl: onDetach)
template markDirty*(c: VComponent) =
## mark the component as dirty so that it is re-rendered.
inc c.version
proc setAttr*(n: VNode; key: cstring; val: cstring = "") = proc setAttr*(n: VNode; key: cstring; val: cstring = "") =
if n.attrs.isNil: if n.attrs.isNil: