S
Solara•7mo ago
Johnowhitaker

Does anyone know how I might interact

Does anyone know how I might interact with anywidgets widgets in solara? I can make one:
class CounterWidget(anywidget.AnyWidget):
# Widget front-end JavaScript code
_esm = """
function render({ model, el }) {
let button = document.createElement("button");
button.innerHTML = `count is ${model.get("value")}`;
button.addEventListener("click", () => {
model.set("value", model.get("value") + 1);
model.save_changes();
});
model.on("change:value", () => {
button.innerHTML = `count is ${model.get("value")}`;
});
el.appendChild(button);
}
export default { render };
"""
# Stateful property that can be accessed by JavaScript & Python
value = traitlets.Int(0).tag(sync=True)
class CounterWidget(anywidget.AnyWidget):
# Widget front-end JavaScript code
_esm = """
function render({ model, el }) {
let button = document.createElement("button");
button.innerHTML = `count is ${model.get("value")}`;
button.addEventListener("click", () => {
model.set("value", model.get("value") + 1);
model.save_changes();
});
model.on("change:value", () => {
button.innerHTML = `count is ${model.get("value")}`;
});
el.appendChild(button);
}
export default { render };
"""
# Stateful property that can be accessed by JavaScript & Python
value = traitlets.Int(0).tag(sync=True)
Then since it uses traitlets I can set up an observer to do something when the value changes (including for e.g. updating a solara reactive variable to keep them in sync). BUT: to show the widget in a solara app I have to use counter = CounterWidget().element() but this makes it much harder to then figure out how to get the value (I'm not familiar with whatever magic .element does yet). This (traitlets etc) doesn't even feel like quite the right approach - perhaps someone more familiar with anywidgets can give a tip on the quickest way to have some shared state between custom javascript and solara?
5 Replies
Johnowhitaker
JohnowhitakerOP•7mo ago
Ah I see one mistake I was making was using counter = CounterWidget().element() not counter = CounterWidget.element()
MaartenBreddels
MaartenBreddels•7mo ago
CounterWidget.elements(value=r.value, on_value=r.set) where r is a reactive value. This should work. @alonsosilva you just wrote a blog post about this right?
Johnowhitaker
JohnowhitakerOP•7mo ago
text_1 = solara.reactive("This is a global reactive variable")
count = solara.reactive(0)

class CounterWidget(anywidget.AnyWidget):
# Widget front-end JavaScript code
_esm = """
function render({ model, el }) {
let button = document.createElement("button");
button.innerHTML = `count is ${model.get("value")}`;
button.addEventListener("click", () => {
model.set("value", model.get("value") + 1);
model.save_changes();
});
model.on("change:value", () => {
button.innerHTML = `count is ${model.get("value")}`;
});
el.appendChild(button);
}
export default { render };
"""
# Stateful property that can be accessed by JavaScript & Python
value = traitlets.Int(0).tag(sync=True)

def __init__(self):
super().__init__()
self.value = count.value

@traitlets.observe("value")
def _on_value_change(self, change):
count.set(int(change["new"]))

@solara.component
def Page():
counter = CounterWidget.element()
solara.Markdown(f"### Counter Widget count: {count.value}")
solara.Button("Reset counter", on_click=lambda: count.set(0))
text_1 = solara.reactive("This is a global reactive variable")
count = solara.reactive(0)

class CounterWidget(anywidget.AnyWidget):
# Widget front-end JavaScript code
_esm = """
function render({ model, el }) {
let button = document.createElement("button");
button.innerHTML = `count is ${model.get("value")}`;
button.addEventListener("click", () => {
model.set("value", model.get("value") + 1);
model.save_changes();
});
model.on("change:value", () => {
button.innerHTML = `count is ${model.get("value")}`;
});
el.appendChild(button);
}
export default { render };
"""
# Stateful property that can be accessed by JavaScript & Python
value = traitlets.Int(0).tag(sync=True)

def __init__(self):
super().__init__()
self.value = count.value

@traitlets.observe("value")
def _on_value_change(self, change):
count.set(int(change["new"]))

@solara.component
def Page():
counter = CounterWidget.element()
solara.Markdown(f"### Counter Widget count: {count.value}")
solara.Button("Reset counter", on_click=lambda: count.set(0))
This is what I had which sort of worked (ckicking the custom widget updated the markdown, but resetting the reactive variable count didn't reset the custom widget. Just seen your message, will try Yes that works
import solara
import anywidget
import traitlets

class CounterWidget(anywidget.AnyWidget):
# Widget front-end JavaScript code
_esm = """
function render({ model, el }) {
let button = document.createElement("button");
button.innerHTML = `count is ${model.get("value")}`;
button.addEventListener("click", () => {
model.set("value", model.get("value") + 1);
model.save_changes();
});
model.on("change:value", () => {
button.innerHTML = `count is ${model.get("value")}`;
});
el.appendChild(button);
}
export default { render };
"""
# Stateful property that can be accessed by JavaScript & Python
value = traitlets.Int(0).tag(sync=True)

@solara.component
def Page():
count = solara.use_reactive(0)
counter = CounterWidget.element(value=count.value, on_value=count.set)
solara.Markdown(f"### Counter Widget count: {count.value}")
solara.Button("Reset counter", on_click=lambda: count.set(0))
import solara
import anywidget
import traitlets

class CounterWidget(anywidget.AnyWidget):
# Widget front-end JavaScript code
_esm = """
function render({ model, el }) {
let button = document.createElement("button");
button.innerHTML = `count is ${model.get("value")}`;
button.addEventListener("click", () => {
model.set("value", model.get("value") + 1);
model.save_changes();
});
model.on("change:value", () => {
button.innerHTML = `count is ${model.get("value")}`;
});
el.appendChild(button);
}
export default { render };
"""
# Stateful property that can be accessed by JavaScript & Python
value = traitlets.Int(0).tag(sync=True)

@solara.component
def Page():
count = solara.use_reactive(0)
counter = CounterWidget.element(value=count.value, on_value=count.set)
solara.Markdown(f"### Counter Widget count: {count.value}")
solara.Button("Reset counter", on_click=lambda: count.set(0))
^^ If anyone wants the solution Many thanks @MaartenBreddels
Alonso Silva
Alonso Silva•7mo ago
Yes, and I did that mistake as well! After this discussion, I have updated the blog post by adding how to access the anywidget counter https://alonsosilvaallende.github.io/blog/posts/2024-04-23-Build_a_basic_widget_with_AnyWidget.html
Johnowhitaker
JohnowhitakerOP•7mo ago
Fantastic post, came for the content stayed for the satisfying confetti 😄
Want results from more Discord servers?
Add your server