Skip to content

Commit

Permalink
Merge pull request #168 from NHDaly/documenter
Browse files Browse the repository at this point in the history
Add a docs/ site for Blink using Documenter.jl!
  • Loading branch information
NHDaly authored Oct 25, 2018
2 parents 3c54b47 + c22e998 commit 74205c6
Show file tree
Hide file tree
Showing 6 changed files with 351 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build
21 changes: 21 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Documenter, Blink

makedocs(
modules = [Blink],
format = :html,
sitename = "Blink",
pages = [
"index.md",
"guide.md",
"Communication" => "communication.md",
"api.md",
#"Subsection" => [
# ...
#]
],
)

#deploydocs(
# repo = "github.com/NHDaly/Blink.jl.git",
# julia = "1.0"
#)
30 changes: 30 additions & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# API

## Window

```@docs
Window
```

```@docs
title
progress
flashframe
```

### Misc

```@docs
opentools
closetools
tools
```

## RPC

```@docs
@js
@js_
js
Blink.JSString
```
204 changes: 204 additions & 0 deletions docs/src/communication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# [Communication between Julia and Javascript](@id Communication)

After creating a Window and loading HTML and JS, you may want to interact with
julia code (e.g. by clicking a button in HTML, or displaying a plot from julia).

This section covers this two-way communication.

## Julia to Javascript
```@setup Blink-win
using Blink
win = Window(Dict(:show=>false), async=false)
```

The easiest way to communicate to javascript from julia is with the [`@js`](@ref) and
[`@js_`](@ref) macros. These macros allow you to execute arbitrary javascript code in a
given Window.

```@repl Blink-win
@js win x = 5;
@js win x
```

The `@js_` macro executes its code asynchronously, but doesn't return its
result:
```@repl Blink-win
@time @js win begin # Blocks until finished; `i` is returned
for i in 0:1000000 end # waste time
i # return i
end
@time @js_ win begin # Returns immediately, but `i` is not returned.
for i in 0:1000000 end # waste time
i # This is ignored
end
```

If your javascript expression is complex, or you want to copy-paste existing
javascript, it can be easier to represent it as a pure javascript string.
For that, you can call the [`js`](@ref) function with a [`JSString`](@ref Blink.JSString):
```@repl Blink-win
body!(win, """<div id="box" style="color:red;"></div>""", async=false);
div_id = "box";
js(win, Blink.JSString("""document.getElementById("$div_id").style.color"""))
```

Note that the code passed to these macros runs in its own scope, so any
javascript variables you create with `var` (or the `@var` equivalent for `@js`)
will be inaccessible after returning:
```@repl Blink-win
@js win (@var x_var = 5; x_var) # x_var is only accessible within this scope.
@js win x_var
```

## Javascript to Julia
Communication from javascript to julia currently works via a message passing
interface.

To invoke julia code from javascript, you specify a julia callback via `handle`:
```julia-repl
julia> handle(w, "press") do args
@show args
end
```
This callback can then be triggered from javscript via `Blink.msg()`:
```@setup handler
using Blink
w = Window(Dict(:show=>false), async=false)
handle(w, "press") do args
@show args
end
```
```@repl handler
@js w Blink.msg("press", "Hello from JS");
```
Note that the javascript function `Blink.msg` takes _exactly_ 1 argument. To
pass more or fewer arguments, pass your arguments as an array:
```@repl handler
handle(w, "event") do count, values, message
# ...
end
@js w Blink.msg("event", [1, ['a','b'], "Hi"]);
```

Finally, here is an example that uses a button to call back to julia:
```@setup Blink-w
using Blink
w = Window(Dict(:show=>false), async=false)
```
```@repl Blink-w
handle(w, "press") do arg
println(arg)
end
body!(w, """<button onclick='Blink.msg("press", "HELLO")'>go</button>""", async=false);
```
Now, clicking the button will print `HELLO` to julia's STDOUT.


## Back-and-forth

Note that you cannot make a synchronous call to javascript from _within_ a julia
callback, or you'll cause julia to hang:

**BAD**:
```julia-repl
julia> @js w x = 5
julia> handle(w, "press") do args...
# Increment x and get its new value
x = @js w (x += 1; x) # ERROR: Cannot make synchronous calls within a callback.
println("New value: $x")
end
#9 (generic function with 1 method)
julia> @js w Blink.msg("press", [])
# JULIA HANGS UNTIL CTRL-C, WHICH KILLS YOUR BLINK WINDOW.
```

**GOOD**: Instead, if you need to access the value of `x`, you should simply
provide it when invoking the `press` handler:
```@repl Blink-w
@js w x = 5
handle(w, "press") do args...
x = args[1]
# Increment x
@js_ w (x = $x + 1) # Note the _asynchronous_ call.
println("New value: $x")
end
@js w Blink.msg("press", x)
# JULIA HANGS UNTIL CTRL-C, WHICH KILLS YOUR BLINK WINDOW.
```


