Skip to content

Instantly share code, notes, and snippets.

@angerpointnerd
Last active April 23, 2024 09:00
Show Gist options
  • Save angerpointnerd/03955274a7855ba69fbf4f1c251de4d3 to your computer and use it in GitHub Desktop.
Save angerpointnerd/03955274a7855ba69fbf4f1c251de4d3 to your computer and use it in GitHub Desktop.
Example implementation of VectorWidget for PlutoUI
### A Pluto.jl notebook ###
# v0.19.40
using Markdown
using InteractiveUtils
# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
quote
local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
local el = $(esc(element))
global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
el
end
end
# ╔═╡ f825b5c0-0ca9-451e-8698-52e47b3647ba
using PlutoUI
# ╔═╡ 59fdcc8b-218b-4c18-b05f-b30e14fe1012
using HypertextLiteral
# ╔═╡ 6f644c76-c541-4ac3-85a4-9b1ebba4d138
export VectorWidget
# ╔═╡ a9196427-0e72-47dc-a871-fabcc4351559
md"""
# Examples
"""
# ╔═╡ 3db2fe31-2ce0-4be5-84f0-a6e123927f1d
md"""
## Simple Text Field
"""
# ╔═╡ daa0793c-fe59-45b9-90d0-1258103b9cfe
md"""
## Multi Check Box
"""
# ╔═╡ 2c24d38d-6d12-4a73-9499-9cdad4b60738
md"""
## Combined Widgets
"""
# ╔═╡ 41586971-3036-4823-8879-6829a01b07b7
md"""
## Range Sliders
"""
# ╔═╡ 6ba00e5e-7178-4e40-814b-a8a80c4430b0
md"""
## Clocks
"""
# ╔═╡ fc199372-671e-4631-9110-ebab9b5bb1c4
md"""
## Nested widget containing `VectorWidget`
Still working on this one.
The nested vector widget does not get displayed correctly (all cells are visible) and there is some bond transformation error.
The weird thing is that the cell with the bound value shows the correct output for a split second, but then errors...
"""
# ╔═╡ e60d72d3-0d6a-4739-bee9-ea12108866e2
md"""
---
"""
# ╔═╡ 6d86c1e7-ad95-48ac-a950-1b4b4f20cc1a
import AbstractPlutoDingetjes.Bonds
# ╔═╡ 9c5b35a3-73bc-4a2a-b499-a9ab770e8f07
begin
local docs = begin
"""
```julia
VectorControllerWidget()
```
A "helper" widget for dynamically adjusting vector-valued inputs.
Used in `vector_widget`.
"""
struct VectorControllerWidget
maxElements::Int
initiallyVisibleElements::Int
function VectorControllerWidget(m, i)
@assert m >= 0 "Maximum number of entries has to be non-negative"
@assert i >= 0 "Cannot show less list entries than zero"
@assert m >= i "Cannot show more list entries than the maximum number"
return new(m, i)
end
end
end
function Bonds.initial_value(w::VectorControllerWidget)
return w.initiallyVisibleElements
end
function Base.show(io, m::MIME"text/html", controller::VectorControllerWidget)
content = @htl """
<adjustable-vector-controller>
<button class="button removeElementButton">–</button>
$br
<button class="button addElementButton">+</button>
<span class="warningMessage"></span>
<script>
const controller = currentScript.closest("adjustable-vector-controller")
const widget = currentScript.closest("adjustable-vector")
const ul = widget.querySelector("ul");
const items = Array.from(ul.children);
const removeButton = controller.querySelector(".removeElementButton")
const addButton = controller.querySelector(".addElementButton")
const message = controller.querySelector(".warningMessage");
const minElements = 0
const maxElements = $(controller.maxElements)
let value = $(controller.initiallyVisibleElements)
items.forEach((i) => make_invisible(i));
items.slice(0,value).forEach((i) => make_visible(i));
update_button_visibility();
refresh();
addButton.addEventListener("click", () => {
if (value < maxElements) {
make_visible(items[value]);
value += 1;
update_button_visibility();
} else {
console.log("ERROR: cannot add more elements to the array widget");
}
refresh();
})
removeButton.addEventListener("click", () => {
if (value > minElements) {
value -= 1;
make_invisible(items[value]);
update_button_visibility();
} else {
console.log("ERROR: cannot remove more elements to the array widget");
}
refresh();
})
Object.defineProperty(controller, "value", {
get: () => value,
})
function update_button_visibility() {
if (value <= minElements) {
make_invisible(removeButton);
message.innerHTML = "Vector empty; add more elements by pressing +";
} else {
message.innerHTML = "";
make_visible(removeButton);
}
if (value >= maxElements) {
make_invisible(addButton);
message.innerHTML = "Vector full; remove elements by pressing –";
} else {
make_visible(addButton);
}
}
function make_visible(element) {
element.style.removeProperty("display");
}
function make_invisible(element) {
element.style.display = "none";
}
function refresh() {
controller.dispatchEvent(new CustomEvent("input", {}));
}
</script>
</adjustable-vector-controller>
"""
show(io, m, content)
end
docs
end
# ╔═╡ b83cf74c-00ec-4a71-991b-d3f965ab3cc2
begin
local docs = begin
"""
```julia
vector_widget(widget_maker::Function; maxEntries = 10, initialEntries = 1)
```
A widget that allows to input a vector of identical inputs.
The number of elements can be controlled dynamically with buttons.
The vector elements are widgets returned by the function `widget_maker`.
`maxEntries` controls an upper limit of vector elements.
The resulting vector may be empty (after removing all elements), but note that it will be a `Vector{Any}`.
There is currently no logic determining the element type, so it will be whatever the element widget returns (and `Any` in case of no elements).
# Examples
## Simple vector of numbers
Existing widgets can be used by simply wrapping them in a function that generates a new widget for each array element:
```julia
@bind myNumberVec vector_widget(() -> NumberField(1:10))
myNumberVec == [1]
```
## Vector with combined widget
It can be convenient to use the `do` syntax to define more complicated element widgets:
```julia
@bind myCombinedVec vector_widget() do
PlutoUI.combine() do Child
md"\""
value \$(Child("value", Slider(1:10; show_value=true))) \$br
comment \$(Child("comment", TextField()))
"\""
end
end
myCombinedVec == [(; value = 1, comment = "")]
```
"""
function vector_widget(widget_maker::Function, maxEntries = 10, initialEntries = 1)
bonds = [widget_maker() for _ in 1:maxEntries]
controller = VectorControllerWidget(maxEntries, initialEntries)
combineWidget = PlutoUI.combine() do Child
list_item(bond) = @htl "<li>$(Child(bond))</li>"
@htl """
<adjustable-vector>
<ul>
$(list_item.(bonds))
</ul>
$(Child(controller))
</adjustable-vector>
"""
end
vectorWidget = PlutoUI.Experimental.transformed_value(combineWidget) do from_js
vectorElements..., nVisibleElements = from_js
return collect(vectorElements)[1:nVisibleElements]
end
end
end
docs
end
# ╔═╡ 20edea2b-435a-4ac7-9f31-be3b86c27b3b
@bind testText vector_widget(() -> TextField())
# ╔═╡ 14300f48-f6ec-4041-b1d2-01b39e22533d
testText
# ╔═╡ e77ce0b2-8e97-4244-8589-bce4c23b0d3d
@bind testMultiBox vector_widget(() -> MultiCheckBox(["🥕", "🐟", "🍌"]; select_all=true))
# ╔═╡ b90d4805-4965-4b38-9c71-c8fdfa6732f6
testMultiBox
# ╔═╡ 91e33a47-201e-426f-bc8b-827e11afeac2
@bind testCombine vector_widget() do
PlutoUI.combine() do Child
md"""
value $(Child("value", Slider(1:10; show_value=true))) $br
range $(Child("range", RangeSlider(1:10))) $br
comment $(Child("comment", TextField()))
"""
end
end
# ╔═╡ 15025622-c28b-45be-aaf5-c227c65fa13e
testCombine
# ╔═╡ f959ceda-06eb-4703-b067-52d3aaf516fd
@bind testRange vector_widget(() -> RangeSlider(1:10))
# ╔═╡ d2a6df5a-12f4-4544-b996-857d72e0a4da
testRange
# ╔═╡ c7ebff3c-4c3d-4399-baeb-d14609da22d4
@bind testClock vector_widget(() -> Clock())
# ╔═╡ d797b26e-1bb9-4abf-a1a7-8bc2ff306bbf
testClock
# ╔═╡ 0c8fe79a-e2ce-465f-b9f9-e0a06a65aaca
@bind testNested PlutoUI.combine() do Child
md"""
Title $(Child(TextField())) $br
List of stuff
$(Child(vector_widget(() -> TextField(), 3))) $br
More stuff:
$(Child(NumberField(1:10)))
"""
end
# ╔═╡ 8a3afd28-258c-49a6-9618-7b3d6ca54448
testNested
# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
AbstractPlutoDingetjes = "6e696c72-6542-2067-7265-42206c756150"
HypertextLiteral = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
[compat]
AbstractPlutoDingetjes = "~1.3.1"
HypertextLiteral = "~0.9.5"
PlutoUI = "~0.7.58"
"""
# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised
julia_version = "1.10.2"
manifest_format = "2.0"
project_hash = "88e3ec972704b9568612a10fd51d49f28f47cd6f"
[[deps.AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "297b6b41b66ac7cbbebb4a740844310db9fd7b8c"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.3.1"
[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
version = "1.1.1"
[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "b10d0b65641d57b8b4d5e234446582de5047050d"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.5"
[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
version = "1.1.0+0"
[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
[[deps.Downloads]]
deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
version = "1.6.0"
[[deps.FileWatching]]
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"
[[deps.Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "179267cfa5e712760cd43dcae385d7ea90cc25a4"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.5"
[[deps.HypertextLiteral]]
deps = ["Tricks"]
git-tree-sha1 = "7134810b1afce04bbc1045ca1985fbe81ce17653"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.5"
[[deps.IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "8b72179abc660bfab5e28472e019392b97d0985c"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.4"
[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.4"
[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
version = "0.6.4"
[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
version = "8.4.0+0"
[[deps.LibGit2]]
deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
[[deps.LibGit2_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"]
uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5"
version = "1.6.4+0"
[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
version = "1.11.0+1"
[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
[[deps.LinearAlgebra]]
deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
[[deps.MIMEs]]
git-tree-sha1 = "65f28ad4b594aebe22157d6fac869786a255b7eb"
uuid = "6c6e2e6c-3030-632d-7369-2d6c69616d65"
version = "0.1.4"
[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
version = "2.28.2+1"
[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
version = "2023.1.10"
[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
version = "1.2.0"
[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
version = "0.3.23+4"
[[deps.Parsers]]
deps = ["Dates", "PrecompileTools", "UUIDs"]
git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.8.1"
[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
version = "1.10.0"
[[deps.PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "FixedPointNumbers", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "MIMEs", "Markdown", "Random", "Reexport", "URIs", "UUIDs"]
git-tree-sha1 = "71a22244e352aa8c5f0f2adde4150f62368a3f2e"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.58"
[[deps.PrecompileTools]]
deps = ["Preferences"]
git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f"
uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
version = "1.2.1"
[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.4.3"
[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
[[deps.Random]]
deps = ["SHA"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"
[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "0.7.0"
[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
[[deps.SparseArrays]]
deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
version = "1.10.0"
[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
version = "1.10.0"
[[deps.SuiteSparse_jll]]
deps = ["Artifacts", "Libdl", "libblastrampoline_jll"]
uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c"
version = "7.2.1+1"
[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
version = "1.0.3"
[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
version = "1.10.0"
[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[[deps.Tricks]]
git-tree-sha1 = "eae1bb484cd63b36999ee58be2de6c178105112f"
uuid = "410a4b4d-49e4-4fbc-ab6d-cb71b17b3775"
version = "0.1.8"
[[deps.URIs]]
git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.5.1"
[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
version = "1.2.13+1"
[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
version = "5.8.0+1"
[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
version = "1.52.0+1"
[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
version = "17.4.0+2"
"""
# ╔═╡ Cell order:
# ╠═6f644c76-c541-4ac3-85a4-9b1ebba4d138
# ╠═9c5b35a3-73bc-4a2a-b499-a9ab770e8f07
# ╠═b83cf74c-00ec-4a71-991b-d3f965ab3cc2
# ╟─a9196427-0e72-47dc-a871-fabcc4351559
# ╟─3db2fe31-2ce0-4be5-84f0-a6e123927f1d
# ╟─20edea2b-435a-4ac7-9f31-be3b86c27b3b
# ╠═14300f48-f6ec-4041-b1d2-01b39e22533d
# ╟─daa0793c-fe59-45b9-90d0-1258103b9cfe
# ╟─e77ce0b2-8e97-4244-8589-bce4c23b0d3d
# ╠═b90d4805-4965-4b38-9c71-c8fdfa6732f6
# ╟─2c24d38d-6d12-4a73-9499-9cdad4b60738
# ╟─91e33a47-201e-426f-bc8b-827e11afeac2
# ╠═15025622-c28b-45be-aaf5-c227c65fa13e
# ╟─41586971-3036-4823-8879-6829a01b07b7
# ╠═f959ceda-06eb-4703-b067-52d3aaf516fd
# ╠═d2a6df5a-12f4-4544-b996-857d72e0a4da
# ╟─6ba00e5e-7178-4e40-814b-a8a80c4430b0
# ╠═c7ebff3c-4c3d-4399-baeb-d14609da22d4
# ╠═d797b26e-1bb9-4abf-a1a7-8bc2ff306bbf
# ╟─fc199372-671e-4631-9110-ebab9b5bb1c4
# ╠═0c8fe79a-e2ce-465f-b9f9-e0a06a65aaca
# ╠═8a3afd28-258c-49a6-9618-7b3d6ca54448
# ╟─e60d72d3-0d6a-4739-bee9-ea12108866e2
# ╠═f825b5c0-0ca9-451e-8698-52e47b3647ba
# ╠═59fdcc8b-218b-4c18-b05f-b30e14fe1012
# ╠═6d86c1e7-ad95-48ac-a950-1b4b4f20cc1a
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment