0

I have a partial inside a partial which has radio buttons or check boxes. What I want to do is to get the radio button or checkboxes which are clicked. Now initially when the page is loaded I am able to get the buttons but when I go to the next partial I am not able to get the new buttons. How can I get the buttons of the new partial everytime some button is clicked in a separate js. If I use onclick function inline with radio button or checkbox then the function is called correctly but I want to get the present displayed elements in a separate js file.

I tried to use window.addEventListener('change'). If I use this then the function is not called on first click but in the subsequent clicks it calls that many number of times i.e., on second click the function is called once, on third click the function is called twice and so on.

// window.addEventListener('change', () => {
window.addEventListener('DOMContentLoaded', () => {
  if (document.querySelectorAll('[name="question[answer_id]"]').length !== 0) {
    document.querySelectorAll('[name="question[answer_id]"]').forEach((questionAnswerButton) => {
      questionAnswerButton.addEventListener('click', (event) => {
        console.log(event);
        fetchCall(event.target.value);
      });
    });
  }
});

radio_button_partial.html.erb

<%= radio_button_tag 'question[answer_id]',
                                  answer.id,
                                  (user_answer == answer.id),
                                  {
                                    class: 'answer_opt',
                                    // onclick: fetchCall("<%= answer.id %>")
                                  } %>

Here if I uncomment the onclick function then I get the desired functionality. But what should I change in this that I get the present displayed radio buttons from the separate js file?

Lax_Sam
  • 1,099
  • 2
  • 14
  • 32

1 Answers1

1

Instead of attaching a listener directly to the elements you want to use event bubbling:

document.addEventListener('click', (event) => {
  if (event.target.matches('[name="question[answer_id]"]')) {
    console.log(event);
    fetchCall(event.target.value);
  }
});

When an event is fired it up bubbles up the DOM until a handler is found. Unlike attaching event handlers directly to the elements this is idempotent and the handler will work for elements dynamically inserted into the page. Its also compatible with turbolinks.

This code should not be placed in a script tag or .js.erb abomination as it will add a handler every time the code is executed. Put it in the assets pipeline.

If fetchCall does an ajax call you will want to use a debouncing technique such as disabling the input and re-enabling it when the promise is resolved.

document.addEventListener('click', (event) => {
  if (event.target.matches('[name="question[answer_id]"]')) {
    console.log(event);
    // Todo refactor fetchCall so that it returns a promise
    let promise = fetchCall(event.target.value);
    event.target.disabled = true;
    promise.then((successMessage) => {
      event.target.disabled = false;
    });
  }
});
max
  • 96,212
  • 14
  • 104
  • 165
  • Yes. fetchCall returns a promise. It uses fetch API. `fetchCall = async (someId) => {};` – Lax_Sam Feb 17 '20 at 16:09
  • Can you please tell me why I should disable and then re-enable the element before and after the promise is resolved? Also why is `js.erb` an abomination? I am still learning about rails and javascript. So I do not know these. – Lax_Sam Feb 17 '20 at 16:22
  • Disabling the element prevents it from sending multiple ajax calls if the user double clicks or clicks the element again before the first ajax call is read. Also why is `js.erb` an abomination? This is really just my opinion but it blurs the responsibilites between the client side and server side to the point where everything just ends a a huge mess of spagetti code and really does a disservice to anyone trying to learn javascript. – max Feb 17 '20 at 16:36
  • The premise of being able to reuse your rails views in javascript is really nice in theory but once you start going down that rabbit hole your server side application ends up ridiculous mess of non-restful routes that really just serve to change the page state of the browser. Also you end up with smatterings of javascript everywhere that are not minified, impotent or anything but garbage gluecode. This is just a fancier version of filling your views with script tags which we know is a bad practice. – max Feb 17 '20 at 16:39
  • There is also the whole issue of mental context switching. When your generating javascript dynamically in ruby it becomes much harder to reason about the code as it becomes even more unclear what is actually happening on the server and what will happen later on the client. This is already an area that most beginners struggle with in web development and blurring the lines does not help. – max Feb 17 '20 at 16:58
  • Oh ok. I understood. Thanks for the explanation. If I use the code you provided it solves my problem but if I click on the checked button even when it is disabled it is making the Ajax call. How can I rectify it? – Lax_Sam Feb 17 '20 at 17:02
  • Do you you think you could make that a new question? Its kind of outside of the scope of your original question and I would really need to see the fetchCall function. – max Feb 17 '20 at 17:07
  • Ok. Sure. I will try to find the solution. If not then I will post another question and put the link here in another comment. Thanks for the answer and explanations. – Lax_Sam Feb 17 '20 at 17:12