Button with both js_on_click and python action

Hi there,

What is the proper way to have the same button for 2 actions :

  • First a Python action
  • next a js action.

exemple code :

boutonSaveFinish = pn.widgets.Button()

boutonSaveFinish.on_click(self._boutonSaveFinish)

redirect = f"""
        window.location.href="{self.url}"
        """
boutonSaveFinish.js_on_click(code=redirect)

I try this but js_on_click is run first and the python method self._boutonSaveFinish is not called.

Any purpose would be appreciate

1 Like

Maybe it is not so elegant but you can try an html pane and in your self._boutonSaveFinish method send the javascript code through html

html_pane = pn.pane.HTML(" ")

def _boutonSaveFinish():
    # do python stuff

    html_pane.object = f""" <script> window.location.href= "{{self.url}}" </script>
        """
    html.param.trigger('object')
2 Likes

So your example isn’t fully complete since I can’t see what self._boutonSaveFinish does. If I provide my own callback it does get called, but as you point out that doesn’t happen until after the JS callback is triggered. For now using the HTML pane is indeed probably your best bet, in the longer term we will add support for a Bokeh DataModel which will let you sync arbitrary attribute changes with the frontend and trigger JS callbacks.

thanks for your reply.

my exemple could not be run like this indeed . I will update the example with something like nghenzi2019 used, because here, what does the self._butonSaveFinish is not a big deal.

Thanks again

I had something similar to do and have a solution perhaps less convoluted if the python call is changing an attribute of a bokeh model.
So there is no unused elements.

In this example, the python callback empties the text input ; while the javascript callback focus on the text input. The latter does so only once the text input is correctly emptied, not before.


import panel as pn

import param

pn.extension()

pn.__version__

val_button = pn.widgets.Button(name = 'Focus once empty',
                                       button_type='primary',
                                       height_policy = 'fit',
                                       )

class TextInput_with_name(pn.widgets.TextInput):
    """
    Subclass to add name attribute and find bokehjs model
    """
    _name = param.String(default='')
    
    _rename = pn.widgets.TextInput._rename.copy()
    _rename.update({'_name': 'name'})

txt = TextInput_with_name(name='Input here', value='I shall be removed', _name='txtinput')

def empty_text_func(event):
    """remove text"""
    txt.value = ''

val_button.on_click(empty_text_func)

val_button.jscallback(clicks = """
let txt_element = document.getElementsByName(txt.name)[0]

function focus_only_when_text_empty() {
    if(txt.value!=='') {//if not empty
        setTimeout(focus_only_when_text_empty, 50);//recheck in 50 milliseconds
        return;
    }
    txt_element.focus();//focus only when empty
}

focus_only_when_text_empty();
""",
                     args={'txt':txt})

mylist = pn.Row(val_button, txt)

mylist.show()