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

Add a docs/ site for Blink! #168

Merged
merged 8 commits into from
Oct 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel a bit weird about having repeated myself here, but i think it's good to have simple examples here and a full discussion of these complicated mechanisms there. What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me.


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
```