Re-render entire panel app with button click

Hi,
I am trying to re-render entire panel app with a button click. In the snippet provided below I am trying to clear the ‘template.main’ and put another column but it does not seem to work.

Also any other workaround with which we can re-render the app itself or replace the ‘main’ with another column would be fine.

Thanks

import panel as pn
from datetime import datetime

my_button = pn.widgets.Button(name="CLICK",button_type="primary")

def handle_button_click(event):
    global my_template
    my_button.name = "changed"
    # my_template.main = pn.Column(pn.pane.Alert("NEW"))
    my_template.main.clear()
    my_template.main.append(pn.Column(pn.pane.Alert("NEW")))
    

def get_main_section_1():
    my_button.on_click(handle_button_click)
    return my_button

def get_main_section_2():
    return pn.pane.Alert(f'## time is {datetime.now()}')

my_template = pn.template.FastListTemplate(
        title="DASHBOARD TITLE",
        main=[
            get_main_section_1,
            get_main_section_2,
        ],
    )
def app():
    return my_template

app().servable()

Last year, I was testing ways to make static html files that behaved dynamically. I was able to mimic dynamic panels by controlling the visible property for each element. The technique also used a Tab element to support different layouts on each tab. It worked on FastGridTemplate and FastListTemplate. The built-in full screen at the top right corner of each pane worked nicely most of the time. The management effort grows as the number of elements you want to act dynamically grows. Pre-load the elements and make them (in)visible as appropriate. I recall making it work with custom JS code as was as active panel server code.

Below is the heart of the technique for both python and JS. titles and texts should be able to be any panel elements. In testing titles was holoview charts, tables, and/or echart gauges. texts was markdown text in a side panel that accompanied what is in the title element. The title element is a list where each entry resides on a separate Tab panel. The text element is a list of markdown panels populating a single column.

if RUNPYTHON:
    def match_text(*events):
        for event in events:
            if event.name == 'active':
                for i in range(len(texts)):
                    titles[i].visible = (i == event.new)
                    texts[i].visible = (i == event.new)

    watchtabs = tabs.param.watch(match_text, ['active'], onlychanged=True)
    tabs.param.trigger('active')
else: # Custom JS
    watchtitles = []
    watchtexts = []
    for i in range(len(texts)):
        watchtitles.append(tabs.jslink(titles[i], code={'active': f'target.visible = (source.active == {i})'}))
        watchtexts.append(tabs.jslink(texts[i], code={'active': f'target.visible = (source.active == {i})'}))
        
    tabs.param.trigger('active')

first.js_on_click(args={'target': tabs, 'tab': len(tabs)-1}, code='target.active = 0')
minus.js_on_click(args={'target': tabs, 'tab': len(tabs)-1}, code='target.active = Math.max(target.active - 1, 0)')
plus.js_on_click(args={'target': tabs, 'tab': len(tabs)-1}, code='target.active = Math.min(target.active + 1, tab)')
last.js_on_click(args={'target': tabs, 'tab': len(tabs)-1}, code='target.active = tab')

Hopefully this will set you on a productive path.

1 Like

Hi @rcravotta I am not quite sure this is will be helpful for our use-case.
What we are trying to achieve is have a complete re-render of the the app (all widgets) with a button click.

What do you mean exactly by re-render? Resetting all the parameters of your app? The equivalent of a page refresh? Removing components?

Yes @maximlt resetting all the parameters, equivalent to a page refresh.

So for the sample code mentioned here we want the ‘main’ to reset and ‘get_main_section_2’ should reflect the current time.

The actual use-case involves resetting of various components of the template.

The documentation states that:

These four areas (header, sidebar, main, modal) behave very similarly to other Panel layout components and have list-like semantics. This means we can easily append new components into these areas. Unlike other layout components however, the contents of the areas is fixed once rendered. If you need a dynamic layout you should therefore insert a regular Panel layout component (e.g. a Column or Row) and modify it in place once added to one of the content areas.

So you can’t directly modify main in your example, but you can add e.g. a Column in there that you modify on updates. I have spent very little time with your code but this is a working example of that.

import panel as pn
from datetime import datetime

my_button = pn.widgets.Button(name="CLICK",button_type="primary")

def handle_button_click(event):
    global my_template
    my_button.name = "CHANGED"
    my_template.main[1][0] = pn.pane.Alert('New')
    

def get_main_section_1():
    my_button.on_click(handle_button_click)
    return my_button

def get_main_section_2():
    return pn.pane.Alert(f'## time is {datetime.now()}')

my_template = pn.template.FastListTemplate(
        title="DASHBOARD TITLE",
        main=[
            get_main_section_1,
            pn.Column(get_main_section_2,),
        ],
    )
def app():
    return my_template

app().servable()

Now to reset your application, you could either reset all the widgets/components/parameters to their default (maybe discarding some events with the discard_events context manager provided by Param), or re-create the components of your app and replace the existing ones.

1 Like

Thanks @maximlt
Adding a column in main and modifying it for update worked fine.

3 Likes