Embed widgets in markdown

I am currently trying to embed widgets in a markdown context in a jupyter notebook. My idea is to create a template with markdown and some placeholders for the widgets. This template is converted to HTML with the help of pandoc and the placeholders are replaced by HTML representations of the widgets so that I am able to display this with the HTML cell magic from IPython.

Is there a way to get these HTML representations and display them with cell magics? And if so, are the widgets still able to communicate with the kernel to execute callbacks etc.?
(I do not want to use this markdown template in standalone web page, just in a notebook.)

It is a natural extension to the idea of widgets… but it’s actually a relatively tricky problem.

While the comm specification is theoretically kernel-agnostic (e.g one JS widget package can theoretically be serviced by multiple backends), the naming of things isn’t, and there is no canonical way to identify widgets in a way that a client can predict, much less the user… they just get generated uuids. So there’s no way today one could write

And unless you really want pandoc, you’d likely be able to get away with something more portable e.g. nbconvert.filters.markdown_mistune.markdown2html_mistune, which is guaranteed to be around (vs pandoc which is still… non-trivial on windows).

At any rate, you can kind of fake it with an HBox, and maybe some custom CSS, that would print out, roughly:

With the right css, the line breaks could be improved.

For a solution that worked with markdown cells as well, it’s possible one could extend jupyterlab-markup, which replaces marked with the much-more-extensible markdown-it, and could be extended to know about widgets:

  • for example, an extension could be authored that abused the link syntax, allowing for something like
    • ![a title](jupyter-widget://@jupyter-widgets/controls/FloatSliderView/?description=x "some text to show if it doesn't work")
2 Likes

Thank you for this extensive reply!

My hope was to use markdown tables and lists with widgets. But I think the easiest way is to fake it with your HBox approach. Unfortunately I cannot really use markdown tables and lists with this approach.

Your hint on using that function from nbconvert for converting from markdown to HTML is also very helpful. Thank you!

Yeah, the markdown-it/jupyterlab-markup route is probably the most promising long-term solution.

Perhaps the most closed-form one would be a MarkdownBox(source="x is ![](#w1)", children={w1: FloatSlider()}) where the naming-things problem was kept entirely within a specific instance rather than being global- or document-scoped. As long as they weren’t full Tangle documents, this would work pretty nicely… and even in that case, many of those could be faked… just not with markdown-level primitives, probably.

Nearer term: markdown tables are nothing to write home about, syntactically, but one could probably write another Box subclass that was rendered like a table (actually used display: table-*). Seems like it would actually be quite useful, if rather verbose:

Table([Row(), RowGroup()], header=[Row()], footer=[Row()])

This fine-grained control would actually be well beyond what markdown tables support, so at least you’d get something for it.

Lists on the other hand, are lovely syntax: I could even see them working with - [ ] checklists.

1 Like

For those who are facing the same problem. I found a little workaround:

  1. Convert your markdown template to html.
  2. Insert your widgets with HTML syntax.
  3. Make use of Jupyter.notebook.kernel.execute in a JS script to make the values of your HTML widgets known to the kernel.

Example:

from IPython.display import HTML, Javascript, display
from nbconvert.filters.pandoc import convert_pandoc
from ipywidgets import Button

msg = None  # the value of input_html is stored in this variable later on
input_html = '<input type="text" id="md_input">'
md_template = """
Column | Column
:----: | :----:
$x^2$  | {input_html}
"""
html = convert_pandoc(md_template, 'markdown', 'html', extra_args=['--mathjax'])
display(HTML(html.format(input_html=input_html)))

def input_to_msg(btn):
    display(Javascript("""
    var value = document.getElementById("md_input").value;
    var code = "msg = '" + value + "'";
    Jupyter.notebook.kernel.execute(code); 
    """))
b = Button(description='input value to msg')
b.on_click(input_to_msg)
display(b)

Unbenannt

This solution works but you have to implement all your functionality with js.