To be able to edit code and run cells, you need to run the notebook yourself. Where would you like to run the notebook?

This notebook takes about 20 seconds to run.

In the cloud (experimental)

Binder is a free, open source service that runs scientific notebooks in the cloud! It will take a while, usually 2-7 minutes to get a session.

On your computer

(Recommended if you want to store your changes.)

  1. Copy the notebook URL:
  2. Run Pluto

    (Also see: How to install Julia and Pluto)

  3. Paste URL in the Open box

Frontmatter

If you are publishing this notebook on the web, you can set the parameters below to provide HTML metadata. This is useful for search engines and social media.

Author 1

Creating custom bond objects

This guide covers how to create custom objects to be used with the @bind macro in Pluto.

While you can use @bind without understanding JavaScript (with the PlutoUI package, for example), creating custom object requires familiarity with JavaScript. If you want to learn JS - have a look at the tutorials by Mozilla.

👀 Reading hidden code
530 μs

HTML output

Before diving into interactivity, let's have a look at how Pluto.jl turns Julia objects into pretty pixels on your screen.

Every Julia object can be used as argument for the repr function:

👀 Reading hidden code
320 μs
"123"
👀 Reading hidden code
repr(123)
10.7 ms
"[3.0, 1.5, 1.0]"
👀 Reading hidden code
repr(3 ./ (1:3))
164 ms
"Hi **Nick!**\n"
👀 Reading hidden code
repr(md"Hi **Nick!**")
88.5 ms

The repr function turns anything into a human-readable String. It is used in the Julia REPL to show the result of a command. This basic representation format is called MIME("text/plain") - and every object can be shown in this format.

Some objects can also be displayed in the MIME("text/html") format, which is prettier ✨. You can tell repr which MIME type you want:

👀 Reading hidden code
453 μs
"<div class=\"markdown\"><p>Hi <strong>Nick&#33;</strong></p>\n</div>"
repr(MIME("text/html"), md"Hi **Nick!**")
👀 Reading hidden code
91.1 ms

🙋 But that's not pretty!

What you see is the HTML representation of the Markdown object, in its raw form. When Pluto.jl calls repr internally, it uses this HTML code as the cell's output in this page, and your browser turns it into nice-looking text.

Right-click on the words "Hi Nick!" below, and click on Inspect. You'll see that the <celloutput> element contains the HTML code given by repr.

👀 Reading hidden code
415 μs

Hi Nick!

md"Hi **Nick!**"
👀 Reading hidden code
225 μs

HTML objects

One object that can be represented in the MIME("text/html") format is the HTML object:

👀 Reading hidden code
247 μs
Wow cool
message = HTML("Wow <i>cool</i>")
👀 Reading hidden code
13.8 μs
"Wow <i>cool</i>"
repr(MIME("text/html"), message)
👀 Reading hidden code
9.2 ms

Hannes & Floep

dogs = HTML("""
<img src='https://fonsp.com/img/doggoSmall.jpg?raw=true' width='150'></img>
<p><i>Hannes & Floep</i></p>
""")
👀 Reading hidden code
13.7 μs
first_slider = HTML("""
<input type='range'>
""")
👀 Reading hidden code
13.0 μs

The @bind macro

You can use @bind on anything that can be represented as MIME("text/html"):

👀 Reading hidden code
263 μs

Hannes & Floep

@bind uhm dogs
👀 Reading hidden code
213 ms
@bind first_val first_slider
👀 Reading hidden code
596 μs
missing
uhm
👀 Reading hidden code
8.5 μs
missing
first_val
👀 Reading hidden code
11.3 μs

This will tell the JS front-end to attach an event listener to the given element, which will listen for the input event. Objects that don't emit this event (like the <img>) simply won't set a value to the bound variable.

On the Julia side, the @bind macro sets the variable to the initial value missing. HTML objects with a value property will use this value as initializer.

👀 Reading hidden code
316 μs

The @bind macro creates a Bond object:

👀 Reading hidden code
235 μs
name_bond = @bind name html"<input>"
👀 Reading hidden code
2.6 ms
typeof(name_bond), typeof(name)
👀 Reading hidden code
13.6 μs

The MIME("text/html") representation of the above Bond is:

👀 Reading hidden code
202 μs
"<bond def=\"name\" unique_id=\"EaAnlAG5XeAK\"><input></bond>"
👀 Reading hidden code
29.4 ms

This <bond> tag (which is normally ignored by browsers) is used to tell the JS front-end that its inner HTML should be watched for input events, and the def attribute tracks which Bond it is.

👀 Reading hidden code
227 μs

Input events

md"""## Input events

"""
👀 Reading hidden code
181 μs

👀 Reading hidden code
66.1 μs

This syntax displays the HTML object as the cell's output, and uses its latest value as the definition of x. Of course, the variable x is reactive, and all references to x come to life ✨

Try it out! 👆

👀 Reading hidden code
337 μs

👀 Reading hidden code
67.0 μs


