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.
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:
"123"
"[3.0, 1.5, 1.0]"
"Hi **Nick!**\n"
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:
"<div class=\"markdown\"><p>Hi <strong>Nick!</strong></p>\n</div>"
🙋 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
.
Hi Nick!
HTML objects
One object that can be represented in the MIME("text/html")
format is the HTML
object:
"Wow <i>cool</i>"

Hannes & Floep
The @bind
macro
You can use @bind
on anything that can be represented as MIME("text/html")
:

Hannes & Floep
missing
missing
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.
The @bind
macro creates a Bond
object:
Bond
Missing
The MIME("text/html")
representation of the above Bond
is:
"<bond def=\"name\" unique_id=\"EaAnlAG5XeAK\"><input></bond>"
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.
Input events
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! 👆
missing
Combining bonds
The @bind
macro returns a Bond
object, which can be used inside Markdown and HTML literals:
How many pets do you have?
Dogs:
Cats:
You have missing dogs and missing cats!
Input types
You can use any DOM element that fires an input
event. For example:
a =
b =
c =
d =
e =
f =
missing
missing
missing
missing
missing
missing
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.
missing
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!
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.
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.
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
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.
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.
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.
That's it for now! Let us know what you think using the feedback button below! 👇