How do I create a layout (Accordion) from ReactiveHTML

I would like to try out and test if I can create custom layouts for the Fast Templates of Panel. The Fast Templates already includes the js and css dependencies required for using the Fast Components. This includes the Fast Accordion.

I’m able to create the below but have several problems

  • How do I use the name of the obj in the loop as the heading of each accordion item?
  • How to I get the component1 and vgl_pane to show it self correctly? I.e. show the background colors and anything at all for the vgl_pane.
  • What would a reference implementation look like? Probably the FastAccordion should also inherit from pn.pane.ListLike similarly to pn.Column.

import panel as pn
import param

pn.extension("vega", sizing_mode="stretch_width")

SVG_OPEN = """<svg style="stroke: #e62f63;" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" slot="collapsed-icon">
<path d="M15.2222 1H2.77778C1.79594 1 1 1.79594 1 2.77778V15.2222C1 16.2041 1.79594 17 2.77778 17H15.2222C16.2041 17 17 16.2041 17 15.2222V2.77778C17 1.79594 16.2041 1 15.2222 1Z" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M9 5.44446V12.5556" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M5.44446 9H12.5556" stroke-linecap="round" stroke-linejoin="round"></path></svg>"""

SVG_CLOSED = """<svg style="stroke: #e62f63;" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" slot="expanded-icon">
<path d="M15.2222 1H2.77778C1.79594 1 1 1.79594 1 2.77778V15.2222C1 16.2041 1.79594 17 2.77778 17H15.2222C16.2041 17 17 16.2041 17 15.2222V2.77778C17 1.79594 16.2041 1 15.2222 1Z" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M5.44446 9H12.5556" stroke-linecap="round" stroke-linejoin="round"></path></svg>"""

# Should probably inherit from pn.pane.ListLike also similarly to pn.Column
class FastAccordion(pn.reactive.ReactiveHTML):
    icon_open = param.String(SVG_OPEN)
    icon_closed = param.String(SVG_CLOSED)
    objects = param.List()


    _template = html ="""
<fast-accordion>
{% for obj in objects %}
<fast-accordion-item slot="item">
<option id="option">${obj}</option>
<div slot="heading">obj.name</div>
{{icon_open}}{{icon_closed}}</fast-accordion-item>
{% endfor %}
</fast-accordion>
"""

    def __init__(self, *objects, **params):
        params["objects"]=[pn.panel(obj) for obj in objects]
        super().__init__(**params)

component1 = pn.Column("a", pn.Spacer(background="blue"),
    background="yellow", name="Component 1")
vegalite = {
  "$schema": "https://vega.github.io/schema/vega-lite/v4.json",
  "data": {"url": "https://raw.githubusercontent.com/vega/vega/master/docs/data/barley.json"},
  "mark": "bar",
  "encoding": {
    "x": {"aggregate": "sum", "field": "yield", "type": "quantitative"},
    "y": {"field": "variety", "type": "nominal"},
    "color": {"field": "site", "type": "nominal"},
  },
  "width": 'container',
}
vgl_pane = pn.panel(vegalite, height=240)

accordion = FastAccordion(component1, vgl_pane, "d")

pn.template.FastListTemplate(
    title="Awesome Panel",
    main = [accordion, vgl_pane],
    main_layout="",
).servable()

Wrapping the opts in a div element improves things significantly.

I discovered that if I put the opts in a <div> things start to display nicely.

Now only getting the vgl_pane to display is needed.

_template = """
<fast-accordion>
{% for obj in objects %}
<fast-accordion-item slot="item"><div id="option" style="background:lightgray">${obj}</div>
<div slot="heading">obj.name</div>
{{icon_open}}{{icon_closed}}</fast-accordion-item>
{% endfor %}
</fast-accordion>
"""

Resizing Makes the Vega Pane Appear

Resizing is now working

I added the script

_scripts = {
      '_resize': """setTimeout(function(){window.dispatchEvent(new Event('resize'))}, 25);"""
    }

fast-accordion2

import panel as pn
import param

pn.extension("vega", sizing_mode="stretch_width")

ACCENT_BASE_COLOR = "#DAA520"

LOGO = "https://panel.holoviz.org/_static/logo_stacked.png"

