better event handling for components and in the DSL
This commit is contained in:
parent
91d7ba25ea
commit
507094bdfd
4 changed files with 183 additions and 3 deletions
|
|
@ -61,6 +61,7 @@ template setNativeValue(ev, val) = cast[Element](ev.target).value = val
|
||||||
template keyeventBody() =
|
template keyeventBody() =
|
||||||
let v = nativeValue(ev)
|
let v = nativeValue(ev)
|
||||||
n.value = v
|
n.value = v
|
||||||
|
assert action != nil
|
||||||
action(ev, n)
|
action(ev, n)
|
||||||
if n.value != v:
|
if n.value != v:
|
||||||
setNativeValue(ev, n.value)
|
setNativeValue(ev, n.value)
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,19 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||||
result.add n[0]
|
result.add n[0]
|
||||||
for i in 1 ..< n.len:
|
for i in 1 ..< n.len:
|
||||||
result.add tcall2(n[i], tmpContext)
|
result.add tcall2(n[i], tmpContext)
|
||||||
|
of nnkProcDef:
|
||||||
|
let name = getName n[0]
|
||||||
|
if name.startsWith"on":
|
||||||
|
# turn it into an anon proc:
|
||||||
|
let anon = copyNimTree(n)
|
||||||
|
anon[0] = newEmptyNode()
|
||||||
|
if tmpContext == nil:
|
||||||
|
error "no VNode to attach the event handler to"
|
||||||
|
else:
|
||||||
|
result = newCall(bindSym"addEventHandler", tmpContext,
|
||||||
|
newDotExpr(bindSym"EventKind", n[0]), anon, ident("kxi"))
|
||||||
|
else:
|
||||||
|
result = n
|
||||||
of nnkVarSection, nnkLetSection, nnkConstSection:
|
of nnkVarSection, nnkLetSection, nnkConstSection:
|
||||||
result = n
|
result = n
|
||||||
of nnkCallKinds:
|
of nnkCallKinds:
|
||||||
|
|
@ -87,6 +100,8 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||||
result.add newDotAsgn(tmp, key, x[1])
|
result.add newDotAsgn(tmp, key, x[1])
|
||||||
elif eqIdent(key, "setFocus"):
|
elif eqIdent(key, "setFocus"):
|
||||||
result.add newCall(key, tmp, x[1], ident"kxi")
|
result.add newCall(key, tmp, x[1], ident"kxi")
|
||||||
|
elif eqIdent(key, "events"):
|
||||||
|
result.add newCall(bindSym"mergeEvents", tmp, x[1])
|
||||||
else:
|
else:
|
||||||
result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1])
|
result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1])
|
||||||
elif ck != ComponentKind.Tag:
|
elif ck != ComponentKind.Tag:
|
||||||
|
|
@ -100,7 +115,28 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||||
else:
|
else:
|
||||||
result.add newCall(bindSym"add", tmpContext, tmp)
|
result.add newCall(bindSym"add", tmpContext, tmp)
|
||||||
elif tmpContext != nil and op notin StmtContext:
|
elif tmpContext != nil and op notin StmtContext:
|
||||||
|
var hasEventHandlers = false
|
||||||
|
for i in 1..<n.len:
|
||||||
|
let it = n[i]
|
||||||
|
if it.kind in {nnkProcDef, nnkStmtList}:
|
||||||
|
hasEventHandlers = true
|
||||||
|
break
|
||||||
|
if not hasEventHandlers:
|
||||||
result = newCall(bindSym"add", tmpContext, n)
|
result = newCall(bindSym"add", tmpContext, n)
|
||||||
|
else:
|
||||||
|
let tmp = genSym(nskLet, "tmp")
|
||||||
|
var slicedCall = newCall(n[0])
|
||||||
|
let ex = newTree(nnkStmtListExpr)
|
||||||
|
ex.add newEmptyNode() # will become the let statement
|
||||||
|
for i in 1..<n.len:
|
||||||
|
let it = n[i]
|
||||||
|
if it.kind in {nnkProcDef, nnkStmtList}:
|
||||||
|
ex.add tcall2(it, tmp)
|
||||||
|
else:
|
||||||
|
slicedCall.add it
|
||||||
|
ex[0] = newLetStmt(tmp, slicedCall)
|
||||||
|
ex.add tmp
|
||||||
|
result = newCall(bindSym"add", tmpContext, ex)
|
||||||
elif op == "!" and n.len == 2:
|
elif op == "!" and n.len == 2:
|
||||||
result = n[1]
|
result = n[1]
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
19
src/vdom.nim
19
src/vdom.nim
|
|
@ -105,6 +105,9 @@ buildLookupTables()
|
||||||
type
|
type
|
||||||
EventHandler* = proc (ev: Event; target: VNode) {.closure.}
|
EventHandler* = proc (ev: Event; target: VNode) {.closure.}
|
||||||
NativeEventHandler* = proc (ev: Event) {.closure.}
|
NativeEventHandler* = proc (ev: Event) {.closure.}
|
||||||
|
|
||||||
|
EventHandlers* = seq[(EventKind, EventHandler, NativeEventHandler)]
|
||||||
|
|
||||||
VKey* = cstring
|
VKey* = cstring
|
||||||
|
|
||||||
VNode* = ref object of RootObj
|
VNode* = ref object of RootObj
|
||||||
|
|
@ -114,7 +117,7 @@ type
|
||||||
kids: seq[VNode]
|
kids: seq[VNode]
|
||||||
# even index: key, odd index: value; done this way for memory efficiency:
|
# even index: key, odd index: value; done this way for memory efficiency:
|
||||||
attrs: seq[cstring]
|
attrs: seq[cstring]
|
||||||
events*: seq[(EventKind, EventHandler, NativeEventHandler)]
|
events*: EventHandlers
|
||||||
when false:
|
when false:
|
||||||
hash*: Hash
|
hash*: Hash
|
||||||
validHash*: bool
|
validHash*: bool
|
||||||
|
|
@ -157,9 +160,21 @@ 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, index: -1, kids: @args)
|
VNode(kind: VNodeKind.dthunk, text: name, index: -1, kids: @args)
|
||||||
|
|
||||||
|
proc setEvent(v: VNode; kind: EventKind; handler: EventHandler) =
|
||||||
|
assert handler != nil
|
||||||
|
for i in 0..<v.events.len:
|
||||||
|
if v.events[i][0] == kind:
|
||||||
|
v.events[i][1] = handler
|
||||||
|
return
|
||||||
|
v.events.add((kind, handler, nil))
|
||||||
|
|
||||||
|
proc mergeEvents*(v: VNode; handlers: EventHandlers) =
|
||||||
|
## Overrides or adds the event handlers to `v`'s internal event handler list.
|
||||||
|
for h in handlers: v.setEvent(h[0], h[1])
|
||||||
|
|
||||||
proc defaultChangedImpl*(v, newInstance: 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.key != newInstance.key or v.version != v.renderedVersion
|
||||||
|
|
||||||
proc defaultUpdatedImpl*(v, newInstance: VComponent) =
|
proc defaultUpdatedImpl*(v, newInstance: VComponent) =
|
||||||
discard
|
discard
|
||||||
|
|
|
||||||
128
tests/components.nim
Normal file
128
tests/components.nim
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils
|
||||||
|
|
||||||
|
type TextInput* = ref object of VComponent
|
||||||
|
value: cstring
|
||||||
|
isActive: bool
|
||||||
|
|
||||||
|
proc render(x: VComponent): VNode =
|
||||||
|
let self = TextInput(x)
|
||||||
|
|
||||||
|
let style = style(
|
||||||
|
(StyleAttr.position, cstring"relative"),
|
||||||
|
(StyleAttr.paddingLeft, cstring"10px"),
|
||||||
|
(StyleAttr.paddingRight, cstring"5px"),
|
||||||
|
(StyleAttr.height, cstring"30px"),
|
||||||
|
(StyleAttr.lineHeight, cstring"30px"),
|
||||||
|
(StyleAttr.border, cstring"solid 1px " & (if self.isActive: cstring"red" else: cstring"black")),
|
||||||
|
(StyleAttr.fontSize, cstring"12px"),
|
||||||
|
(StyleAttr.fontWeight, cstring"600")
|
||||||
|
).merge(self.style)
|
||||||
|
|
||||||
|
let inputStyle = style.merge(style(
|
||||||
|
(StyleAttr.color, cstring"inherit"),
|
||||||
|
(StyleAttr.fontSize, cstring"inherit"),
|
||||||
|
(StyleAttr.fontWeight, cstring"inherit"),
|
||||||
|
(StyleAttr.fontFamily, cstring"inherit"),
|
||||||
|
(StyleAttr.position, cstring"absolute"),
|
||||||
|
(StyleAttr.top, cstring"0"),
|
||||||
|
(StyleAttr.left, cstring"0"),
|
||||||
|
(StyleAttr.height, cstring"100%"),
|
||||||
|
(StyleAttr.width, cstring"100%"),
|
||||||
|
(StyleAttr.border, cstring"none"),
|
||||||
|
(StyleAttr.backgroundColor, cstring"transparent"),
|
||||||
|
))
|
||||||
|
|
||||||
|
proc flip(ev: Event; n: VNode) =
|
||||||
|
self.isActive = not self.isActive
|
||||||
|
echo "flip! ", self.isActive, " id: ", self.debugId, " version ", self.version
|
||||||
|
markDirty(self)
|
||||||
|
|
||||||
|
result = buildHtml(tdiv(style=style)):
|
||||||
|
input(style=inputStyle, value=self.value, onblur=flip, onfocus=flip,
|
||||||
|
events=self.events)
|
||||||
|
|
||||||
|
proc update(current, next: VComponent) =
|
||||||
|
let current = TextInput(current)
|
||||||
|
let next = TextInput(next)
|
||||||
|
current.value = next.value
|
||||||
|
current.key = next.key
|
||||||
|
|
||||||
|
proc changed(current, next: VComponent): bool = true
|
||||||
|
|
||||||
|
proc newTextInput*(style: VStyle = VStyle(); key: cstring;
|
||||||
|
value = cstring""): TextInput =
|
||||||
|
result = newComponent(TextInput, render, changed=changed, updated=update)
|
||||||
|
result.style = style
|
||||||
|
result.value = value
|
||||||
|
result.key = key
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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"]
|
||||||
|
errmsg = cstring""
|
||||||
|
|
||||||
|
proc renderPerson(text: cstring, index: int): VNode =
|
||||||
|
result = buildHtml(tdiv):
|
||||||
|
newTextInput(VStyle(), &index, text):
|
||||||
|
proc onkeyuplater(ev: Event; n: VNode) =
|
||||||
|
let v = n.value
|
||||||
|
if v.len > 0:
|
||||||
|
persons[index] = v
|
||||||
|
errmsg = ""
|
||||||
|
else:
|
||||||
|
errmsg = "name must not be empty"
|
||||||
|
button:
|
||||||
|
proc onclick(ev: Event; n: VNode) =
|
||||||
|
persons.delete(index)
|
||||||
|
errmsg = ""
|
||||||
|
echo persons
|
||||||
|
text "(x)"
|
||||||
|
|
||||||
|
|
||||||
|
proc createDom(): VNode =
|
||||||
|
result = buildHtml(tdiv):
|
||||||
|
tdiv:
|
||||||
|
for index, text in persons.pairs:
|
||||||
|
renderPerson(text, index)
|
||||||
|
tdiv:
|
||||||
|
newTextInput(VStyle(), &persons.len, ""):
|
||||||
|
proc onkeyuplater(ev: Event; n: VNode) =
|
||||||
|
let v = n.value
|
||||||
|
if v.len > 0:
|
||||||
|
persons.add v
|
||||||
|
errmsg = ""
|
||||||
|
else:
|
||||||
|
errmsg = "name must not be empty"
|
||||||
|
tdiv:
|
||||||
|
text errmsg
|
||||||
|
|
||||||
|
setRenderer createDom
|
||||||
Loading…
Add table
Add a link
Reference in a new issue