Custom button could not change the layout

Hi all,

I am trying to build a custom button that could be triggered with keyboard “enter” button.
It seems works well but I could not change the layout, such as height and width of the button.
This is the code.

import panel as pn
import param

BUTTON_TYPES = ['default', 'primary', 'success', 'warning', 'danger']
class CustomButton(pn.reactive.ReactiveHTML):
    name = param.String()
    clicks = param.Integer()
    value = param.Event()
    button_type = param.ObjectSelector(default='default', objects=BUTTON_TYPES)
    
    def __init__(self, **params):
        if not "name" in params:
            params["name"]=''
        super().__init__(**params)
        
    _template = """
    <div id="keydown" onkeydown='${script("keydown_enter")}'></div>
    <div>
    <button class="bk bk-btn bk-btn-${button_type}" type="button" id="pn_custom_button" onclick="${_increment}"></button>
    </div>
    """
    
    _scripts = {
        'render': """
        pn_custom_button.textContent = data.name
        """,
        
        'keydown_enter': """
            if(window.event.keyCode == 13) {
                pn_custom_button.click();
            }
        """,
    }
    
    def _increment(self, _):
        self.clicks+=1
        self.param.trigger('value')

    
custom_button = CustomButton(name='test')
pn.extension()
pn.Row(custom_button.controls(),custom_button).servable()

Any suggestions are greatly appreciated.

Thanks,
Arifin

adding button and height to the style of the button ? It can be that width and height keywords are used, then it does not work if you use width, and height. Just in case, I put width1.

Edit: maybe someone can answer you better, but it you use the width and height of the layout controls, it will not work. The reactive html bypass the bokeh layout engine, then those parameters are not more used. This improve rendering time when the layout of your app is nested, with several components.

import panel as pn
import param

BUTTON_TYPES = ['default', 'primary', 'success', 'warning', 'danger']
class CustomButton(pn.reactive.ReactiveHTML):
    name = param.String()
    clicks = param.Integer()
    width1 = param.Integer(default=400)
    value = param.Event()
    button_type = param.ObjectSelector(default='default', objects=BUTTON_TYPES)
    
    def __init__(self, **params):
        if not "name" in params:
            params["name"]=''
        super().__init__(**params)
        
    _template = """

    <div id="keydown" onkeydown='${script("keydown_enter")}'></div>
    <div>
        <button class="bk bk-btn bk-btn-${button_type}" 
            type="button" id="pn_custom_button" style="width:${width1}px"
            onclick="${_increment}"
            >
        </button>
    </div>
    """
    
    _scripts = {
        'render': """
        pn_custom_button.textContent = data.name
        """,
        
        'keydown_enter': """
            if(window.event.keyCode == 13) {
                pn_custom_button.click();
            }
        """,
    }
    
    def _increment(self, _):
        self.clicks+=1
        self.param.trigger('value')

    
custom_button = CustomButton(name='test')
pn.extension()
pn.Row(custom_button.controls(),custom_button).servable()
1 Like

Instead of adding a width1 parameter. You can also experiment with setting style="width:100%;height:100%;" or something similar on the button. Alternatively you can try using model.width.

For example this does not need a width2. I think it works much better as it works with any combination of sizing_mode, width and height I’ve tried.

import panel as pn
import param

BUTTON_TYPES = ['default', 'primary', 'success', 'warning', 'danger']

class CustomButton(pn.reactive.ReactiveHTML):
    name = param.String()
    clicks = param.Integer()
    value = param.Event()
    button_type = param.ObjectSelector(default='default', objects=BUTTON_TYPES)

    height = param.Integer(default=40, bounds=(0, None))


    def __init__(self, **params):
        if not "name" in params:
            params["name"]=''
        super().__init__(**params)

    _template = """

    <div id="keydown" onkeydown='${script("keydown_enter")}'></div>
    <button class="bk bk-btn bk-btn-${button_type}"
        type="button" id="pn_custom_button" style="width:100%"
        onclick="${_increment}"
    >{{name}}</button>


    """

    _scripts = {
        'keydown_enter': """
            console.log("click")
            if(window.event.keyCode == 13) {
                pn_custom_button.click();
            }
        """,
    }

    def _increment(self, _):
        self.clicks+=1
        self.param.trigger('value')


custom_button = CustomButton(name='test')
pn.extension()
pn.Row(custom_button.controls(), custom_button).servable()
2 Likes

An alternative might be something like the Canvas example Custom Components — Panel 0.12.3 documentation which uses the model.width and model.height.

2 Likes

you are a source of wisdom @Marc

I was playing with d3 a little, and I have several problems with width and the autosizing mode. I will play a little more with this new info and I will post here. I tried with render and after_layout scripts, but when resizing thw window it becomes buggy.

2 Likes

It would be nice if there was one way to implement width and height. And if we could add a description to the docs. But so far it’s always involved a bit of experimentation for me.

1 Like

Hi @nghenzi and @Marc,

Thank your solutions. Got it! As you said, it is really helpful if there are some documentations regarding the width and height.

Anyway, thanks!