diff --git a/experiments/nextgen.nim b/experiments/nextgen.nim index 4f955aa..d311eb9 100644 --- a/experiments/nextgen.nim +++ b/experiments/nextgen.nim @@ -1,6 +1,25 @@ import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils, reactive +proc newTextInput*(text: RString; focus: RBool): VNode = + proc onFlip(ev: Event; target: VNode) = + focus <- not focus.value + + proc onKeyupEnter(ev: Event; target: VNode) = + text <- target.value + #text.notifyObservers() + + proc onkeyup(ev: Event; n: VNode) = + # keep displayValue up to date, but do not tell the client yet! + text.value = n.value + + result = buildHtml(input(`type`="text", + value=text.value, onblur=onFlip, onfocus=onFlip, + onkeyupenter=onkeyupenter, onkeyup=onkeyup, setFocus=focus.value)) + +var + errmsg = rstr("") + type User = ref object of ReactiveBase firstname, lastname: cstring @@ -8,6 +27,43 @@ type var gu = newRSeq(@[ (User(firstname: "Some", lastname: "Body")), (User(firstname: "Some", lastname: "One")), (User(firstname: "Some", lastname: "Two"))]) +var selected = newReactive[User](nil) + +proc toUI*(): RString = + result = RString() + result.subscribe proc (v: cstring) = + if v.len > 0: + if selected >= 0: + # XXX what's happening here? + persons[selected].firstname = v + notifyObservers(persons[selected]) + errmsg <- "" + else: + errmsg <- "name must not be empty" + +var inp = toUI() + +proc adaptFocus(def = false): RBool = + result = RBool() + result.value = def + result.subscribe proc (hasFocus: bool) = + if not hasFocus: + inp.notifyObservers() + +var focus = adaptFocus() + +proc styler(): VStyle = + result = 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 8px " & (if focus.value: cstring"red" else: cstring"black")), + (StyleAttr.fontSize, cstring"12px"), + (StyleAttr.fontWeight, cstring"600") + ) + var clicks = 0 discard """ @@ -22,20 +78,23 @@ discard """ t """ -proc renderUser(u: User): VNode = #{.track.} = - proc inner(u: User): VNode = - result = buildHtml(tdiv): - text u.firstname & " " & u.lastname - button: - text "(X)" - proc onclick(ev: Event; n: VNode) = - #u.firstname = "kfdj" - #u.lastname = &clicks - #notifyObservers(u) - gu.deleteElem(u) +template observe(s: cstring): RString = + let tmp = rstr(s) + u.subscribeSelf proc () = + tmp <- s + temp - result = inner(u) - doTrack(u, result, inner(u)) +proc renderUser(u: User; selected: RBool): VNode = + result = buildHtml(tdiv): + let displayName = observe(u.firstname & " " & u.lastname) + if selected == u: + newTextInput displayName, focus + else: + text displayName + button: + text "(x)" + proc onclick(ev: Event; n: VNode) = + gu.deleteElem(u) template vmap(x: RSeq; elem, f: untyped): VNode = let tmp = buildHtml(elem): @@ -44,18 +103,18 @@ template vmap(x: RSeq; elem, f: untyped): VNode = doTrackResize(x, tmp, f(x[pos])) tmp -proc main(gu: RSeq[User]): VNode = #{.track.} = - proc inner(gu: RSeq[User]): VNode = - result = buildHtml(tdiv): - vmap(gu, tdiv, renderUser) - tdiv: - button: - text "Add User" - proc onclick(ev: Event; n: VNode) = - inc clicks - gu.add User(firstname: "Added", lastname: &clicks) - result = inner(gu) - doTrack(gu, result, inner(gu)) +proc main(gu: RSeq[User]): VNode = + result = buildHtml(tdiv): + tdiv: + button: + text "Add User" + proc onclick(ev: Event; n: VNode) = + inc clicks + gu.add User(firstname: "Added", lastname: &clicks) + tdiv: + text errmsg + vmap(gu, tdiv, renderUser) + proc init(): VNode = main(gu) diff --git a/src/reactive.nim b/src/reactive.nim index 4fcc851..a0944a9 100644 --- a/src/reactive.nim +++ b/src/reactive.nim @@ -8,6 +8,7 @@ type Mark Inserted Deleted + Replaced State = object stale: int @@ -70,7 +71,7 @@ template wrapObserver(f: untyped) = of Mark: if state.stale == 0: x.broadcast(msg, pos) inc state.stale - of Inserted, Deleted: + of Inserted, Deleted, Replaced: dec state.stale if state.stale == 0: x.broadcast(msg, pos) @@ -92,12 +93,20 @@ proc notifyObservers*(x: ReactiveBase) = x.broadcast(Mark) x.broadcast(Changed) -proc subscribe[T](x: Reactive[T], f: proc(x: T)) = +proc subscribeVal*[T](x: Reactive[T], f: proc(x: T)) = let reactor = proc (msg: Message, pos: int) = case msg: of Mark: discard of Changed, Unchanged: f(x.value) - of Inserted, Deleted: discard + of Inserted, Deleted, Replaced: discard + x.addSink reactor + +proc subscribeSelf*[T: ReactiveBase](x: T, f: proc()) = + let reactor = proc (msg: Message, pos: int) = + case msg: + of Mark: discard + of Changed, Unchanged: f() + of Inserted, Deleted, Replaced: discard x.addSink reactor template lift1(op: untyped) = @@ -127,6 +136,8 @@ proc newRSeq*[T](data: seq[T]): RSeq[T] = proc `[]=`*[T](x: RSeq[T]; index: int; v: T) = x.s[index] = v + x.broadcast(Mark) + x.broadcast(Replaced, index) proc `[]`*[T](x: RSeq[T]; index: int): T = x.s[index] proc len*[T](x: RSeq[T]): int = x.s.len diff --git a/src/vdom.nim b/src/vdom.nim index bf84cb8..63f6669 100644 --- a/src/vdom.nim +++ b/src/vdom.nim @@ -239,8 +239,15 @@ proc tree*(kind: VNodeKind; attrs: openarray[(cstring, cstring)]; result = tree(kind, kids) for a in attrs: result.setAttr(a[0], a[1]) -proc text*(s: string): VNode = VNode(kind: VNodeKind.text, text: cstring(s), index: -1) -proc text*(s: cstring): VNode = VNode(kind: VNodeKind.text, text: s, index: -1) +proc rawtext*(s: string): VNode = VNode(kind: VNodeKind.text, text: cstring(s), index: -1) +proc rawtext*(s: cstring): VNode = VNode(kind: VNodeKind.text, text: s, index: -1) + +import reactive + +proc text*(s: RString): VNode = + result = VNode(kind: VNodeKind.text, text: s, index: -1) + s.subscribe proc(v: cstring) = + result.dom.value = v iterator items*(n: VNode): VNode = for i in 0..