Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating of buttons in callback doesn't work #18

Open
tobyX opened this issue Jun 15, 2020 · 10 comments
Open

Creating of buttons in callback doesn't work #18

tobyX opened this issue Jun 15, 2020 · 10 comments

Comments

@tobyX
Copy link

tobyX commented Jun 15, 2020

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
Copy link
Author

tobyX commented Jun 15, 2020

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
Copy link
Author

tobyX commented Jun 15, 2020

I tracked it down to this block:


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
Copy link
Member

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
Copy link
Author

tobyX commented Jun 18, 2020

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
Copy link
Author

tobyX commented Jun 18, 2020

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
Copy link
Member

Which voila version do you have?

@tobyX
Copy link
Author

tobyX commented Jun 18, 2020

We are using 0.1.20

@martinRenou
Copy link
Member

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

@tobyX
Copy link
Author

tobyX commented Jul 16, 2020

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
Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants