Colour coding pulldown menu items in Select

I have developed a bokeh 3.6.2 app to interactively display and correlate telescope test data. There are many test runs (~40) of which 15 are special. I thought it would be nice to colour code the special ones so that when one clicked the pulldown, the special ones would be red.

ChatGPT and I have been toiling over this, and I’ve had just no luck getting something to work. In the code that follows, I set up a css stylesheet and attach it to the Select widget. I then have js code to find widgets using that class and modify the option element colours. While the css and js_code do appear in the html, the js_code seems to never be executed - there is no debug message in the console log.

What am I missing?

from bokeh.io import curdoc
from bokeh.layouts import column, layout
from bokeh.models import Select, Div, GlobalInlineStyleSheet

options = ['Option 1', 'Option 2', 'Option 3', 'Option 4']

# CSS to style the select options
css = """
<style>
.select-dropdown .bk-input option[value="Option 1"] {
    color: orange;
}
.select-dropdown .bk-input option[value="Option 2"] {
    color: green;
}
.select-dropdown .bk-input option[value="Option 3"] {
    color: #ff0000;
}
</style>
"""

# JavaScript to apply styles based on the highlight list
highlight_list = ['Option 1', 'Option 3']
js_code = f"""
document.addEventListener('DOMContentLoaded', function() {{
    console.log("DOMContentLoaded event fired!");
    const selectEls = document.querySelectorAll('.select-dropdown .bk-input');  // Ensure correct class
    console.log("selectEls:", selectEls);
    selectEls.forEach(selectEl => {{
        console.log("Processing selectEl:", selectEl);
        const options = selectEl.querySelectorAll('option');
        console.log("options:", options);
        options.forEach(option => {{
            console.log("Checking option:", option.value);
            if ({highlight_list}.includes(option.value)) {{
                console.log("Highlighting option-red for:", option.value);
                option.classList.add("option-red");
            }} else {{
                console.log("Highlighting option-black for:", option.value);
                option.classList.add("option-black");
            }}
        }});
    }});
}});
"""

stylesheet = GlobalInlineStyleSheet(css=css)

# Create the Select widget with options
name_dropdown = Select(title="Pick an option", value='Option 1', options=options, stylesheets=[stylesheet])

# Inject the JavaScript to ensure CSS is applied after the document is ready
select_js = Div(text=js_code)
curdoc().add_next_tick_callback(lambda: curdoc().add_root(select_js))

# Create layout and add to document
canvas_layout = layout(column(name_dropdown))
curdoc().add_root(canvas_layout)
curdoc().title = "Bokeh Example with Inline CSS and JS Injection"

@richardxdubois I am not that good at css but I believe you need to travel down the shadowRoot tree of the DOM elements.

const selectEls = document.querySelector('.bk-Column').shadowRoot.lastChild.shadowRoot.querySelector('.bk-Select').shadowRoot.querySelectorAll('.bk-input-group .bk-input');

You can also attach a CustomJS callback to the Select widget. (I mention this due to lack of understanding your code :grinning_face:).

cb_select = CustomJS(
    args = {'highlight_list': highlight_list},
    code = """
    //const selectEls = document.querySelectorAll('.select-dropdown.bk-input');  // Ensure correct class
    const selectEls = document.querySelector('.bk-Column').shadowRoot.lastChild.shadowRoot.querySelector('.bk-Select').shadowRoot.querySelectorAll('.bk-input-group .bk-input');
    console.log("selectEls:", selectEls);
    selectEls.forEach(selectEl => {{
        console.log("Processing selectEl:", selectEl);
        const options = selectEl.querySelectorAll('option');
        console.log("options:", options);
        options.forEach(option => {{
            console.log("Checking option:", option.value);
            if (highlight_list.includes(option.value)) {{
                console.log("Highlighting option-red for:", option.value);
                option.classList.add("option-red");
            }} else {{
                console.log("Highlighting option-black for:", option.value);
                option.classList.add("option-black");
            }}
        }});
    }});
    """
    )
stylesheet = InlineStyleSheet(css=css)

# Select widget...

name_dropdown.js_on_change('value', cb_select)

Hi Jonas. My first problem is getting the js_code called. In my real code.

I do have a python callback for it that does all the work for when the input file is changed. I am looking for a mechanism to format the pulldown menu (I’d forlornly hoped that there was a formatter for this; one can apply a formatter to the selected value - not for the entire options= list).

My reading suggested that querySelectorAll would find any widget using the select-dropdown class.

That said, I don’t entirely understand the option.classList.add part of the javascript (and not sure how to check it - this was chatGPT’s suggestion. It looks plausible :slight_smile: .

Richard

Hey. your suggestion however did work, needing to follow the shadowRoot. And I realised I can have both js and python callbacks.

So now I can debug/figure how the colour gets set since all the js is now being executed.

Thanks!

Richard

and closing the loop - I sorted out how to set up a style per option in the js, but to no avail - nothing uses it. I guess not surprising. Looks like monochrome pulldowns after all…

@mateusz do you have any guidance or suggestions here?