Ask A Question

Notifications

You’re not receiving notifications from this thread.

Populate dropdowns based on selection with Stimulus JS

Jay Killeen asked in Javascript

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!');
}
Reply

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.

Reply

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.

Reply

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)
  }
    ```
Reply

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

Reply

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 = ""
  }
}
Reply

Hi, a favor, can you post the full solution, please?

Reply

The dynamic dropdown with Stimulus JS was a feature I spend a lot of time trying to solve, eventually got it working like this.

import { Controller } from "stimulus";

export default class extends Controller {
  static targets = [   
    "property_id",
    "roomId",
    "listRooms",
  ];

updateRooms() {
    const property_id = this.targets.find("property_id").value;
    Rails.ajax({
      type: "GET",
      url: "/get_rooms.json",
      data: "property_id=" + property_id,
      success: (data) => {
        this.updateDropdown(data);
      },
    });
  }

  updateDropdown(data) {
    this.roomIdTarget.innerHTML = "";
    const num_rooms = data;
    if (num_rooms == 0) {
      const option = document.createElement("option");
      option.innerHTML = "Entire place";
      this.roomIdTarget.appendChild(option);
    } else {
      data.forEach((room) => {
        const option = document.createElement("option");
        option.value = room.id;
        option.innerHTML = "Room " + room.name;
        this.roomIdTarget.appendChild(option);
      });
    }
  }
Reply

Thomas or Jay, do you have a repo of your solution(s)? I've been struggling with this same thing and would love to take a look. Thanks!

Reply

I worked this out for Stimulus Reflex. I know when I searched for this it landed me here initially. Here's a link to the repo i made demonstrating this: https://github.com/Vielhammer/stimulus_reflex_geo_selector_demo

Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 82,584+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.