Use combobox with @interact

Hi ipywidgets users,

I want to use Combobox to replace Dropdown in the folloing codes to implement the related boxes.

from ipywidgets import Dropdown, interact
geo = {'USA':['CHI','NYC'],'Russia':['MOW','LED']}
countryW = Dropdown(options=geo.keys())
cityW = Dropdown()

def update_cityW_options(*args, **kwargs): 
    try:
        cityW.options = geo[countryW.value]
    except:
        cityW.options = [""]
cityW.observe(update_cityW_options)
        
@interact(country = countryW, city = cityW)
def print_city(country, city):
    print(country, city)

After substitutions, the code is the following:

from ipywidgets import Combobox, interact
geo = {'USA':['CHI','NYC'],'Russia':['MOW','LED']}
countryW = Combobox(options=list(geo.keys()), description="country")
cityW = Combobox()
cityW.observe(update_cityW_options)

def update_cityW_options(*args, **kwargs): 
    try:
        cityW.options = geo[countryW.value]
    except:
        cityW.options = [""]
    
@interact(country = countryW, city = cityW)
def print_city(country, city):
    print(country, city)

In my code, I can select items in country, but, after selected, I cannot select items in city.
Only after I type somethings in city and erase them, I can select items in city.
Can somebody kindly give me some hint to figure out the problems?

Thank you!

Both of your examples have this line that I find strange:

cityW.observe(update_cityW_options)

This should actually be

countryW.observe(update_cityW_options)

So that when a new country is selected you show its cities in the city widget.

With Combobox I would also add cityW.value = "" into the try block to clear the input box because leaving a city from another country doesn’t make much sense after country change.

With these changes things work pretty much as expected for me =).

2 Likes

@gereleth
Thanks for your kind help

Continuing this topic, I have another problem if I want to use multiple .observe function

from ipywidgets import Combobox, interact
geo0 = {'USA':['CHI','NYC'],'Russia':['MOW','LED']}
geo1 = {"Taiwan":['TPE', "HSI"], "China":["SHA","SHE"]}
countryW, cityW = [], []

# Define list of comboboxes 
for idx in range(0, 2):
    var = eval("geo" + str(idx))
    countryW.append(Combobox(options=list(var.keys()), description="Country "+str(idx)))
    cityW.append(Combobox(description="City "+str(idx)))  

# Define the handler
def update_options(*arg):    
    global idx
    var = eval("geo" + str(idx))
    try:
        cityW[idx].options = [""]
        cityW[idx].options = var[countryW[idx].value]
    except:
        cityW[idx].options = [""]   
        
# Ask for the callback        
for idx in range(0, 2): 
    countryW[idx].observe(update_options)

# Print the country name & city name
for idx  in range(0, 2):
    @interact(country = countryW[idx], city = cityW[idx])
    def print_city(country, city):
            print(country, city)

I don’t know why only the final callback works in this code, that is, there is no option in the City 0 column
image

You use the idx variable that’s going to be equal to 1 after your code runs. So the callback will always run with idx == 1.

The observe handler actually gets a change dict when it runs. You can use change['owner'] to get the widget instance and find its index in the countryW list.

# Define the handler
def update_options(change):    
    idx = countryW.index(change['owner'])
    # update city options...
        
# Ask for the callback        
for idx in range(0, 2): 
    countryW[idx].observe(update_options, names='value')

Here I’ve also added names='value' to the observe call so that your function only runs when widget value changes.

2 Likes

It works pretty well.
This really gives me big hand!
Thanks