Open angerpointnerd opened 2 months ago
Very cool! Can you write a bit more about the two bullet points at the end of your post, especially the first one? Can you share how you implemented this with combine
?
In general, I think you would want to implement this yourself without combine
. Take a look at how combine
and confirm
were implemented, maybe you can make a prototype? Please let me know if you have questions! The progress you made so far looks very promising!
Thanks for your quick reply!
Can you write a bit more about the two bullet points at the end of your post, especially the first one? Can you share how you implemented this with combine?
Sure. I have uploaded a notebook with the current implementation here, that might be the easiest to see all the details: https://gist.github.com/angerpointnerd/03955274a7855ba69fbf4f1c251de4d3
Here is a summary (disclaimer: I used Julia for a while now, but I'm relatively new to JavaScript and web development in general)
MultiSelect
, RangeSlider
, or Clock
don't work at the moment (either they return nothing
inside the Vector
or they don't update their values properly)combine
, it also works. So I can actually wrap the widgets above that don't work inside a combine and it works e.g. combine(Child -> Child(Clock()))
Regarding the current implementation, originally, I wanted to try this
() -> TextField()
or () -> combine(...)
to create new list elementsSince I couldn't figure out how to run a Julia function from within JavaScript, I went for creating just one widget at the start and then just copying it when pressing "+". This worked well with text fields, but I couldn't figure out how to properly create new combine
widgets (or anything with custom <script>
tags inside show
) after the cell with @bind xyz VectorWidget(...)
has already been run.
As far as I understand, the content of <script>
gets handled in a special way by Pluto and "hidden" in the final DOM.
So my current solution for the whole widget is quite primitive: I supply a maximum number of list elements (I usually can estimate how many I will need at most ). All I have to do then is to hide/show the list elements when pressing the buttons β the extra elements are already there in the DOM, but will just be ignored. This is only a half-baked solution of course, but it would be enough for my current use case I think. The advantage is that I can generate proper widgets on the Julia side without having to worry about doing it later from within JS.
In general, I think you would want to implement this yourself without combine. Take a look at how combine and confirm were implemented, maybe you can make a prototype? Please let me know if you have questions! The progress you made so far looks very promising!
The current prototype is actually heavily inspired by the implementation of combine
, especially the part that creates event handlers to listen for input events in any of the list elements π
I think I get the overall logic of how and why combine
works the way it does, but I'm clearly missing something. My guess is that I'm not handling the interface between JS and Julia correctly.
Questions I have going forward:
Object.defineProperty(div, 'value', ...)
or simply sets div.value
?<script>
section of an element in the cell output after the cell has been run ? Or is there another way to attach a new "child" widget to an existing list ? I didn't dive deep enough into the Pluto internals yet to figure that out.Hey @angerpointnerd, thanks for the detailed answer!
I really like the solution of defining a maximum, and rendering all widgets from the start. Genius! An addition would be to set disabled
on the + button when you reach the limit.
In fact, this means that you can probably use combine
and transformed_value
to create this superwidget! That might be nicer and more future-proof than sharing some internals with combine
.
My idea: use combine
to create one widget with as children: all the precomputed bonds, plus one ControllerWidget
:
before_transform = PlutoUI.combine() do Child
@htl """
<adjustable-vector>
<ul>
$(Child.(bonds))
</ul>
$(Child(ControllerWidget()))
</adjustable-vector>
"""
end
"""
Where ControllerWidget is the + - widget, which returns the number of elements selected. But it's also responsible for hiding/showing the vector bonds! And updating text.
Something like:
```html
@htl """
<adjustable-vector-controller>
<button class="button removeElementButton">β</button>
<button class="button addElementButton">+</button>
<script>
const controller = currentScript.closest("adjustable-vector-controller")
const widget = currentScript.closest("adjustable-vector")
const buttons = ...
let value = 1
buttons[1].addEventListener("click", () => {
value += 1
make_visible(0...value)
})
Object.defineProperty(controller, "value", {
get: () => value,
})
</script>
</adjustable-vector-controller>
"""
Then with PlutoUI.Experimental.transformed_value
you can put it together:
result = PlutoUI.Experimental.transformed_value(before_transform) do from_js
values = from_js[1:end-1]
num_elements = from_js[end]
values[1:num_elements]
end
Hope this helps!
Btw, the advantage of Object.defineProperty
is that this lets you write widgets that can also have their value set by Pluto. This happens when you have the same notebook open in two windows, or when two people connect to the same server. Try it with a Slider
or some combine
to see what I mean! But don't focus on it while you're prototyping.
Alternatively, to continue more in the direction of your existing approach:
We recently released this new feature: https://github.com/fonsp/Pluto.jl/pull/2726. You could use this to get the HTML repr of a newly generated widget.
You can't modify a <script>
, but you can render new HTML if you want:
const div = document.createElement("div")
div.innerHTML = "..."
some_element.append(div)
Unfortunately, this won't run scripts included in the HTML. To get this, you could look into is the internals of Pluto's embed_display
function. There is a <pluto-display>
web component that you can use to create new displays (which would also execute Githubissues.
Hi! π
In a project of mine, I sometimes need to input a list of identically-typed values where the number of values is variable (think of a list of chemical reactions to simulate for example). So far I couldn't find any type of "array widget" or "vector widget" here or in other related packages like PlutoExtras.jl, so I posted this issue.
Questions
I'll start with the main questions, details below:
Proposed Feature
VectorWidget
that takes a given widget/bond and creates a list of independent copies of that widget. It should allow for dynamically adjusting the number of elements and return aVector
containing the outputs of the individual widgets. Adjusting the number of elements should not reset the previous inputs (see below).Currently possible workarounds
So far I used a two-cell approach, where the first would define a
numberOfElements
for the vector input and the second cell contains acombine
widget which createsnumberOfElements
copies of a given widget. My main problems with this approach are that it doesn't compose well (not possible to nest this workaround inside acombine
widget) and that changing thenumberOfElements
will re-run the cell containing the array widget and reset its state.Example implementation
I hacked together a solution that (kind of) works for me and behaves like this:
https://github.com/JuliaPluto/PlutoUI.jl/assets/102872423/763daf4c-ef95-4aaf-bd8d-9f70b4bd00f0
There are still some problems I couldn't figure out so far:
combine
works, which is mostly enough for me)VectorWidget
with other widgets inside acombine
doesn't work yet