6

I'm using a Javascript bookmarklet to automatically fill out a form on a page. Some of the options given are drop down selections, which reveal different options depending on what is selected using onchange(). I have code similar to this:

/* Gets first drop down and sets value to first in list */
var dropDown1 = document.getElementById("dropDown1Name");
dropDown1.value = "option1InDropDown";
dropDown1.onchange();

/* Sets value of second drop down to option that is available when first option in first drop down is selected */
var dropDown2 = document.getElementById("dropDown2Name");
dropDown2.value = "optionRevealedByDropDown1Change";

However this doesn't work because the onchange() doesn't populate the second drop down by the time I set it to the value. By the time the script finishes executing, there is no value set in dropDown2. I've tried several methods to make the code "wait" but I haven't been able to find a correct solution. Any suggestions are appreciated.

user1527216
  • 1,143
  • 2
  • 13
  • 18
  • 2
    You can't always trigger the 'change' event like that, try using [element.dispatchEvent](https://developer.mozilla.org/en/DOM/element.dispatchEvent) instead. – GGG Aug 30 '16 at 17:23
  • 1
    I made a fiddle showing how you can do it [here](https://jsfiddle.net/GGG_KILLER/epm7wrk7/1/) – GGG Aug 30 '16 at 18:00
  • Thanks! That was really helpful in learning how to trigger events. I still have my issue though, since it seems like the second drop down value tries to set before the values are "set up" from the event. – user1527216 Aug 30 '16 at 23:04
  • I suggest you take a look at Stubb0rn's answer then, you'll have to watch for when the ` – GGG Sep 03 '16 at 02:26
  • @user1527216 have you reviewed my answer? Simply using `input` event rather than `change` event, should take care of that lag issue. `input` is instant whille `change` doesn't act until the `event.target` is no longer focused on (i.e. `blur` event). So using `change` makes you dependent on what the user does next. – zer00ne Sep 06 '16 at 15:50
  • The problems you will face are: (1) when the second `` element (2) listening to HTML changes requires newer browsers and MutationObserver. Can you tell us the page with the form? It would help for us to know how the second dropdown is populated. Perhaps a custom even is fired and can be listened to. – Nate Sep 06 '16 at 16:51
  • Might be inefficient, but you could use [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval) to check for changes on the second `` then remove the interval with [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval) after processing the second ` – GGG Sep 07 '16 at 20:02

2 Answers2

2

Bind the first <select> with the input event which is like change except it triggers immediately. In order to sync the first <select>'s value to the second <select>'s option and value use selectedIndex property in the event handler.

SNIPPET

var s1 = document.getElementById('s1');
var s2 = document.getElementById('s2');

s1.addEventListener('input', function(e) {
  var idx = this.selectedIndex;
  var data = idx.value;
  s2.selectedIndex = idx;

}, false);
label {
  margin-left: 6ex;
}
<fieldset>
  <legend>Binding Input Event</legend>
  <select id='s1'>
    <option>---</option>
    <option value='1'>1</option>
    <option value='2'>2</option>
    <option value='3'>3</option>
  </select>

  <select id='s2'>
    <option>---</option>
    <option value='A'>A</option>
    <option value='B'>B</option>
    <option value='C'>C</option>
  </select>


</fieldset>
Community
  • 1
  • 1
zer00ne
  • 41,936
  • 6
  • 41
  • 68
1

As it was already pointed out in comments one part of the problem is incorrect triggering of change event. Another part of the problem might be the fact that options for second dropdown might be populated only after receiving result of some async action (for example using XMLHttpRequest). In this case even if change event is triggered correctly, your code still needs to wait for dropdown to get populated before setting it's value. One approach might be based on regular checks if dropdown is populated with values (e.g. using setInterval or setTimeout). More elegant approach could use MutationObserver to wait for dropdown to get populated with values. Take a look at this snippet:

var sel1 = document.querySelector('#sel'),
  sel2 = document.querySelector('#sel2');

// Onchange code
sel1.addEventListener('change', function() {
  sel2.disabled = false;
  // emulate async request for dropdown options
  setTimeout(function populateSecondSelect() {
    ['', 'dynamic1', 'dynamic2'].forEach(function(val) {
      var opt = document.createElement('option');
      opt.setAttribute('value', val);
      opt.textContent = val || '-';
      sel2.appendChild(opt);
    });
  }, 2000);
});

// Code to trigger events (based on http://stackoverflow.com/a/2490876/2671392)
function triggerEvent(type, element) {
  var event; // The custom event that will be created

  if (!!window.Event) {
    event = new Event(type);
  } else if (document.createEvent) {
    event = document.createEvent("HTMLEvents");
    event.initEvent(type, true, true);
  } else {
    event = document.createEventObject();
    event.eventType = type;
  }

  event.eventName = type;

  if (document.createEvent) {
    element.dispatchEvent(event);
  } else {
    element.fireEvent("on" + event.eventType, event);
  }
}

document.querySelector('#btn').addEventListener('click', function() {
  sel1.value = sel1.value === '1' ? '2' : '1';
  // trigger change event
  triggerEvent('change', sel1);
  // set MutationObserver for second dropdown
  var observer = new MutationObserver(function(mutations) {
    sel2.value = 'dynamic2';
    observer.disconnect();
  });
  // only observe changes in the list of children
  var observerConfig = {
    childList: true
  };
  observer.observe(sel2, observerConfig);
});
<div>
  <input type="button" id="btn" value="Select values" />
  <select id="sel">
    <option value="" selected>-</option>
    <option value="1">Option 1</option>
    <option value="2">Option 2</option>
  </select>
  <select id="sel2" disabled>
  </select>
</div>

But be aware that MutationObserver is not available in older browsers.

Stubb0rn
  • 1,269
  • 12
  • 17