voila-material icon indicating copy to clipboard operation
voila-material copied to clipboard

Creating of buttons in callback doesn't work

Open tobyX opened this issue 4 years ago • 10 comments

I want to create buttons after somebody did some input and clicked a button. This is working fine in default voila but not with voila-material.

I installed the latest version via pip: 0.3.0

Example:

import ipywidgets.widgets as widgets
import IPython.display as display

create_button = widgets.Button(description="create button")
box = widgets.Box()
output = widgets.Output()

def print_description(btn):
    with output:
        display.clear_output()
        print(f"Button {btn.description} is clicked")

def callback_on_create(btn):
    try:
        box.children = ()
        for i in range(0, 3):
            nb = widgets.Button(description=f"new button {i}")
            nb.on_click(print_description)
            box.children += (nb,)
    except:
        pass

create_button.on_click(callback_on_create)

display.display(
    widgets.VBox([
        widgets.HBox([create_button]),
        box,
        output
    ])
)

A click on the create button will create following error multiple times:

11:38:10.803 Exception opening new comm default.js:969
    _handleCommOpen default.js:969
11:38:10.805 Error: "Object 'jupyter.widget' not found in registry"
    loadObject default.js:1494
    loadObject default.js:1473
    _handleCommOpen default.js:962
    _handleMessage default.js:1068
    _msgChain default.js:122
default.js:127
    _msgChain default.js:127

The buttons are created and shown but the second callback doesn't work.

tobyX avatar Jun 15 '20 09:06 tobyX

When I create the buttons as a pool and just assign them in "callback_on_create" it works just fine. But I would like to avoid this kind of workaround.

tobyX avatar Jun 15 '20 09:06 tobyX

I tracked it down to this block: https://github.com/voila-dashboards/voila-material/blob/9b6c5b2df09ab5876424f052556d6a41c8ba9f24/share/jupyter/voila/templates/material/nbconvert_templates/voila.tpl#L205 When removed it works as it should. And it looks like as if this part (and the part from the inherited super block) is inserted multiple times? Maybe the problem is in the templating engine? Edit: The multiple insertions of the code is not a problem with the templating engine, just a misplaced endblock. {%- endblock body -%} must be above the footer_js block.

tobyX avatar Jun 15 '20 10:06 tobyX

Thanks for opening an issue and sorry for the late answer.

Its not a problem with the templating engine, just a misplaced endblock. {%- endblock body -%} must be above the footer_js block.

And does it fix your issue to change the place of the endblock? Would you like to open a PR?

martinRenou avatar Jun 18 '20 08:06 martinRenou

No, that just inserts the js code only once, the error is something else (I edited my comment to make it more clear). It looks like as if var kernel = await voila.connectKernel(); opens up a new connection on the same kernel (different client_ids) and this connection is not configured properly (the jupyter wigets are missing). I solved it by copying the code from Voilas main.js (https://github.com/voila-dashboards/voila/blob/0.1.21/share/jupyter/voila/templates/default/static/main.js) into the template, copying the part which is inherited from the footer_js block in base.tpl and removing the super call. But I dont like this solution much. It would be better if there were a hookin in Voila for this or something.

tobyX avatar Jun 18 '20 10:06 tobyX

My solution looks like this:

{%- endblock body -%}

{% block footer_js %}
    <script type="text/javascript">
        requirejs.config({baseUrl: '{{resources.base_url}}voila/', waitSeconds: 30})
        requirejs(
            [
                {% for ext in resources.nbextensions -%}
                    "{{resources.base_url}}voila/nbextensions/{{ ext }}.js",
                {% endfor %}
            ]
        );

        requirejs(['static/voila'], function (voila) {
            (async function () {
                var kernel = await voila.connectKernel();

                const context = {
                    session: {
                        kernel,
                        kernelChanged: {
                            connect: () => {
                            }
                        },
                        statusChanged: {
                            connect: () => {
                            }
                        },
                    },
                    saveState: {
                        connect: () => {
                        }
                    },
                };

                const settings = {
                    saveState: false
                };

                const rendermime = new voila.RenderMimeRegistry({
                    initialFactories: voila.standardRendererFactories
                });

                var widgetManager = new voila.WidgetManager(context, rendermime, settings);

                kernel.statusChanged.connect(() => {
                    // console.log(kernel.status);
                    var el = document.getElementById("kernel-status-icon");
                    if (kernel.status == 'busy') {
                        el.innerHTML = 'radio_button_checked';
                    } else {
                        el.innerHTML = 'radio_button_unchecked';
                    }
                });

                function init() {
                    widgetManager.build_widgets();
                    // it seems if we attach this to early, it will not be called
                    window.addEventListener('beforeunload', function (e) {
                        kernel.shutdown()
                    });

                    voila.renderMathJax();
                }

                if (document.readyState === 'complete') {
                    init()
                } else {
                    window.addEventListener('load', init);
                }
            })();
        });
    </script>
{% endblock footer_js %}

tobyX avatar Jun 18 '20 10:06 tobyX

Which voila version do you have?

martinRenou avatar Jun 18 '20 10:06 martinRenou

We are using 0.1.20

tobyX avatar Jun 18 '20 10:06 tobyX

Yeah I can reproduce. Would you like to open a PR with the fix you found?

martinRenou avatar Jun 18 '20 11:06 martinRenou

Sorry for the late reply. I don't think my workaround is the best solution. It would be better to enter some hooks into Voila to allow for plugins to add functionality into the JS code. Also Voila and this template have changed since the version I'm using and so my solution wont work there. So no, I wont open a PR at the moment.

tobyX avatar Jul 16 '20 12:07 tobyX

Sorry I did not look much at your solution. I might spend more time on this later.

martinRenou avatar Jul 16 '20 12:07 martinRenou