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 79,047+ developers who get early access to new tutorials, screencasts, articles, and more.

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

    Screencast tutorials to help you learn Ruby on Rails, Javascript, Hotwire, Turbo, Stimulus.js, PostgreSQL, MySQL, Ubuntu, and more.

    © 2023 GoRails, LLC. All rights reserved.