How to trigger re-render of _template?

Below, I use {{ index }} and I understand it can automatically update with ${index}, but I was wondering if there’s a way to manually trigger it in _log or _scripts

from panel.reactive import ReactiveHTML
import param

class JSSlideshow(ReactiveHTML):

    index = param.Integer(default=0)

    _template = """<img id="slideshow" src="https://picsum.photos/800/300?image={{ index }}" onclick="${script('click')}"></img>"""

    _scripts = {'click': 'data.index += 1'}

    @param.depends("index", watch=True)
    def _log(self):
        print(self.index)

ss = JSSlideshow(width=800, height=300)
ss

Specifically, this works…

from panel.reactive import ReactiveHTML
import param
import panel as pn
pn.extension()

class Test(ReactiveHTML):

    value = param.List(doc="The selected reactions.")

    options = param.Dict(default={"favorite": "heart"}, doc="""
        A key-value pair of reaction values and their corresponding tabler icon names
        found on https://tabler-icons.io.""")

    _template = """
        <div id="reactions" class="reactions">
            {% for reaction, icon_name in options.items() %}
            <div id="v-{{ loop.index0 }}">${icon_name}</div>
            {% endfor %}
        </div>
    """

test = Test()
test

But not:

from panel.reactive import ReactiveHTML
import param
import panel as pn
pn.extension()

class Test(ReactiveHTML):

    value = param.List(doc="The selected reactions.")

    options = param.Dict(default={"favorite": "heart"}, doc="""
        A key-value pair of reaction values and their corresponding tabler icon names
        found on https://tabler-icons.io.""")

    _template = """
        <div id="reactions" class="reactions">
            {% for reaction, icon_name in options.items() %}
            <img id="reaction-{{ loop.index0 }}" src="https://tabler-icons.io/icons/static/tabler-icons/{{ icon_name }}.svg" alt="${reaction}"></img>
            {% endfor %}
        </div>
    """

test = Test()
test

So I am planning to use {{ }} instead as a work around.

Okay, I think rebuilding the HTML with Python param.depends works:

from panel.reactive import ReactiveHTML
import param
import panel as pn

pn.extension()


class Test(ReactiveHTML):
    value = param.List(doc="The selected reactions.")

    options = param.Dict(
        default={"favorite": "heart"},
        doc="""
        A key-value pair of reaction values and their corresponding tabler icon names
        found on https://tabler-icons.io.""",
    )

    _icons_html = param.String()

    _template = """
        <div id="reactions" class="reactions">
        ${_icons_html}
        </div>
    """

    @param.depends("options", watch=True, on_init=True)
    def _update_icons_html(self):
        self._icons_html = "\n".join(
            f"""
            <img id="reaction-{reaction}"
                src="https://tabler-icons.io/static/tabler-icons/icons/{icon_name}.svg"
                alt="{reaction}">
            </img>
            """
            for reaction, icon_name in self.options.items()
        )

test = Test()
test

I guess it’s not able to attach scripts…

I finally found an elegant solution that is completely reactive after discovering how to loop properly:

class ReactionIcons(ReactiveHTML):
    value = param.List()

    options = param.Dict(default={})

    _reactions = param.List()

    _icons = param.List()

    _base_url = param.String("https://tabler-icons.io/static/tabler-icons/icons/")

    _template = """
    <div id="reaction-icons">
        {% for option in options %}
        <span id="reaction-{{ loop.index0 }}" onclick=${_update_value} style="cursor: pointer;">${_icons[{{ loop.index0 }}]}</div>
        {% endfor %}
    </div>
    """

    def _update_value(self, event):
        reaction_index = int(event.node.split("-")[1])
        reaction = self._reactions[reaction_index]
        if reaction in self.value:
            self.value = [r for r in self.value if r != reaction]
        else:
            self.value = [*self.value, reaction]

    @pn.depends("value", "options", watch=True, on_init=True)
    def _update_icons(self):
        self._reactions = list(self.options.keys())
        icons = []
        for reaction, icon in self.options.items():
            src = f"{self._base_url}{icon}.svg"
            if reaction in self.value:
                src = src.replace(".svg", "-filled.svg")
            icons.append(src)
        self._icons = icons

ri = ReactionIcons(
    options={"like": "thumb-up", "dislike": "thumb-down"}, value=["like"]
)
ri

For a single icon:

import param
import panel as pn
import requests
pn.extension()

class ToggleIcon(pn.reactive.ReactiveHTML):
    icon = param.String(default="thumb-up")

    active = param.Boolean(default=False)

    _svg = param.String()

    _template = """
    <div id="icon" onclick=${_click_icon} style="cursor: pointer;">${_svg}</div>
    """

    def _click_icon(self, event):
        self.active = not self.active

    @pn.cache
    def _fetch_svg(self, icon, active):
        filled = "-filled" if active else ""
        response = requests.get(
            f"https://tabler-icons.io/static/tabler-icons/icons/" f"{icon}{filled}.svg"
        )
        svg = response.text
        return svg

    @pn.depends("icon", "active", watch=True, on_init=True)
    def _update_icon(self):
        svg = self._fetch_svg(self.icon, self.active)
        if self.width:
            svg = svg.replace('width="24"', f'width="{self.width}"')
        if self.height:
            svg = svg.replace('height="24"', f'height="{self.height}"')
        self._svg = svg


ToggleIcon(active=True, width=200, height=200)