Post request from stimulus for a dropdown component

I was able to understand turbo-streams and I would like now to use them for a dropdown autocomplete component, but I am having issues triggering the post request to get the turbo-streams after the user selects the item from the dropdown.

I have an html component for a dropdown like so:

<div class="dropdown is-active is-fullwidth" data-controller="dropdown">
...
<input class="input" data-action="input->dropdown#search click->dropdown#clear" data-dropdown-target="userinput" placeholder="Search..." type="search">

And then a controller like so:

    application.register("dropdown", class extends Stimulus.Controller {
        static get targets() {
            return ["dropdownItem", "userinput", "fromDropdown", "ddContent"]
        };
        initialize() {
            this.dropdownItemTargets.forEach((element, index) => {
                element.classList.add("is-hidden");
            });
            this.content.classList.add("is-hidden");
        };
        clear() {
            if (this.input == null) {
                this.content.classList.add("is-hidden")
            }
        };
        search() {
            if (this.input == "") {
                this.content.classList.add("is-hidden");
                this.dropdownItemTargets.forEach((element, index) => {
                    element.classList.add("is-hidden");
                });
            } else {
                this.content.classList.remove("is-hidden");
                this.dropdownItemTargets.forEach((element, index) => {
                    if (element.innerText.includes(this.input)) {
                        element.classList.remove("is-hidden");
                    } else {
                        element.classList.add("is-hidden");
                    };
                });
            }
        };

        set(event) {
            event.preventDefault()
            const value = event.target.dataset.value
            this.input = value
            this.content.classList.add("is-hidden")
            const baseUrl = "/scheduler/testing"
        };

        get content() {
            return this.ddContentTarget
        }

        get items() {
            return this.itemTarget.value
        };

        get input() {
            return this.userinputTarget.value
        };

        set input(value) {
            return this.userinputTarget.value = value
        };
    })

The controller works ok but now I want to issue a post request on the action set() and I am struggling with this. There doesn’t seem an easy way to send a post request from stimulus? What is the best practice? I tried both creating the post request fully in js and using js to submit an html form with no results.

Basically I want a post request after the user selects the dropdown item, this post request should not trigger any redirect.

It sound like you need fetch:

set(event) {
  // ...
  fetch(baseUrl, { method: "POST" })
}

This will just make a request, it won’t redirect you.

I tried with fetch but the turbo-streams response is not picked up by turbo. Instead, if I put that baseUrl into a form button it works and the dom is mutated with the response from post.

I need to run from javascript as I would like the post to fire on selecting the dropdown item instead of requiring a button click after the selection.

Ah got you. Have you tried requestSubmit? That will submit the form programatically (no button click) and Turbo will pick up your stream resonse.

You’ll probably need a polyfill, though.

This works with the code below for set, is it hotwire best practice? I am asking because going like this means we need to create buttons even when users won’t use them due to being triggered from js.

        set(event) {
            event.preventDefault()
            const value = event.target.dataset.value;
            this.input = value;
            this.content.classList.add("is-hidden");

            const form = document.getElementById("jobs_dropdown_form");
            form.action = `/scheduler/from_dropdown_to_table/?name=${value}`
            console.log(form)
            const submitButton = document.getElementById("jobs_dropdown_button");
            form.requestSubmit(submitButton);
        };

Hmm…I’m not sure you need a button for requestSubmit to work. The submitter argument — submitButton in your example — is optional. When it’s not passed in, the form is the submitter, as the MDN doc linked above says. requestSubmit is not simply an API to say submitButton.click() (although it achieves the same thing), it’s an api to submit the submitter.

Remember, if you need to support Safari (and SafariMobile) you will need a polyfill for requestSubmit.

Walter

@walterdavis thanks, in fact I am not a javascript expert so I’ll dig into that too. Lucky thing is that in my company I can tell people to use Firefox or Chrome and so I am less worried about compatibility for now

@dan thanks good pointer, here is my solution now. 2 things are interesting here:

  • You can register any value on any element with data-. So I have registered both the dropdown display value and the href value to hit with the post request. Amazing stuff
  • I can create and remove a form following @dan input
<div class="dropdown-item" 
data-action="click->dropdown#set" 
data-dropdown-target="dropdownItem" 
data-href="/scheduler/from_dropdown_to_table?name=name1" 
data-value="name1">
name1
</div>
        set(event) {
            event.preventDefault()
            const value = event.target.dataset.value;
            const href = event.target.dataset.href
            this.input = value;
            this.content.classList.add("is-hidden");

            var form = document.createElement("form");
            form.id = "jobs_dropdown_form"
            form.action = href
            form.method = "post"
            document.body.appendChild(form);
            form.requestSubmit();
            form.remove()
        };

I have to say that creating/removing a form feels a bit hacky but it allows for a clean dropdown api on the python/server side. I have been reading around this forum, it seems this practice is ok, right?