Skip to content
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

Refactor nbJs #148

Merged
merged 30 commits into from
Nov 13, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dd5d345
basics are working
HugoGranstrom Oct 26, 2022
5842306
fix tests
HugoGranstrom Oct 26, 2022
68bb641
update counters.nim
HugoGranstrom Oct 26, 2022
4a15a89
exclude interactivity.nim from docs until rewritten
HugoGranstrom Oct 26, 2022
582e632
bye bye old gensym
HugoGranstrom Oct 26, 2022
d0f14a4
remove old code. So little code left :o
HugoGranstrom Oct 26, 2022
de6ff11
update interactivity.nim
HugoGranstrom Oct 27, 2022
1908383
update lots of small things
HugoGranstrom Oct 27, 2022
977aad5
update nbJs tests
HugoGranstrom Oct 27, 2022
dc4919c
update text of counters.nim
HugoGranstrom Oct 27, 2022
3d7e994
refactor code accoridng to review
HugoGranstrom Nov 3, 2022
817c844
update tests
HugoGranstrom Nov 3, 2022
517f5a6
update counters
HugoGranstrom Nov 3, 2022
d0bcc8f
make interactivty compile. Still have to update it
HugoGranstrom Nov 3, 2022
a20b2da
draft updated interactivity
HugoGranstrom Nov 5, 2022
0cb15ca
review updates
HugoGranstrom Nov 5, 2022
49f3dc0
make nimibCode official
HugoGranstrom Nov 5, 2022
189cac8
update changelog
HugoGranstrom Nov 12, 2022
ebce38b
Update docsrc/interactivity.nim
HugoGranstrom Nov 13, 2022
9fb5c03
Update src/nimib.nim
HugoGranstrom Nov 13, 2022
5609a99
Update src/nimib/renders.nim
HugoGranstrom Nov 13, 2022
c563a87
rename nbCodeToJs partial to nbJsFromCode
HugoGranstrom Nov 13, 2022
a3f6a8b
add link to changelog
HugoGranstrom Nov 13, 2022
55a8a3f
add ceasar link to interactivity
HugoGranstrom Nov 13, 2022
3eafe1c
update index and readme
HugoGranstrom Nov 13, 2022
6d42649
fix counters not showing code
HugoGranstrom Nov 13, 2022
94dec60
bump nimble version
HugoGranstrom Nov 13, 2022
c865727
bump changelog version
HugoGranstrom Nov 13, 2022
1dbee81
add nimibCode to changelog
HugoGranstrom Nov 13, 2022
ce74194
upload thumbnail nimconf
HugoGranstrom Nov 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ Currently most of the documentation on customization is given by the examples.

