-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
nbNewCode + Generate javascript from Nim #87
Comments
can you expand a little bit more the example you give? I am still not sure what the use case would be and what the code is supposed to do. |
Let's say you want a block that is a counter (button + label) that increases every time you press it. In psuedo-HTML it would be: <p id="label">0</p>
<button id="btn">Click me</button>
<script>
const btn = document.getElementById("btn")
const label = document.getElementById("label")
let count = 0
btn.addEventlistener("click", () => {
count++
label.innerHtml = count
})
</script> Now if we want to implement this kind of block in nimib we would first use template counterButton:
nbRawOutput: """
<p id="label">0</p>
<button id="btn">Click me</button>
"""
nbJsCode:
import std/dom
var counter = 0
# I don't know how std/dom work so this is very psuedo:
var btn = document.getElementById("btn")
var label = document.getElementById("label")
btn.addEventListener("click",
proc() =
counter += 1
label.innerHtml = $counter
) So far everything works just fine. But what if I create two of these buttons?
Because the button and label tags in both blocks have the same ids we will be in trouble. So then we create unique ids for each one. For example by passing in the id as a string: template counterButton(id: string):
nbRawOutput: """
<p id=fmt"label{id}">0</p>
<button id=fmt"btn{id}">Click me</button>
"""
nbJsCode:
import std/dom
var counter = 0
# I don't know how std/dom work so this is very psuedo:
var btn = document.getElementById(fmt"btn{id}")
var label = document.getElementById(fmt"label{id}")
btn.addEventListener("click",
proc() =
counter += 1
label.innerHtml = $counter
) This is where we get into trouble though! Because how can the code in the Please tell me if anything was unclear 😄 |
perfectly clear thanks! Now I understand why this can be an issue in some cases, especially when we want to make reusable widgets. Let's say that this is the case where you want the Js code to change according to some context. I would say that the better solution for this could be to abandon the untyped version of template counterButton(id: string):
nbRawOutput: fmt"""
<p id=fmt"label{id}">0</p>
<button id=fmt"btn{id}">Click me</button>
"""
nbJsCode: fmt"""
import std/dom
var counter = 0
# I don't know how std/dom work so this is very psuedo:
var btn = document.getElementById(fmt"btn{id}")
var label = document.getElementById(fmt"label{id}")
btn.addEventListener("click",
proc() =
counter += 1
#label.innerHtml = $counter
)
""" here you lose the advantage of the untyped version, but not being executed the only change is that syntax is checked and highlighted (and if this is a library element it is not a big deal to specify the string instead of the code). Alternatively, a more hacky solution could be to call the untyped version and then call something like |
Writing Nim code in a string is nearly as bad as writing javascript in a string to me 😅 Plus simple string interpolation won't work for objects. If we define a type like this: type
Context = object
field: float
count: int Then the string version of it isn't valid Nim-code for creating it: echo $Context()
# (field: 0.0, count: 0)
echo Context().repr
# [field = 0.0,
# count = 0] So we will have to do some sort of serialization to get it into a suitable string format. And sure we could do But the same underlying structure could be used for the string-version and the untyped-version as the untyped code will become a string sooner or later either way. So I'm insisting on having both of them available although we could start with just the string version to get going. I'll happily write the required macro so don't worry about that 😜 I guess I'll have to add a |
A working Proof of concept: template nbJsCode*(code: string) =
writeFile("code.nim", code)
discard execShellCmd("nim js -d:danger -o:code.js code.nim")
let jscode = readFile("code.js")
nbRawOutput: "<script>\n" & jscode & "\n</script>"
nbText: "Counter POC"
nbRawOutput: """
<p id="label">0</p>
<button id="btn">Click me</button>
"""
nbJsCode: """
import std/dom
echo "Hello world!"
var label = getElementById("label".cstring)
var button = getElementById("btn".cstring)
var counter: int = 0
button.addEventListener("click",
proc (ev: Event) =
counter += 1
label.innerHtml = ($counter).cstring
)
""" The next step I guess is to implement a proper "system" á la |
The name type
NbCodeScript* = ref object
code*: string
template nbNewCode*(codeString: string): NbCodeScript =
NbCodeScript(code: codeString)
template addCode*(script: NbCodeScript, codeString: string) =
script.code &= "\n" & codeString
template addToDocAsJs*(script: NbCodeScript) =
writeFile("code.nim", script.code)
discard execShellCmd("nim js -d:danger -o:code.js code.nim")
let jscode = readFile("code.js")
nbRawOutput: "<script>\n" & jscode & "\n</script>"
template nbJsCode*(code: string) =
let script = nbNewCode(code)
script.addToDocAsJs |
I've finally gotten the untyped version to work (a lot thanks to @Vindaar's macro magic). He also had an idea, but let's start with the problem. As it stands I need the macro to be untyped because otherwise, we will get I modified this idea a bit into this: template nbNewCode*(args: varargs[untyped]): untyped =
let code = nimToJsString(args)
NbCodeScript(code: code) where And here is a working example which prints out let k = 3
let script = nbNewCode(k):
let a = 1
let b = 2
let c = a + k
echo c
script.addToDocAsJs What do you think about this approach of unifying |
Amazing! Looks great! |
Nice, I'll do some cleanup and create a PR later tonight then 👍 And then It's just a matter of trying it out and finding the bugs! |
closed by #88 (different api name, not |
As the scope of this grew beyond just nbFile it might as well get its own issue. Continuation on discussion in #34.
ping @ajusa saw you had an interest in something like this in the #28. Feel free to weigh in. If we could use client-side karax using this as well it would be really cool as we would be able to create interactive elements without having to leave Nim-land.
This is the API suggestion thus far:
But there are still things lacking like being able to capture variables from the compiled to the javascript version. We will essentially have to inline the values in the code and my current best idea is something like:
We serialize the variable on the C-side and do some sort of string interpolation to replace all occurrences of the variable by
fromJson("json value of the variable")
.And for this, I think we will eventually need a preprocessing macro to for example normalize all variable names and check that the user doesn't do anything like
var nimVar = nimVar
in which case we shouldn't replace it with the serialized variable anymore. But this can be added at the very end so let's not focus on it right now.The text was updated successfully, but these errors were encountered: