Skip to main content

Populate dropdowns based on selection with Stimulus JS

Javascript • Asked by Jay Killeen

This is a follow up question from Populate dropdowns based on selection that I asked like... 3 years ago... and still haven't reallly done it very well.

Now that StimulusJS is here. Things seem more structured.

I have a stimulus controller below. After I complete the AJAX request, I want to send that data to another function just to keep my code clean. How do I call the this.doThingWithData(data) in the Ajax success callback? All I get so far is a TypeError: this.doThingWithData is not a function

import { Controller } from "stimulus"

export default class extends Controller {

  static targets = [ "material_id", "to_unit", "result"]

  get material_id() {
    return this.targets.find("material_id").value
  }

  updateToUnitOptions() {
    const material_id = this.material_id

    Rails.ajax({
      type: "GET",
      url: "/alt_units.json",
      data: "material_id=" + material_id,
      success: function(data) {
        message()
        this.doThingWithData(data)
      }
    })
    this.resultTarget.innerHTML = "You have selected material: " + material_id
  }

  doThingWithData(data) {
    // update a Stimulus Target
    console.log(data)
  }
}

function message() {
  console.log('Alt Units were got!');
}

Simply, all I am doing here is taking the material_id from a select dropdown, and then eventually I want to update the next dropdown menu item with the filtered list of alt_units that comes back from the alt_units_controller#index from rails.


You need to have your callback using a fat arrow => instead so it keeps the scope.

      success: (data) => {
        this.message()
        this.doThingWithData(data)
      }

That will retain the scope so that this refers to the Stimulus controller. That will fix your method call error.


Wow. I was just deep diving in the console and wondering why this inside the callback was only referencing the Rails.ajax object... OK! Back on the road again!

I am now at the point where the dropdown menu 'toTarget' options need to be updated. Thanks for your help.

  updateToUnitOptions() {
    const material = this.material

    Rails.ajax({
      type: "GET",
      url: "/alt_units.json",
      data: "material=" + material,
      success: (data) => {
        console.log('Alt Units were got!')
        this.refreshDropdownValues(data)
      }
    })
  }

  refreshDropdownValues(data) {
    // update a Stimulus Target
    this.result = this.material
    this.toTarget <<<<< here is where I need to update selection option values.
    console.log(data)
  }
    ```

Did you ever finish this? I am looking to do the exact same thing.


I did and it worked pretty well. I'll hunt down a sample of the code and share back here soon. Might take me a while though as I am away for a few days. I'll dump a bit below but I don't have time to cut out the sensitive info.

It was a little more to it than I first thought it would be. Here is a bit of stimulus. Notice the stuff with the Rails.Ajax that is querying my rails controller and returning json. So a bit is needed to be done in the controller to respond to that ajax request and only return the json in a format for stimulus to use.

I'm running it all from a new.html.erb that is requesting to create and then returning a show partial which allows the dropdown to be updated based on the selection of the previous dropdown. It really needs a demo of the whole thing I put together in a more generic way than what I had done in my application (ie do a simple country / state / city selector from Rails api all the way through to stimulus).

import { Controller } from "stimulus"

export default class extends Controller {

  static targets = [ "material", "price", "from", "query", "result", "button"]

  initialize() {
    console.log("Stimulus at your service!")
    this.updateQueryParams()
    this.toggleLoading()
  }

  get from () {
    return this.targets.find("from").value
  }

  get material() {
    return this.targets.find("material").value
  }

  get price() {
    return this.targets.find("price").value
  }

  toggleLoading() {
    this.targets.find("button").classList.toggle("is-loading")
  }

  updateToUnitOptions() {
    this.clearResult()

    Rails.ajax({
      type: "GET",
      url: "/alt_units.json",
      data: "material=" + this.material,
      success: (data) => {
        console.log('Alt Units were got!')
        this.refreshDropdownValues(data)
      }
    })
  }

  refreshDropdownValues(data) {
    let fromBefore = this.from
    this.fromTarget.innerHTML = ""
    for(var i = 0; i < data.length; i++) {
      var opt = data[i]
      this.fromTarget.innerHTML += "<option value=\"" + opt.name + "\">" + opt.name + "</option>"
    }
    this.fromTarget.value = fromBefore
    this.updateQueryParams()
  }

  clearResult() {
    this.queryTarget.innerHTML = ""
  }
}

Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 22,346+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.