Clearing Autocomplete value when restrict=False

Hi,

I recently stumbled on the restict option of Autocomplete. I desire to utilize this to allow users to add regular expressions to match numerous items at once. After they enter their selection, I desire for the autocomplete object value to be an empty string. If, in a python callback, I set the value of the Autocomplete instance to an empty string, I’ll envoke the callback. To avoid this, I remove the callback, clear the string, then add the call back back again. (is there a better way to do this in general?)

The problem, when I set restrict=False on the auto-complete object and I select a completion (rather than un-restricted text), the callback is called twice even though at the time of clearing the ‘value’ there are no callbacks on the object. This only happens when:

  • restrict=False
  • my option is in the completion list

Here is a bokeh server example that illustrates this:

from bokeh.plotting import figure, curdoc
from bokeh.layouts import layout
from bokeh.models import AutocompleteInput


def add_cb(obj):
    def hello(attr,old,new):
        print('hello', attr, old, new)
        obj.remove_on_change('value',this_function)
        obj.value=''
        obj.on_change('value',this_function)
        pass

    this_function=hello
    obj.on_change('value',hello)


completions=['Apple','Pear','Banana','']
a=AutocompleteInput(title='Restricted',completions=completions,restrict=True)
b=AutocompleteInput(title='UnRestricted',completions=completions,restrict=False)
add_cb(a)
add_cb(b)

l=layout([a,b])
curdoc().add_root(l)

If you type “Dog”, for example, in the ‘Unrestricted’ Autocomplete box, the call back is performed once:

hello value  Dog

If you type “Apple”, however, in the ‘Unrestricted’ Autocomplete box, the call back is perfomed twice:

hello value  Apple
hello value  Apple

Typing Apple in the ‘Restricted’ callback works as expected.

Ultimate desire, clear the contents of the autocomplete object without invoking the second call back --which in my cases toggles the selected value off.

Thanks,

I have to confess the code above does not make sense to me. In the callback for obj you are setting its own value, and this is genarally not a thing to do. I think you mean to clear the other callback? But if so, I think you are actually over-complicating things by making this “callback factory” function add_cb. I would just make two separate callback functions that do whatever they need to do (e.g. if they fire with an empty value, just return early or whatever is appropriate… that is also IMO simpler than temp adding/removing callbacks).

Also I cannot reproduce what you are seeing with latest Bokeh 2.4. Are you using an older version? You should always always include version information in any support question about any piece of software.

Hi Bryan,

Thanks for the quick response. What I truly want:

  • User inputs value
  • Server processes value
  • Autocomplete value is now empty and ready for the user to add another value, otherwise they need to clear out the value.

So what is the best way to clear the value of the Autocomplete object?

Justin

On Bokeh 2.4 your code above does exactly that as far as I can tell. If you are using an older version, then the solution is to upgrade. As for the approach, personally I would probably just check and return early for an empty string at the top of the callback, rather than bothering with the temporary add/remove.

Hi Bryan,

I’ve updated to Bokeh 2.4.0, but still see the issue. I’ve re-written the code to try and be explicit with what my observation is, while also taking your suggestion to bail out when there is an empty string:

from bokeh.plotting import figure, curdoc
from bokeh.layouts import layout,column
from bokeh.models import AutocompleteInput,Div


def add_cb(obj):
    def hello(attr,old,new):
        if new=='':
            return
        print('Calling cb on {name} with attr={attr}; old={old}; new={new}'.format(name=obj.name,attr=attr,old=old, new=new))
        obj.update(value='')
        div.text=log()
        pass

    obj.name=obj.title
    obj.on_change('value',hello)
    pass



from io import StringIO
import sys
stdout=StringIO()
#comment this line to see standard log                                                                                                                                                                                                                                                   
sys.stdout=stdout

def log():
    return """<h2>My Log File</h2>                                                                                                                                                                                                                                                       
    <pre>                                                                                                                                                                                                                                                                                
{}                                                                                                                                                                                                                                                                                       
</pre>""".format(stdout.getvalue())


def standard_hello(attr,old,new):
    print('standard_hello',attr,old,new)
    div.text=log()
    pass



completions=['Apple','Pear','Banana']
div=Div(text=log(),width=400,name='my_div')
a=AutocompleteInput(title='Restricted',completions=completions,restrict=True,placeholder='Type Apple, see one log message')
b=AutocompleteInput(title='UnRestricted',completions=completions,restrict=False,placeholder='Type Apple, see two log messages')
c=AutocompleteInput(title='Standard',completions=completions,restrict=True,placeholder='Backspace to clear last selected value')
add_cb(a)
add_cb(b)
c.on_change('value',standard_hello)

l=layout(column([a,b,c]),column(div))

curdoc().add_root(l)
  • create Autocomplete instance with restrict=False
  • clear the value at end of callback so user does not have to do that themselves
  • while app is running type in a valid completion
  • observe the call back get invoked twice with the completion value
    • in my case 4 times since I get the callback when clearing the value.

The way I use bokeh widgets is to artificially give them a state. So I track all selected values that an object has. If a user wants to de-select an item, they simply type it in and I note that they no longer want to see ‘Apples’. So when it gets called twice, I can never properly capture any values. (I am aware of Multichoice and will probably utilize it more in the future.)

Justin

Hi Bryan,

I am able to get desirable behavior by modifying this js line:

if (!this.model.restrict) { 

to

else if (!this.model.restrict) {

So If I am not mistaken, then there is no need to change_input if the first condition succeeds, that is I although restrict is False, I still selected an item in my completions list.

If you agree, I can submit a bug report and or change request.

Thanks,
Justin

Let’s start with an issue. I think if possible it will be a clearer demonstration if the example is pared down to just the one widget with restrict=True and a simple non-closure callback.

Sure, but you mean restrict=False? That is where I have the issue.

Yes, apologies, I just mean in general that an MRE should only contain the broken case (whatever that is)

Issue created:

Perhaps my use case is unique, however, I think my proposed fix is an overall improvement.

Thanks,