Nim and static type noob here. So the problem is when I compile my code and run it in browser, fill the name and description fields and click add thing button, my browser console gives following cryptic runtime errors:
TypeError: c_225079 is null quicktest.js:579:16
cstrToNimstr quicktest.js:579
HEX24_10841036 quicktest.js:2878
HEX3Aanonymous_10850242 quicktest.js:3172
wrapper_10750162 quicktest.js:3049
HEX3Aanonymous_10190109 quicktest.js:1234
I cannot interpret the error given, it seems that the error has something to do with the cstring -> string transformation. I've isolated the problem somewhat to the handler proc actionAddThing and the getVNodeById karax procedure. When I substitute the name and desc values into something more static eg. "foo" and "bar" the script works. (well of course I cannot capture user inputs but anyways...)
Here is the rather verbose code (sorry it's already pretty stripped version of the original code) with some commenting of my intents:
include karax/prelude
# These are used in the html text input field's id:
const
IdInputThingName: kstring = "inputThingName"
IdInputThingDesc: kstring = "inputThingDesc"
type
Thing = ref object
uid: kstring
name: kstring
desc: kstring
proc `$`(t: Thing): string =
return "Thing(uid=" & $t.uid & ", name=" & $t.name & ", desc=" & $t.desc & ")"
# Some getters for thing, after creating the thing, it's attributes shouldn't
# be edited.
proc getName*(thing: Thing): kstring =
return thing.name
proc getUid*(thing: Thing): kstring =
return thing.uid
proc getDesc*(thing: Thing): kstring =
return thing.desc
proc newThing(uid, name, desc: kstring): Thing =
result = Thing(uid: uid, name: name, desc: desc)
# proc which returns the thing factory with the id initialised to zero.
# the returned closure factory will increment the id everytime the thing
# is created and passes the uid to Thing constructor.
proc thingBuilder(): proc(name, desc: kstring): Thing =
var nextId = 0
return proc (name, desc: kstring): Thing =
nextId.inc()
result = newThing("uid-" & $nextId, name, desc)
# Creating the global factory:
let thingFactory = thingBuilder()
# App state and related procs/methods
type
AppState = ref object
things: seq[Thing]
proc newAppState(): AppState =
# Default init is empty things list:
result = AppState(things: @[])
# Should modify the existing app state and it's
# things list by adding the given thing into the list.
proc addThing*(self: var AppState, t: Thing) =
self.things.add(t)
# Getter for the things list in app state.
# Not sure if it's making copies or not?
proc getAllThings*(self: AppState): seq[Thing] =
result = @[]
for t in self.things:
result.add(t)
# Handler for clicking the button. Should read the
# text input field's (created in the makeControls proc) texts and create
# the thing using the thingFactory. After this it should add the created
# thing into the app state's thing list.
proc actionAddThing(state: var AppState): proc() =
return proc() =
# These doesn't work, why?!
let name = getVNodeById(IdInputThingName).value()
let desc = getVNodeById(IdInputThingDesc).value()
# If I substitute name and desc with "foo" and "bar"
# this works. WTF!? Why I cannot get the values in those nodes?
# let name = kstring("foo")
# let desc = kstring("bar")
# Create the thing based on the given name and desc:
let thing = thingFactory(name, desc)
echo("Thing created: ", $thing)
# Add the created thing into app state's thing list:
state.addThing(thing)
echo $state.getAllThings()
# Supposed to return odd string if the given number is
# odd and even if even...duh. String is used when making
# things and added into their class attribute.
proc evenOdd(idx: int): kstring =
if idx %% 2 != 0:
return "even"
return "odd"
# Make/render the thing list based on the given sequence:
proc makeThingList(tlist: seq[Thing]): VNode =
result = buildHtml(tdiv(class="thing-list")):
for idx, th in tlist:
tdiv(id=th.uid, class="thing " & evenOdd(idx)):
text kstring($th)
# Make/render the controls button and the text inputs
# necessary to create new thingies:
proc makeControls(state: var AppState): VNode =
result = buildHtml(tdiv(class="controls")):
label(`for`=IdInputThingName):
text "Thing Name: "
input(`type`="text", id=IdInputThingName, placeholder=kstring("Input the thing name here..."))
label(`for`=IdInputThingDesc):
text "Thing Description: "
input(`type`="text", id=IdInputThingDesc, placeholder=kstring("Input the thing desc here..."))
button(onclick=actionAddThing(state)):
text "Add Thing"
# Make/render the whole dom (return closure for karax):
proc createDom(state: var AppState): proc(): VNode =
return proc(): VNode =
result = buildHtml(tdiv(id="things-are-dope")):
makeThingList(state.getAllThings())
makeControls(state)
when isMainModule:
# Initialize app state as variable because I want to change it's insides later
# (this is probably a bad idea...)
var state = newAppState()
# Add few things into the app state's list of things:
state.addThing(thingFactory("Thing A", "Desc about Thing A"))
state.addThing(thingFactory("Thing B", "Desc about Thing B"))
state.addThing(thingFactory("Thing C", "Desc about Thing C"))
# Now we are ready to create the DOM:
setRenderer createDom(state)
I'm hopeful that this isn't again in the series of stupid questions... :)Well I debugged it a bit and seems like .value for both nodes is empty (nodes themselves are found without issues). And skimming through the code of Karax I don't think that this is the right way to get a value of an input, all input examples in Karax seem to use custom event handlers or set the receiving value variable themselves.
(I might be wrong though, I don't really use Karax myself)
by using karax the way you got value from vnode through data binding , get value from state or event.
or using traditional web technic document.getElementById(xxx).value or addEventListener
Thanks a ton @Yardanico and @bung, this solved a lot of trouble. I also looked at the nim forum source but didn't understand why they were using the dom.document api instead of karax's own vdom, but as @Yardanico pointed I was thinking I'm manipulating the dom.
Altought it's still not perfectly clear why there exists karax vdom at all if that is something the user shouldn't generally touch. (I'm trying to figure out some use cases for it...) Hell I don't even know what draw me to using it in the first place! :D
So @bung what you're saying is that I should use some state variable and update that via registering a procedure for input fields oninput event? And use this state variable change creating the things? So basically I should make the state variable as subject of the observer pattern? Do you have a link to some source code or example that would clear what you're saying? I liked the tradional way, it seems much more simple. Of course then I have alot const id's which I don't like...
Thank you guys/gals!
Ps. when I solve this I'm going to post my final solution.
these two ways found in readme, I dont using karax , doest know if there is dom event to state binding syntax sugar like vue's v-model, but you can always get value from event.target.value through browser native event.