voila-dashboards / voila-material

Material design template for Voilà
Other
45 stars 23 forks source link

Creating of buttons in callback doesn't work #18

Open tobyX opened 4 years ago

tobyX commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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.

martinRenou commented 4 years ago

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?

tobyX commented 4 years ago

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 commented 4 years ago

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 %}
martinRenou commented 4 years ago

Which voila version do you have?

tobyX commented 4 years ago

We are using 0.1.20

martinRenou commented 4 years ago

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

tobyX commented 4 years ago

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.

martinRenou commented 4 years ago

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