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() =
|
||||
let v = nativeValue(ev)
|
||||
n.value = v
|
||||
assert action != nil
|
||||
action(ev, n)
|
||||
if n.value != v:
|
||||
setNativeValue(ev, n.value)
|
||||
|
|
|
|||
|
|
@ -59,6 +59,19 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
|||
result.add n[0]
|
||||
for i in 1 ..< n.len:
|
||||
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:
|
||||
result = n
|
||||
of nnkCallKinds:
|
||||
|
|
@ -87,6 +100,8 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
|||
result.add newDotAsgn(tmp, key, x[1])
|
||||
elif eqIdent(key, "setFocus"):
|
||||
result.add newCall(key, tmp, x[1], ident"kxi")
|
||||
elif eqIdent(key, "events"):
|
||||
result.add newCall(bindSym"mergeEvents", tmp, x[1])
|
||||
else:
|
||||
result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1])
|
||||
elif ck != ComponentKind.Tag:
|
||||
|
|
@ -100,7 +115,28 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
|||
else:
|
||||
result.add newCall(bindSym"add", tmpContext, tmp)
|
||||
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)
|
||||
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:
|
||||
result = n[1]
|
||||
else:
|
||||
|
|
|
|||
19
src/vdom.nim
19
src/vdom.nim
|
|
@ -105,6 +105,9 @@ buildLookupTables()
|
|||
type
|
||||
EventHandler* = proc (ev: Event; target: VNode) {.closure.}
|
||||
NativeEventHandler* = proc (ev: Event) {.closure.}
|
||||
|
||||
EventHandlers* = seq[(EventKind, EventHandler, NativeEventHandler)]
|
||||
|
||||
VKey* = cstring
|
||||
|
||||
VNode* = ref object of RootObj
|
||||
|
|
@ -114,7 +117,7 @@ type
|
|||
kids: seq[VNode]
|
||||
# even index: key, odd index: value; done this way for memory efficiency:
|
||||
attrs: seq[cstring]
|
||||
events*: seq[(EventKind, EventHandler, NativeEventHandler)]
|
||||
events*: EventHandlers
|
||||
when false:
|
||||
hash*: Hash
|
||||
validHash*: bool
|
||||
|
|
@ -157,9 +160,21 @@ proc vthunk*(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)
|
||||
|
||||
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 =
|
||||
## 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) =
|
||||
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