Rails 6 + UJS: make ajax call on blur without jQuery

1.3k views Asked by At

I have a form with two input text fields. The datalist for the second field needs to be populated when the first field has a value. I have everything working as I want except I can't work out how to fire the ajax call when the first field loses focus (I have put in a link I can click to test everything else is working.)

org.rb

class Org < ApplicationRecord
  has_many: competitions
end

_form.html.haml

  .field
    = f.label "Competition:"
    League:
      = text_field_tag :org, @match.org&.name, list: "orgs", size: 60 # onblur: ?
    Division:
      = text_field_tag :competition, @match.competition&.name, list: "comps", size: 30

  %datalist#orgs
    = options_for_select Org.all.map(&:name).sort

  %datalist#comps
    = render "comps"

_comps.html.haml

= options_for_select @comps.map(&:name).sort

controller

  def comps
    org = Org.find_from_identifier(params[:org])
    @comps = org&.competitions || []
    respond_to do |format|
      format.js
    end
  end

comps.js.erb

comps = document.getElementById("comps")
comps.innerHTML = "<%= j render "comps" %>"

If I put this line into the form everything works as I expect when I click it:

= link_to 'test', update_comps_path(org: 'scottishfootballleague'), remote: true

(Routes.rb has get 'matches/comps', to: "matches#comps", as: :update_comps in it.)

So my question is how do I do what that link does when the first input field loses focus?

I tried putting onblur: 'updateComps()' into the field and using

:javascript
  function updateComps() {
    var org = document.getElementById("org").value
    fetch("/matches/comps?org=" + org )
  }

This works in the sense that the call gets made at the right time but I'm missing whatever magic data-remote: true attaches to the link to make it an ajax call that works in the rails world. Am I missing a Rails helper? Or do I need to instruct fetch() with what to do with the js that gets returned?

Edit: I've got as far as working out that the response to the fetch consists of the javascript I expect, and which presumably needs to be executed. I've tried this:

:javascript
  function updateComps() {
    var org = document.getElementById("org").value
    fetch("/matches/comps?org=" + org,
      { credentials: "include"  })
    .then(response => {
      eval(response.text());
    })
  }

There are two problems with this. 1) it doesn't work 2) this doesn't seem very rails-like.

Update (with an answer)

I got the fetch approach working and executing the server generated js:

function updateComps() {
    var org = document.getElementById("org").value
    fetch("/matches/comps.js?org=" + org,
      { credentials: "include"  })
    .then(response => response.text())
    .then(text => {
      eval(text)
    })
  }

I'm still not at all sure what the "Rails way" of doing this is. Rails.ajax() seems to have been removed (or is only available 'internally') according to some github discussions.

2

There are 2 answers

1
Amit Patel On

Simplest hack is hide(display:none) your test link and fire click on blur of first field.

<script>
  var firstField = document.getElementById("id-of-first-filed");
  firstField.addEventListener("blur", updateComps);

  function updateComps(){
    document.getElementById("id-of-test-link").click();
  }
</script>

Rails way of doing is user Rails.ajax so your updateComps will looks like.

function updateComps() {
  var org = document.getElementById("org").value

  Rails.ajax({
    url: "/matches/comps.html?org=" + org,
    data: { credentials: "include" }
  })
}

Rest of the code for controller and js template will be reused.

1
smithWEBtek On

In app/assets/javascripts/appliction.js

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require rails-ujs
//= require activestorage
//= require_tree .

then instead of Rails.ajax

use $.ajax