0

In my Rails app, I have a form for voting on various "post" titles. The form has one select element, and submits remotely to a VotesController using rails-ujs. I want to handle response the normal way, in the controller receiving the request, with respond_to do |format|.

One option is adding {:onchange=>'$(this.form).submit();'} to the f.select builder, as other answers have suggested.

However, because form submit takes about 8 seconds, I'd like to add my own handler to either the select onchange, or the form onsubmit (which event doesn't matter to me) to

  • disable all other forms on the same page
  • show a modal saying why we're disabling the other forms
  • only allow one "favorite" vote per user (downgrade previous votes on other titles)
  • store some data attributes in the select for later handling

The issue i'm having is

  • if I bind my extra handler to the select change event, i can fire a submit via native rails-ujs, but the browser asks me if I want to leave the current page. I can solve this by adding my own AJAX call to my handler , but I'm trying to use the default Rails controllers and rails-ujs to handle submit. I just want to add an extra handler to change the DOM before the slow response arrives.
  • if I bind my extra handler to the form submit event, the form does not submit and the binding does not have any effect on elements in the DOM, although there are no console errors.

Here's my code:

# partial appears on PostsController#show
# there are several of these on the page, for voting on post titles

<%= form_with(model: @vote, url: naming_vote_path(naming_id: naming.id),
              method: :patch, local: false, id: "cast_vote_#{naming.id}",
              class: "naming-vote-form") \
              do |f| %>
  <%= f.select(:value, menu, {},
               { class: "form-control w-100",
                 # onchange: "this.form.submit();", # works but does not allow further bindings
                 data: { role: "change_vote", id: naming.id } }) %>
<% end %>

  $(document).ready(function () {

    var change_vote_selects = function () {
      return $("[data-role='change_vote']");
    };

    var attach_bindings = function () {
      change_vote_selects().on("change", function (event) {
        var _this = $(this);
        var value = _this.val();
        var naming_id = _this.data("id");
        _haveVotesChanged = true;

        // If setting vote to 3.0, go through all the rest and downgrade any
        // old 3.0's to 2.0.  Only one 3.0 vote is allowed. Also disable all
        // the selects while the AJAX request is pending.
        if (value == "3.0") {
          change_vote_selects().each(function () {
            var _this2 = $(this);
            if (_this2.data("id") != naming_id && _this2.val() == "3.0") {
              _this2.val("2.0");
            }
            _this2.attr("disabled", "disabled");
          });
        }

        // modal printed in layout already, add text to it and activate
        $('#naming_ajax_progress_caption').empty().append(
          $("<span>").text(translations.show_namings_saving + "... "),
          $("<span class='spinner-right mx-2'></span>")
        );
        $("#naming_ajax_progress").modal('show');

        _this.parent().submit();
      });

      // Save initial value in case of error, when we'll need to revert.
      change_vote_selects().each(function (event) {
        var _this = $(this);
        _this.data("old_value", _this.val());
        _this.attr("disabled", null);
      });
    };

    // Alert the user if they haven't saved data.
    window.onbeforeunload = function () {
      if (_haveVotesChanged && !_haveVotesBeenSaved)
        return translations.show_namings_lose_changes;
    }

    attach_bindings();
  });
Ryan M
  • 18,333
  • 31
  • 67
  • 74
nimmolo
  • 191
  • 3
  • 14

1 Answers1

0

Thanks to this answer, I need to fire a native rails-ujs submit on the form, to submit remotely.

  $(document).ready(function () {

    var change_vote_selects = function () {
      return $("[data-role='change_vote']");
    };

    var attach_bindings = function () {
      change_vote_selects().on("change", function (event) {
        var _this = $(this);
        var value = _this.val();
        var naming_id = _this.data("id");
        _haveVotesChanged = true;

        // If setting vote to 3.0, go through all the rest and downgrade any
        // old 3.0's to 2.0.  Only one 3.0 vote is allowed. Also disable all
        // the selects while the AJAX request is pending.
        if (value == "3.0") {
          change_vote_selects().each(function () {
            var _this2 = $(this);
            if (_this2.data("id") != naming_id && _this2.val() == "3.0") {
              _this2.val("2.0");
            }
            _this2.attr("disabled", "disabled");
          });
        }

        // modal printed in layout already, add text to it and activate
        $('#naming_ajax_progress_caption').empty().append(
          $("<span>").text(translations.show_namings_saving + "... "),
          $("<span class='spinner-right mx-2'></span>")
        );
        $("#naming_ajax_progress").modal('show');

        nativeFormEl = _this.parent()[0]
        Rails.fire(nativeFormEl, 'submit')

      });

      // Save initial value in case of error, when we'll need to revert.
      change_vote_selects().each(function (event) {
        var _this = $(this);
        _this.data("old_value", _this.val());
        _this.attr("disabled", null);
      });
    };

    // Alert the user if they haven't saved data.
    window.onbeforeunload = function () {
      if (_haveVotesChanged && !_haveVotesBeenSaved)
        return translations.show_namings_lose_changes;
    }

    attach_bindings();
  });
nimmolo
  • 191
  • 3
  • 14