* `nbImage`: image command to show images (see `penguins.nim` example linked above)
* `nbFile`: content (string or untyped) is saved to file (see example document [files](https://pietroppeter.github.io/nimib/files.html))
* `nbRawOutput`: called with string content, it will add the raw content to document (html backend)
* `nbRawHtml`: called with string content, it will add the raw content to document (html backend)
* `nbTextWithCode`: a variant of `nbText` that also reads nim source. See example of usage
at the end of the source in `numerical.nim` linked above.
* `nbPython`: can be used after calling `nbInitPython()` and it runs and capture output of python code;
Expand Down
9 changes: 6 additions & 3 deletions docsrc/counters.nim
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ A button which increases a counter each time you click it. We will do this in tw
The first method is to use Nim like you would have used Javascript using `getElementById` and `addEventListener`:
"""
nbCode:
## 0:
nbJsFromCodeGlobal:
import std/dom
## 1:
template counterButton(id: string) =
let labelId = "label-" & id
let buttonId = "button-" & id
## 2:
nbRawOutput: """
nbRawHtml: """
<label id="$1">0</label>
<button id="$2">Click me</button>
""" % [labelId, buttonId]
## 3:
nbJsFromCode(labelId, buttonId):
import std/dom
## 4:
var label = getElementById(labelId.cstring)
var button = getElementById(buttonId.cstring)
Expand All @@ -38,10 +40,11 @@ nbCode:

nbText: hlMd"""
Let's explain each part of the code:
0. We import `std/dom` in a `nbJsFromCodeGlobal` block. `std/dom` is where many dom-manipulation functions are located.
1. We define a template called `counterButton` which will create a new counter button. So if you call it somewhere it will
place the widget there, that's the reusable part done. But it also takes an input `id: string`. This is to solve the problem of each widget needing unique ids. It can also be done with `nb.newId` as will be used in the Karax example.
2. Here we emit the `<label>` and `<button>` tags and insert their ids.
3. `nbJsFromCode` is the template that will turn our Nim code into Javascript and we are capturing `labelId` and `buttonId` (Important that you capture all used variables defined outside the code block). `std/dom` is where many dom-manipulation functions are located.
3. `nbJsFromCode` is the template that will turn our Nim code into Javascript and we are capturing `labelId` and `buttonId` (Important that you capture all used variables defined outside the code block).
4. We fetch the elements we emitted above by their ids. Remember that most javascript functions want `cstring`s!
5. We create a variable `counter` to keep track of the counter and add the eventlistener to the `button` element. There we increase the counter and update the `innerHtml` of the `label`.

Expand Down
2 changes: 1 addition & 1 deletion docsrc/index.nim
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Currently most of the documentation on customization is given by the examples.

* `nbImage`: image command to show images (see `penguins.nim` example linked above)
* `nbFile`: content (string or untyped) is saved to file (see example document [files]({docs}/files.html))
* `nbRawOutput`: called with string content, it will add the raw content to document (html backend)
* `nbRawHtml`: called with string content, it will add the raw content to document (html backend)
* `nbTextWithCode`: a variant of `nbText` that also reads nim source. See example of usage
at the end of the source in `numerical.nim` linked above.
* `nbPython`: can be used after calling `nbInitPython()` and it runs and capture output of python code;
Expand Down
73 changes: 47 additions & 26 deletions docsrc/interactivity.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,56 @@ import nimib
nbInit

nbText: hlMd"""
# Creating interactive components in Nimib
# Creating interactive components in nimib

Nimib can easily be used to create static content with `nbText` and `nbCode`, but did you know that you can create interactive
content as well? And that you can do it all in Nim even! This can be achieved using either the `nbJsFromCode`-API or `nbKaraxCode`.
They work by compiling Nim code into javascript and adding it to the resulting HTML file.
This means that arbitrary Javascript can be written but also that Karax, which compiles to javascript, also can be used.

## nbJsFromCodeInit
This is the fundamental API used for compiling Nim-snippets to javascript. It consists of three templates:
- `nbJsFromCodeInit` - Creates a new code script that further code can be added to later.
- `addCodeToJs` - Adds to an existing code script
- `addToDocAsJs` - Takes the Nim code in a script and compiles it to javascript.
## nbJsFromCode
This is the fundamental API used for compiling Nim-snippets to javascript.
Here is a basic example:
"""

nbCode:
let script = nbJsFromCodeInit:
echo "Hello world!"
let x = 3.14
script.addCodeToJs(x):
nbJsFromCode(x):
echo "Hello world!"
echo "Pi is roughly ", x
## Uncomment this line:
##script.addToDocAsJs()
script.addToDocAsJs()
nbJsShowSource("This is the complete script:")


nbText: hlMd"""
The reason `script.addToDocAsJs()` is commented out is just a limitation of nimib not handling nested blocks well.
If you now go to your browser's javascript console you should see `Hello world` and `Pi is roughly 3.14` printed there.
What is up with `script.addCodeToJs(x)` though? Why is `(x)` needed? It is because we have to capture the value of `x`
What is up with `nbJsFromCode(x)` though? Why is `(x)` needed? It is because we have to capture the value of `x`
to be able to use it in the javascript. The code block will basically be copy-pasted into a separate file and
compiled into javascript. And `x` isn't defined there so we have to capture it. This is true for any variable that
we want to use that is defined outside the script blocks.
we want to use that is defined outside the script blocks.

## nbJsFromCode
This is basically a shorthand for running `nbJsFromCodeInit` and `addToDocAsJs` in a single call:
```nim
let x = 3.14
nbJsCode(x):
echo "Pi is roughly ", x
```
The code that you pass to `nbJsFromCode` will internally be put inside a `block`, so things like `import`s which need to be top-level statements
will need to be done using `nbJsFromCodeGlobal`. Code defined using `nbJsFromCodeGlobal` will be visible to all `nbJsFromCode` blocks. So if you want
to have communication between different code blocks, you have to set it up using a global variable here. An example if how this would work is this:
"""

nbCode:
nbJsFromCodeGlobal:
import std / dom # this will be imported for all your nbJs blocks
var globalVar = 1
nbJsFromCode:
echo "First block: ", globalVar
globalVar += 1
nbJsFromCode:
echo "Second block: ", globalVar

nbText: hlMd"""
If you look in the console you should see that it prints out `1` in the first block and `2` in the second block.

## nbKaraxCode

If you want to write a component using karax this is the template for you!
A normal karax program has the following structure:
```nim
nbJsFromCode(rootId):
import karax / [kbase, karax, karaxdsl, vdom, compact, jstrutils, kdom]
include karax / prelude

karaxCode # some code, set up global variables for example

Expand Down Expand Up @@ -86,8 +86,29 @@ nbCode:
proc onClick() =
message = "Poof! Gone!"

nbText: "This is the output this code produces:"
nbText: "This is the output this code produces when called:"

karaxExample()

nbText: hlMd"""
## Internal workings
### nbJsFromCode
The way this works is that each `nbJsFromCode` is put inside a separate `block` inside a common file. So if we have 10 `nbJsFromCode` blocks in
our code, we will have 10 `block`s in the final code that will be compiled. Any code passed to `nbJsFromCodeGlobal` will be put at the top of the file
without any blocks. Here's a simple schematic:
```nim
global code here (imports and global variables)
block:
first nbJsFromCode
block:
second nbJsFromCode
block:
third nbJsFromCode
```

### nbKaraxCode
`nbKaraxCode` works a bit differently, there each code block will be compiled in its own file so there is no global scope.
So (`nbJsFromCode` + `nbJsFromCodeGlobal`) and `nbKaraxCode` are totally isolated from each other.
"""

nbSave
38 changes: 33 additions & 5 deletions src/nimib.nim
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,12 @@ template nbRawHtml*(content: string) =
template nbJsFromStringInit*(body: string): NbBlock =
var result = NbBlock(command: "nbCodeToJs", code: body, context: newContext(searchDirs = @[], partials = nb.partials), output: "")
result.context["transformedCode"] = body
result.context["isOwnFile"] = true
result

#[
template nbJsFromCodeInit*(args: varargs[untyped]): NbBlock =
let (code, originalCode) = nimToJsString(true, args)
let (code, originalCode) = nimToJsString(false, args)
var result = NbBlock(command: "nbCodeToJs", code: originalCode, context: newContext(searchDirs = @[], partials = nb.partials), output: "")
result.context["transformedCode"] = code
result
Expand All @@ -147,6 +149,7 @@ template addCodeToJs*(script: NbBlock, args: varargs[untyped]) =
let (code, originalCode) = nimToJsString(false, args)
script.code &= "\n" & originalCode
script.context["transformedCode"] = script.context["transformedCode"].vString & "\n" & code
]#

template addStringToJs*(script: NbBlock, body: string) =
script.code &= "\n" & body
Expand All @@ -161,8 +164,19 @@ template nbJsFromString*(body: string) =
script.addToDocAsJs

template nbJsFromCode*(args: varargs[untyped]) =
let script = nbJsFromCodeInit(args)
script.addToDocAsJs
let (code, originalCode) = nimToJsString(compileToOwnFile=false, putCodeInBlock=true, args)
nb.nbJsScript.add "\n" & code

template nbJsFromCodeGlobal*(args: varargs[untyped]) =
let (code, originalCode) = nimToJsString(compileToOwnFile=false, putCodeInBlock=false, args)
nb.nbJsGlobalScript.add "\n" & code

template nbJsFromCodeOwnFile*(args: varargs[untyped]) =
let (code, originalCode) = nimToJsString(compileToOwnFile=true, putCodeInBlock=false, args)
var result = NbBlock(command: "nbCodeToJs", code: originalCode, context: newContext(searchDirs = @[], partials = nb.partials), output: "")
result.context["transformedCode"] = "import std / json\n" & code
result.context["isOwnFile"] = true
result.addToDocAsJs

template nbCodeToJs*(args: varargs[untyped]) {.deprecated: "Use nbJsFromCode or nbJsFromString instead".} =
nbJsFromCode(args)
Expand All @@ -171,7 +185,7 @@ template nbCodeToJs*(args: varargs[untyped]) {.deprecated: "Use nbJsFromCode or
when moduleAvailable(karax/kbase):
template nbKaraxCode*(args: varargs[untyped]) =
let rootId = "karax-" & $nb.newId()
nbRawOutput: "<div id=\"" & rootId & "\"></div>"
nbRawHtml: "<div id=\"" & rootId & "\"></div>"
nbKaraxCodeBackend(rootId, args)

template nbJsShowSource*(message: string = "") =
Expand All @@ -192,7 +206,21 @@ template nbSave* =
# order if searchDirs/searchTable is relevant: directories have higher priority. rationale:
# - in memory partial contains default mustache assets
# - to override/customize (for a bunch of documents) the best way is to modify a version on file
# - in case you need to manage additional exceptions for a specific document add a new set of partials before calling nbSave
# - in case you need to manage additional exceptions for a specific document add a new set of partials before calling
if nb.nbJsGlobalScript.len > 0 or nb.nbJsScript.len > 0:
nb.nbJsGlobalScript = "import std / json\n" & nb.nbJsGlobalScript
let completeJsCode = nb.nbJsGlobalScript & "\n" & nb.nbJsScript
echo "Complete Js Code: \n", completeJsCode
var jsBlock = NbBlock(
command: "nbCodeToJs",
code: completeJsCode,
context: newContext(searchDirs = @[], partials = nb.partials),
output: ""
)
jsBlock.context["transformedCode"] = completeJsCode
jsBlock.context["isOwnFile"] = false
jsBlock.addToDocAsJs

nb.context.searchDirs(nb.templateDirs)
nb.context.searchTable(nb.partials)

Expand Down
Loading