SVG_OPEN = f"""<svg style="stroke: {ACCENT_BASE_COLOR};" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" slot="collapsed-icon">
<path d="M15.2222 1H2.77778C1.79594 1 1 1.79594 1 2.77778V15.2222C1 16.2041 1.79594 17 2.77778 17H15.2222C16.2041 17 17 16.2041 17 15.2222V2.77778C17 1.79594 16.2041 1 15.2222 1Z" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M9 5.44446V12.5556" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M5.44446 9H12.5556" stroke-linecap="round" stroke-linejoin="round"></path></svg>"""

SVG_CLOSED = f"""<svg style="stroke: {ACCENT_BASE_COLOR};" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" slot="expanded-icon">
<path d="M15.2222 1H2.77778C1.79594 1 1 1.79594 1 2.77778V15.2222C1 16.2041 1.79594 17 2.77778 17H15.2222C16.2041 17 17 16.2041 17 15.2222V2.77778C17 1.79594 16.2041 1 15.2222 1Z" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M5.44446 9H12.5556" stroke-linecap="round" stroke-linejoin="round"></path></svg>"""
# Should probably inherit from pn.pane.ListLike also similarly to pn.Column
class FastAccordion(pn.reactive.ReactiveHTML):
    icon_open = param.String(SVG_OPEN)
    icon_closed = param.String(SVG_CLOSED)
    expand_mode = param.ObjectSelector("multi", objects=["multi", "single"])
    objects = param.List()

    def _input_change(self, *events):
        print(*events)

    _template = """
<fast-accordion id="accordion" expand-mode="${expand_mode}" onchange="${script('_resize')}">
{% for obj in objects %}
<fast-accordion-item slot="item"><div id="option">${obj}</div>
<div slot="heading"><h3>obj.name</h1></div>
{{icon_open}}{{icon_closed}}</fast-accordion-item>
{% endfor %}
</fast-accordion>
"""

    def _input_change(self, event):
        print(event)

    def __init__(self, *objects, **params):
        params["objects"]=[pn.panel(obj) for obj in objects]
        super().__init__(**params)

    _scripts = {
      '_resize': """setTimeout(function(){window.dispatchEvent(new Event('resize'))}, 25);"""
    }
description = pn.Column(
    pn.pane.Markdown("""
        The Microsoft Fast Design framework provides components that are easy to use with the
        Fast Templates of Panel.

        Here we showcase a new `FastAccordian` Layout.

        You can select the `expand_mode`. Either `multi` or `single`.
    """),
    pn.Row(
        pn.pane.SVG("https://explore.fast.design/e1e15bd85334e4346744078af2f52308.svg", embed=False, width=400, sizing_mode="fixed"),
        pn.pane.PNG(LOGO, embed=False, height=100), sizing_mode="fixed", width=400,
    )
)
vegalite = {
  "$schema": "https://vega.github.io/schema/vega-lite/v4.json",
  "data": {"url": "https://raw.githubusercontent.com/vega/vega/master/docs/data/barley.json"},
  "mark": "bar",
  "encoding": {
    "x": {"aggregate": "sum", "field": "yield", "type": "quantitative"},
    "y": {"field": "variety", "type": "nominal"},
    "color": {"field": "site", "type": "nominal"},
  },
  "width": 'container',
}
vgl_pane = pn.pane.Vega(vegalite, height=240)

accordion = FastAccordion("# A First Panel", vgl_pane, "# A Third Panel")
pn.template.FastListTemplate(
    site="Awesome Panel",
    title="Custom Fast Accordion using ReactiveHTML",
    logo=LOGO,
    main = [
        description,
        pn.Pane(accordion.param.expand_mode, widgets={"expand_mode": {"widget_type": pn.widgets.RadioButtonGroup, "button_type": "success"}}),
        accordion,
    ],
    main_layout="",
    accent_base_color=ACCENT_BASE_COLOR,
    header_background=ACCENT_BASE_COLOR,
).servable()

The outstanding issues are

  • How can I replace the string “obj.name” with the obj.name of the component?
  • How can I get responsiveness to work for the vega pane? It seems to expand fine. But it does not want to do the opposite to a width less than the original width.
  • How could I add functionality to the FastAccordion to allow the user to programmatically determine which accordion items are open and which are not?
  • What would a reference layout implementation look like. Should the FastAccordion inherit from ListLike just like Column does?

Need to put these somewhere but I have implemented a bunch of Fast components already, here’s my version of the FastAccordion:

