How to get value of a RadioButtonGroup in a jscallback?

I have this code:

pn_select = pn.widgets.Select(options=['Option1', 'Option2'], value='Option1')
pn_radio_button_group = pn.widgets.RadioButtonGroup(options=['Option1', 'Option2'], value='Option1')

pn_select .jscallback(
    args={'widget': pn_select }, 
    value="""
    console.log('Select: ', widget.value, cb_obj.value);
    """
)

pn_radio_button_group.jscallback(
    args={'widget': pn_radio_button_group}, 
    value="""
    console.log('RadioButtonGroup: ', widget.value, cb_obj.value);
    """
)

The respective results when changing the values are:

Select: Option2 Option2
RadioButtonGroup: undefined undefined

Although the Select works fine, neither the automatically created cb_obj, neither the manually created widget has a value attribute when a RadioButtonGroup changes. How can I still access it?

After seeing in the generated HTML code that the selected button receives a bk-active class, I checked the cb_obj and seen that it has a labels property containing every option and an active property containing the index of the selected option, so this works:

pn_radio_button_group.jscallback(
    args={'widget': pn_radio_button_group}, 
    value="""
    console.log('RadioButtonGroup: ', widget.labels[widget.active], cb_obj.labels[cb_obj.active]);
"""
)

Is this the intended way of getting the value of this widget? If so, why the difference?

FYI, if you defined your options as a dict, the situation is way worse:

rbg_options = {'Option One': 'option1', 'Option Two': 'option2'}
pn_radio_button_group = pn.widgets.RadioButtonGroup(options=rbg_options, value='option1')

In this case the widget.labels[widget.active] javascript code gives you only the label, and there is no way to get the actual value, they are not even showing up in the generated HTML code. If you still need that, as a workaround, you need to make your whole options dict available as a javascript object to be able to make a label->value translation like this:

import json
pn_radio_button_group.jscallback(
    args={'widget': pn_radio_button_group}, 
    value="""
    var rbg_options_in_js = """+json.dumps(rbg_options)+""";
    var widget_selected_label = widget.labels[widget.active];
    var cb_selected_label = cb_obj.labels[cb_obj.active];
    var widget_selected_value = rbg_options_in_js[widget_selected_label];
    var cb_selected_value = rbg_options_in_js[cb_selected_label];
    console.log('RadioButtonGroup: ', widget_selected_label, cb_selected_label, 
widget_selected_value, cb_selected_value);
"""
)

I included both widget and cb_obj in the example, just to see if it works, but you wouldn’t need both. If you only need to run this when the RadioButtonGroup changes, cb_obj will be automatically generated. If you need this function to run when another widget changes (so instead of pn_radio_button_group.jscallback it would be some_other_widget.jscallback), you need to manually input your RadioButtonGroup widget as I did in the example with the widget variable.

Also, the example above is fine if the RBG widget rarely changes and/or has few options. If it’s a weirdly huge widget or changes constantly (eg triggered by multiple sliders multiple times per second), you may want to put a pn.pane.HTML("<script>window['rbg_options_in_js'] = "+json.dumps(rbg_options)+";</script>") block once in your document and then using that window['rbg_options_in_js'] in your JS code to avoid dumping the same options every time the event runs.