AbstractPlutoDingetjes.jl

AbstractPlutoDingetjes.jl

An abstract package to be implemented by packages/people who create widgets (or other dingetjes) for Pluto. If you are just happy using Pluto to make cool stuff, you probably don't want to use this package directly. This package is not necessary to create widgets in Pluto, but it can add more advanced functionality to your widgets. See the Interactivity sample notebook inside Pluto's main menu to learn more!

Bonds

The initial value of a bond. In a notebook containing @bind x my_widget, this will be used in two cases:

  1. The value of x will be set to x = PlutoAbstractDingetjes.initial_value(my_widget) during the @bind call. This initial value will be used in cells that use x, until the widget is rendered in the browser and the first value is received.
  2. When running a notebook file without Pluto, e.g. shell> julia my_notebook.jl, this value will be used for x.

When not overloaded for your widget, it defaults to returning missing.

Example

struct MySlider
    range::AbstractRange{<:Real}
end

Base.show(io::IO, m::MIME"text/html", s::MySlider) = show(io, m, HTML("<input type=range min=$(first(s.values)) step=$(step(s.values)) max=$(last(s.values))>"))

PlutoAbstractDingetjes.Bonds.initial_value(s::MySlider) = first(s.range)

# Add the following for the same functionality on Pluto versions TODO and below. Will be ignored in future Pluto versions. See the compat info below.
Base.get(s::MySlider) = first(s.range)
Note about `transform_value`

If you are also using transform_value for your widget, then the value returned by initial_value should be the value after transformation.

Pluto TODO

This feature only works in Pluto version TODO: NOT RELEASED YET or above.

Older versions of Pluto used a Base.get overload for this (to avoid the need for the AbstractPlutoDingetjes package, but we changed our minds ๐Ÿ’•). To support all versions of Pluto, use both methods of declaring the initial value.

Use AbstractPlutoDingetjes.is_supported_by_display if you want to check support inside your widget.

The possible values of a bond. This is used when generating precomputed PlutoSliderServer states, see https://github.com/JuliaPluto/PlutoSliderServer.jl/pull/29. Not relevant outside of this use (for now...).

The returned value should be an iterable object that you can call length on (like a Vector or a Generator without filter) or return InfinitePossibilities() if this set is inifinite.

Examples

struct MySlider
    range::AbstractRange{<:Real}
end

Base.show(io::IO, m::MIME"text/html", s::MySlider) = show(io, m, HTML("<input type=range min=$(first(s.values)) step=$(step(s.values)) max=$(last(s.values))>"))

PlutoAbstractDingetjes.Bonds.possible_values(s::MySlider) = s.range
struct MyTextBox end

Base.show(io::IO, m::MIME"text/html", s::MyTextBox) = show(io, m, HTML("<input type=text>"))

PlutoAbstractDingetjes.Bonds.possible_values(s::MySlider) = PlutoAbstractDingetjes.Bonds.InfinitePossibilities()
Note about `transform_value`

If you are also using transform_value for your widget, then the values returned by possible_values should be the values before transformation.

Pluto TODO

This feature only works in Pluto version TODO: NOT RELEASED YET or above.

Transform a value received from the browser before assigning it to the bound julia variable. In a notebook containing @bind x my_widget, Pluto will run x = PlutoAbstractDingetjes.Bonds.transform_value(my_widget, $value_from_javascript). Without this hook, widgets in JavaScript can only return simple types (numbers, dictionaries, vectors) into bound variables.

When not overloaded for your widget, it defaults to returning the value unchanged, i.e. x = $value_from_javascript.

Example

struct MyVectorSlider
    values::Vector{<:Any} # note! a vector of arbitrary objects, not just numbers
end

Base.show(io::IO, m::MIME"text/html", s::MyVectorSlider) = show(io, m, HTML("<input type=range min=1 max=$(length(s.values))>"))

PlutoAbstractDingetjes.Bonds.transform_value(s::MySlider, value_from_javascript::Int) = s.values[value_from_javascript]
Pluto TODO

This feature only works in Pluto version TODO: NOT RELEASED YET or above. Values are not transformed in older versions.

Use AbstractPlutoDingetjes.is_supported_by_display if you want to check support inside your widget.

Validate a value received from the browser before "doing the pluto thing". In a notebook containing @bind x my_widget, Pluto will run PlutoAbstractDingetjes.Bonds.validate_value(my_widget, $value_from_javascript). If the result is false, then the value from JavaScript is considered "invalid" or "insecure", and no further code will be executed.

This is a protection measure when using your widget on a public PlutoSliderServer, where people could write fake requests that set bonds to arbitrary values.

The returned value should be a Boolean.

Example

struct MySlider
    range::AbstractRange{<:Real}
end

Base.show(io::IO, m::MIME"text/html", s::MySlider) = show(io, m, HTML("<input type=range min=$(first(s.values)) step=$(step(s.values)) max=$(last(s.values))>"))

PlutoAbstractDingetjes.Bonds.validate_value(s::MySlider, x::Any) = x isa Real && first(s.range) <= x <= last(s.range)
Note about `transform_value`

If you are also using transform_value for your widget, then the value validated by validate_value will be the value before transformation.

Warning

Be sure to add a dispatch for validate_value(s::MyWidget, x::Any), not just for validate_value(s::MyWidget, x::ExpectedType), since this would return the fallback value true when x is not of the expected type.

Pluto TODO

This feature only works in Pluto version TODO: NOT RELEASED YET or above.

Return InfinitePossibilities() from your overload of possible_values to signify that your bond has no finite set of possible values.

NotGiven() is the default return value of possible_values(::Any).

Extras

is_inside_pluto(io::IO)::Bool

Are we rendering inside a Pluto notebook?

This function should be used inside a Base.show method, and the first argument should be the io provided to the Base.show method.

is_inside_pluto()::Bool

Are we running inside a Pluto notebook?

is_supported_by_display(io::IO, f::Union{Function,Module})::Bool

Check whether the current runtime/display (Pluto) supports a given feature from AbstractPlutoDingetjes (i.e. is the Pluto version new enough). This function should be used inside a Base.show method. The first argument should be the io provided to the Base.show method, and the second argument is the feature to check.

You can use this information to:

  • Error the show method of your widget if the runtime/display does not support the required features, or
  • Render a simpler version of your widget that does not depend on the advanced Pluto features.

Example

import AbstractPlutoDingetjes: is_supported_by_display, Bonds

struct MyCoolDingetje
    values::Vector
end

function Base.show(io::IO, m::MIME"text/html", d::MyCoolDingetje)
    if !(is_supported_by_display(io, Bonds.initial_value) && is_supported_by_display(io, Bonds.transform_value))
        error("This widget does not work in the current version of Pluto.")
    end
    
    write(io, html"...")
end

See also: is_inside_pluto.