import param

from panel.io.server import init_doc, state
from panel.layout.base import ListLike, NamedListLike
from panel.reactive import ReactiveHTML


class FastDesignProvider(ListLike, ReactiveHTML):

    _template = '<fast-design-system-provider id="fdsp" use-defaults>${objects}</fast-design-system-provider>'


class FastComponent(ReactiveHTML):

    sizing_mode = param.ObjectSelector(default='stretch_width', objects=[
        'fixed', 'stretch_width', 'stretch_height', 'stretch_both',
        'scale_width', 'scale_height', 'scale_both', None])

    __abstract = True

    def get_root(self, doc=None, comm=None, preprocess=True):
        doc = init_doc(doc)
        root_obj = FastDesignProvider()
        root_obj.append(self)
        root = root_obj.get_root(doc, comm, False)
        if preprocess:
            root_obj._preprocess(root)
        ref = root.ref['id']
        state._views[ref] = (self, root, doc, comm)
        return root



class FastAccordion(FastComponent, NamedListLike):

    active = param.List()

    _scripts = {'on_change': """
      const active = []
      for (let i=0; i < fast_accordion.children.length; i++) {
        if (fast_accordion.children[i].expanded)
          active.push(i)
      }
      data.active = active;
      """
    }

    _template = """
    <fast-accordion id="fast-accordion" onchange="${script('on_change')}">
      {% for object in objects %}
      <fast-accordion-item slot="item" slot="item" {% if loop.index0 in active %} expanded {% endif %}>
          <div slot="heading" slot="heading">{{ objects_names[loop.index0] }}</div>
          <svg style="stroke: #e62f63;" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" slot="collapsed-icon" slot="collapsed-icon">
            <path d="M15.2222 1H2.77778C1.79594 1 1 1.79594 1 2.77778V15.2222C1 16.2041 1.79594 17 2.77778 17H15.2222C16.2041 17 17 16.2041 17 15.2222V2.77778C17 1.79594 16.2041 1 15.2222 1Z" stroke-linecap="round" stroke-linejoin="round"></path>
            <path d="M9 5.44446V12.5556" stroke-linecap="round" stroke-linejoin="round"></path>
            <path d="M5.44446 9H12.5556" stroke-linecap="round" stroke-linejoin="round"></path>
          </svg>
          <svg style="stroke: #e62f63;" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" slot="expanded-icon" slot="expanded-icon">
            <path d="M15.2222 1H2.77778C1.79594 1 1 1.79594 1 2.77778V15.2222C1 16.2041 1.79594 17 2.77778 17H15.2222C16.2041 17 17 16.2041 17 15.2222V2.77778C17 1.79594 16.2041 1 15.2222 1Z" stroke-linecap="round" stroke-linejoin="round"></path>
            <path d="M5.44446 9H12.5556" stroke-linecap="round" stroke-linejoin="round"></path>
          </svg>
          <div id="accordion-content">${object}</div>
      </fast-accordion-item>
      {% endfor %}
    </fast-accordion>
    """ # noqa

    def __init__(self, *objects, **params):
        NamedListLike.__init__(self, *objects, **params)
        FastComponent.__init__(self, objects=self.objects, **params)

    @property
    def _child_names(self):
        return {'objects': self._names}

Let’s start a repo somewhere with these components. As I mentioned I already have quite a few implemented.

1 Like

Just created a panel-components org with a panel-fast repo: GitHub - panel-components/panel-fast: Panel components wrapping the FAST UI library

2 Likes

That is great @philippjfr .

I also have a lot of fast components Awesome Panel Extensions Package — Awesome Panel documentation. But they are Bokeh extensions. There is more functionality and already some reference notebook guides.

Is this worth migrating? Or is ReactiveHTML actually the way to go going forward?

I’d be perfectly happy to see a mix of both in a package like this. That said distributing a package with compiled JS is much more involved.

2 Likes

ReactiveHTML has so many advantages

  • Very fast development. No compilation needed. --autoreload enables hot reloading.
  • Much simpler development means potential contributor base much bigger.
  • No problems with Bokeh Layout engine.

The downsides are 1) Its new and probably needs improvements 2) Performance compared to Bokeh Extensions is unknown.

Do you create panel-bootstrap and panel-material repos too? Then the community can start contributing their work there.

They can just be empty repos for now.

1 Like