## Tasks

The julia webserver is implemented via Julia
[Tasks](https://docs.julialang.org/en/v1/manual/control-flow/#man-tasks-1). This
means that julia code invoked from javascript will run _sort of_ in parallel to
your main julia code.

In particular:
- Tasks are _coroutines, not threads_, so they aren't truly running in parallel.
- Instead, execution can switch between your code and the coroutine's code whenever a piece of computation is _interruptible_.

So, if your Blink callback handler performs uninterruptible work, it will fully
occupy your CPU, preventing any other computation from occuring, and can
potentially hang your computation.

### Examples:

**BAD**: If your callback runs a long loop, it won't be uninterruptible while
it's running:
```julia-repl
julia> handle(w, "press") do args...
println("Start")
while true end # infinite loop
println("End")
end
#40 (generic function with 1 method)
julia> body!(w, """<button onclick='Blink.msg("press", 1)'>go</button>""", async=false);
julia> # CLICK THE go BUTTON, AND YOUR PROCESS WILL FREEZE
Start
```

**BAD**: The same is true if your _main_ julia computation is hogging the CPU, then
your callback can't run:
```julia-repl
julia> handle(w, "press") do args...
println("Start")
sleep(5) # This will happily yield to any other computation.
println("End")
end
#41 (generic function with 1 method)
julia> body!(w, """<button onclick='Blink.msg("press", 1)'>go</button>""", async=false);
julia> while true end # Infinite loop
# NOW, CLICK THE go BUTTON, AND NOTHING HAPPENS, SINCE THE CPU IS BEING HOGGED!
```

**GOOD**: So to allow for happy communication, all your computations should be interruptible, which you can achieve with calls such as `yield`, or `sleep`:
```julia-repl
julia> handle(w, "press") do args...
println("Start")
sleep(5) # This will happily yield to any other computation.
println("End")
end
#39 (generic function with 1 method)
julia> body!(w, """<button onclick='Blink.msg("press", 1)'>go</button>""", async=false);
julia> while true # Still an infinite loop, but a _fair_ one.
yield() # This will yield to any other computation, allowing the callback to run.
end
# NOW, CLICKING THE go BUTTON WILL WORK CORRECTLY ✅
Start
End
```
72 changes: 72 additions & 0 deletions docs/src/guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Usage Guide

Using Blink to build a local web app has two basic steps:
1. Create a window and load all your HTML and JS.
2. Handle interaction between julia and your window.

## 1. Setting up a new Blink Window

Create a new window via [`Window`](@ref), and load some html via [`body!`](@ref).

```julia-repl
julia> using Blink
julia> w = Window(async=false) # Open a new window
Blink.AtomShell.Window(...)
julia> body!(w, "Hello World", async=false) # Set the body content
```

The main functions for setting content on a window are [`content!(w,
querySelector, html)`](@ref) and [`body!(w, html)`](@ref). `body!` is just
shorthand for `content!(w, "body", html)`.

You can also load an external url via `loadurl`, which will replace the current
content of the window:
```julia
loadurl(w, "http://julialang.org") # Load a web page
```

Note the use of `async=false` in the examples above. By default, these functions
return immediately, but setting `async=false` will block until the function has
completed. This is important if you are executing multiple statements in a row
that depend on the previous statement having completed.


### Loading stadalone HTML, CSS & JS files

You can load complete standalone files via the [`load!`](@ref) function. Blink
will handle the file correctly based on its file type suffix:
```julia
load!(w, "ui/app.css")
load!(w, "ui/frameworks/jquery-3.3.1.js")
```

You can also call the corresponding `importhtml!`, `loadcss!`, and `loadjs!` directly.

## 2. Setting up interaction between Julia and JS

```@setup Blink-w
using Blink
w = Window(Dict(:show=>false), async=false)
```

This topic is covered in more detail in the [Communication](@ref) page.

Just as you can directly write to the DOM via `content!`, you can
directly execute javscript via the [`@js`](@ref) macro.

```@repl Blink-w
@js w Math.log(10)
```

To invoke julia code from javascript, you can pass a "message" to julia:

```julia
# Set up julia to handle the "press" message:
handle(w, "press") do args
@show args
end
# Invoke the "press" message from javascript whenever this button is pressed:
body!(w, """<button onclick='Blink.msg("press", "HELLO")'>go</button>""");
```
23 changes: 23 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Blink.jl Documentation

## Overview

Blink.jl is the Julia wrapper around [Electron](https://electronjs.org/). It
can serve HTML content in a local window, and allows for communication between
Julia and the web page. In this way, therefore, Blink can be used as a GUI
toolkit for building HTML-based applications for the desktop.

## Installation
To install Blink, run:

```julia-repl
julia> Pkg.add("Blink")
julia> Blink.AtomShell.install()
```

This will install the package, and its dependencies: namely, `Electron`.

## Documentation Outline

```@contents
```

0 comments on commit 74205c6

Please sign in to comment.