Trigger callback on a TextArea component on enter key press (or allow multi-line values in Input component)

Hi there,

I have not found a way of being able to “shift+enter” to create new lines in an Input component.

Therefore, I’m going towards using TextArea. However, I’d like to avoid forcing users to get to the Submit button using the mouse, and allow them to press enter (as can be done with the Input component).

Do you know of any workaround either using Input or Textarea that fulfills those requirements?

Thank you so much!

Hello @newlog,

I believe that these would need to have the onSubmit function there.

Input has this available via debounce:

TextArea does not have this available.

Indeed. I’m using the debounce with the Input component in order to send it by pressing enter. However, with the Input component it seems I can’t get multi-line values.

And it seems that with the TextArea the opposite happens.

Am I missing anything? Isn’t there a way to achieve this? I guess as a last resort I might be able to create a JS callback, but I’m kind of out of my depth and I’d like to avoid this hacky approach.

The issue with a TextArea is that the enter key is used inside of it, while the Input doesnt not support line breaks.

If using a TextArea, you could use tab and then hit enter on the submit button. With text areas, I would think that this is a standard process, especially since it could be a lot of text and the user may want to review before submitting.

You could check out event listeners on dash-extensions, but you’d still have to do some custom JS.

Here is something you could try with the id of the component:

app.clientside_callback(
""" function (id) {
    document.getElementById(id).addEventListener('keydown', (event) => {
        if (event.Enter && !(event.ctrlKey || event.shiftKey)) {
            document.getElementById('submit_button').click() // triggers submit button
        }
    })
    return window.dash_clientside.no_update
}""",
Output('textarea','id'), Input('textarea', 'id')
)

Thanks for the guidance. At least I can see there is no straightforward way of achieving this.

I’ll try to use the approach you mention (I had used this in my code before) but try to get the “shift+enter” even and add a new line in the contents of the Input.

Thanks again!

1 Like

This is a solution that most likely I won’t keep for the reasons below, but I thought it might be useful to someone.

function(){
    var chatInputs = document.getElementsByClassName("user-input-class");
    for (var i = 0; i < chatInputs.length; i++) {
        chatInputs[i].addEventListener('keydown', (event) => {
            if (event.key === 'Enter' && event.shiftKey) {
                event.preventDefault();
                var inputElement = document.getElementById(event.target.id);
                if (inputElement && inputElement.value) {
                    inputElement.value = inputElement.value + "\\n";
                    inputElement.rows = inputElement.rows + 1; 
                }  
            }
        })    
    }
    return window.dash_clientside.no_update;
}

This code assumes you have a callback with the textarea n_submit Input so you can send messages when pressing the enter key.

The problems with this approach:

  1. Textarea does not shrink dynamically when newlines are deleted.
  2. When shift-enter is clicked when in the middle of a line, the cursor makes a weird jump and skips one line or so.

The alternative I will explore is to use a regular textarea and hook the enter key press and submit the click as the response above. However, I won’t get the dynamic rows. I’ve googled around and it seems to get a textarea that dynamically grows/shrinks with the content seems tricky:

Hey @newlog

Give the dash-mantine-components TextArea a try. It has an auto size feature:

@AnnMarieW

Thanks for the response. I will definitely take a look.

For now, I (just) implemented it using a client-side callback:

def _set_callback_for_dynamically_resize_textarea(page: DashBlueprint):
    js_function = """function(){
        var chatInputs = document.getElementsByClassName("user-input-class");
        for (var i = 0; i < chatInputs.length; i++) {
            chatInputs[i].addEventListener('input', (event) => {
                var textareaElement = document.getElementById(event.target.id);
                if (textareaElement) {
                    textareaElement.style.height = "0px";
                    textareaElement.style.height = textareaElement.scrollHeight + "px";
                }
            })    
        }
        return window.dash_clientside.no_update;
    }"""
    page.clientside_callback(
        js_function,
        Input("bogusOutputForCallback", "id"),
        Output("bogusOutputForCallback", "className", allow_duplicate=True),
    )

There are some other things that need to happen like setting the height when clicking enter (I needed another clientside callback for this), but that’s it.

Edit: Those components look awesome by the way. I wish I knew about them before!

2 Likes