👀 Reading hidden code
608 μs
missing
fo
👀 Reading hidden code
8.6 μs
👀 Reading hidden code
139 μs

Combining bonds

The @bind macro returns a Bond object, which can be used inside Markdown and HTML literals:

👀 Reading hidden code
267 μs

How many pets do you have?

Dogs:

Cats:

begin
🐶slider = @bind 🐶 html"<input type='range'>"
🐱slider = @bind 🐱 html"<input type='range'>"
md"""**How many pets do you have?**
Dogs: $(🐶slider)

Cats: $(🐱slider)"""
end
👀 Reading hidden code
13.7 ms

You have missing dogs and missing cats!

md"You have $(🐶) dogs and $(🐱) cats!"
👀 Reading hidden code
304 μs
👀 Reading hidden code
137 μs

Input types

You can use any DOM element that fires an input event. For example:

👀 Reading hidden code
276 μs

a =

b =

c =

d =

e =

f =

👀 Reading hidden code
4.7 ms
(a,b,c,d,e,f)
👀 Reading hidden code
13.8 μs

You can also use JavaScript to write more complicated input objects. The input event can be triggered on any object using

obj.dispatchEvent(new CustomEvent("input"))

Try drawing a rectangle in the canvas below 👇 and notice that the area variable updates.

👀 Reading hidden code
318 μs

👀 Reading hidden code
68.5 μs
@bind t html"""<script>

const span = observablehq.DOM.element("span")
span.innerText = "😀"
return span

</script>"""
👀 Reading hidden code
10.0 ms
missing
t
👀 Reading hidden code
8.3 μs
👀 Reading hidden code
139 μs

Can I use it?

The @bind macro is built into Pluto.jl — it works without having to install a package.

You can use the (tiny) package PlutoUI for some predefined <input> HTML codes. For example, you use PlutoUI to write

@bind x Slider(5:15)

instead of

@bind x html"<input type='range' min='5' max'15'>"

The @bind syntax in not limited to html"..." objects, but can be used for any HTML-showable object!

👀 Reading hidden code
549 μs

More packages

In fact, any package can add bindable values to their objects. For example, a geoplotting package could add a JS input event to their plot that contains the cursor coordinates when it is clicked. You can then use those coordinates inside Julia.

A package does not need to add Pluto.jl as a dependency to so: only the Base.show(io, MIME("text/html"), obj) function needs to be extended to contain a <script> that triggers the input event with a value. (It's up to the package creator when and what.) This does not affect how the object is displayed outside of Pluto.jl: uncaught events are ignored by your browser.

👀 Reading hidden code
554 μs

Tips

Wrap large code blocks

If you have a large block of code that depends on a bound variable t, it will be faster to wrap that code inside a function f(my_t) (which depends on my_t instead of t), and then call that function from another cell, with t as parameter.

This way, only the Julia code "f(t)" needs to be lowered and re-evaluated, instead of the entire code block.

👀 Reading hidden code
399 μs

Separate definition and reference

If you put a bond and a reference to the same variable together, it will keep evaluating in a loop.

So do not write

md"""$(@bind r html"<input type='range'>")  $(r^2)"""

Instead, create two cells:

md"""$(@bind r html"<input type='range'>")"""
r^2
👀 Reading hidden code
357 μs
👀 Reading hidden code
138 μs

Behind the scenes

What is x?

It's an Int64! Not an Observable, not a callback function, but simply the latest value of the input element.

The update mechanism is lossy and lazy, which means that it will skip values if your code is still running - and only send the latest value when your code is ready again. This is important when changing a slider from 0 to 100, for example. If it would send all intermediate values, it might take a while for your code to process everything, causing a noticable lag.

👀 Reading hidden code
547 μs

What does the macro do?

The @bind macro turns an expression like

@bind x Slider(5:15)

into

begin
	local el = Slider(5:15)
	global x = if applicable(Base.peek, el)
		Base.peek(el)
	else
		missing
	end
	PlutoRunner.Bond(el, :x)
end

The if block in the middle assigns an initial value to x, which will be missing, unless an extension of Base.peek has been declared for the element.

Declaring a default value using Base.peek is not necessary, as shown by the examples above, but the default value will be used for x if the notebook.jl file is run as a plain julia file, without Pluto's interactivity. The package PlutoUI defines default values.

👀 Reading hidden code
463 μs

JavaScript?

Yes! We are using Generator.input from observablehq/stdlib to create a JS Generator (kind of like an Observable) that listens to onchange, onclick or oninput events, depending on the element type.

This makes it super easy to create nice HTML/JS-based interaction elements - a package creator simply has to write a show method for MIME type text/html that creates a DOM object that triggers the input event. In other words, Pluto's @bind will behave exactly like viewof in observablehq.

If you want to make a cool new UI, go to observablehq.com/@observablehq/introduction-to-views to learn how.

👀 Reading hidden code
627 μs
👀 Reading hidden code
137 μs
👀 Reading hidden code
138 μs

That's it for now! Let us know what you think using the feedback button below! 👇

👀 Reading hidden code
193 μs