Hotwire Discussion

Manually force trigger a change event on select in nested form using stimulusjs

Using Haml, ruby 2.7.2, rails 6.1.2.1, stimulus ^2.0.0

So, I am using a nested form that shows a select type. Based on the value of the select type, I will either show or hide a div. I’m converting my coffeescript to stimulus, and I’m not sure how to force trigger a change event, or instead just run the code that checks the value of the select to decide if I should hide or show the div. In the past, Inside my html.haml view I would call this:

:javascript
  $('select.answer_type').change()

Based on another post in here: Triggering Turbo Frame with JS, I tried to change it to say:

:javascript
  q_typeTarget.dispatchEvent(new Event('change'));

And that didn’t work. Maybe there is another way to do this, or I’m using the wrong vocabulary to look up the example I want to do? I’d rather just manually execute the Controller#action and give it the select object to work off of right away, but I’m not sure how to do this in html/script tag. Thanks for any help!

Here is my relevant code that should help:
_question_fields.html.haml

.nested-fields.question
  .form_group.grid-x
    .fields.cell.shrink
      = f.select :answer_type, 
                 options_for_select(["String","Option","Checkbox"],
                 f.object.answer_type),{}, 
                 class: 'answer_type', 
                 data: {nested_form_target: 'q_type', 
                        action: 'nested-form#update_q_type'}

    .cell.answers_group{ style: 'visibility: hidden; display: none'}
      = render partial: 'answers', locals: { f: f, q_level: q_level }

:javascript
  q_typeTarget.dispatchEvent(new Event('change'));

nested_form_controller.js

import { Controller } from "stimulus"

export default class extends Controller {
  static targets = ["q_type", "answers"]

  update_q_type(event) {
    event.preventDefault()
    console.log('You have selected ' + this.q_type )

    let d_question = event.target.closest(".question")
    let d_select   = d_question.querySelector("select")
    let d_answer   = d_question.querySelector(".answers_group")

    console.log('Select: ' + d_select.value )

    switch(d_select.value) {
      case 'Option':
      case 'Checkbox':
        console.log(' Show Answers ')
        d_answer.style.visibility = "visible"
        d_answer.style.display = null
        return

      default:
        console.log(' Hide Answers ')
        d_answer.style.visibility = 'hidden'
        d_answer.style.display = 'none'
        return
    }

}

Update: I didn’t realize my html tags were being interpreted as html. Oops. Changed all tags to italicized words. Ex: ‘/</select/>/’ is now just select

I’m not clear, why are you trying to manually dispatchEvent an event instead of firing the event from an select → onchange event?

1 Like

Hey @tleigh, thanks for replying. Actually, I do call the select → change event, and that works as intended. Here is a .gif of the nested forms, shown below:

  1. I add a Question
  2. I select an option from the select tag, which triggers the select → change event: nested-form#update_q_type
  3. If I select either Option or Checkbox, then I show the div, otherwise I hide the div

Now, here is why I’m trying to trigger the select → change event. If I start to edit this record again, it loads the data just fine. However, it does not know when to show the answers div or not. That’s why I need to trigger the select → change event so that it can properly set up the answers div to either hide or show. Otherwise, it’s defaulted to always ‘hide’.

I have another .gif below. As you can see, it loads the select with Option selected. if I try to select Option again, it doesn’t do anything…because it hasn’t really changed. If I change it to Checkbox, it of course updates. So basically, I need to do this onLoad, you can say.

SurveyTest-2021-02-24_15.48.12|690x360 (Sorry it won’t let me embed the 2nd screenshot example)

Maybe there is a different way to do this? Thank you for your help!

So, you are saying:

  1. It works with select@onchange
  2. It does not work with document@onload (which you want it to)

Am I understanding correctly?

I tried reading your response a few times, but could not grasp what you is trying to do.

are you able to clarify?

Hey @tleish ,

Yes, 1. It works with select@onchange
Yes, 2. It does not work with document@onload…however…It more like a select@onload, except I don’t believe there is such a thing as select@onload. Therefore I’m trying to force a select@onchange as soon as the select html has been created within the partial.

Here is another .gif of the process.

Another was to look at this, how can I force the Select->Change event in Debugger mode? I try the jQuery and it seems to not work. I try the stimulus call (and maybe I’m doing this wrong) and says the reference is not there.

You can try using the stimulus connect method? Something like.

import {Controller} from "stimulus"

export default class extends Controller {
  static targets = ["q_type", "answers"]
  
  connect(){
    this.q_typeTargets.forEach((target) => this.update_q_type(target))
  }

  onChange(event) {
    event.preventDefault()
    console.log('You have selected ' + this.q_type)
    this.update_q_type(event.target);
  }

  update_q_type(target) {
    let d_question = target.closest(".question")
    let d_select = d_question.querySelector("select")
    let d_answer = d_question.querySelector(".answers_group")

    console.log('Select: ' + d_select.value)

    switch(d_select.value) {
        case 'Option':
        case 'Checkbox':
        console.log(' Show Answers ')
        d_answer.style.visibility = "visible"
        d_answer.style.display = null
        return

    default:
      console.log(' Hide Answers ')
      d_answer.style.visibility = 'hidden'
      d_answer.style.display = 'none'
      return
    }
  }
}

Sorry for the long delay in replying. Thank you for the idea, but it didn’t solve the issue. However, didn’t break any existing code either.

Did you get this figured out, or are you still looking for help?