DEV Community

Ravi Katta
Ravi Katta

Posted on

Ajax loading spinner in Rails with Javascript's events & mutation observer

This blog post is intended for intermediate programmers who have prior knowledge of rails ajax forms and javascript events.

There are tons of articles that explain the way to do the AJAX loader using JQuery but how can we solve it with javascript? This thought made me write this article.

Layout

Here we will be building a layout having an account as input and search button to show the list of matching accounts and the loader icon would show and hide during DOM events.

layout

Events

Our main objective is to display a loading spinner during page loading or the time when accounts are being fetched from the backend. So, to fit the spinner in, we can think of 2 two main scenarios

  1. To show a spinner, when a search button is pressed
  2. and to hide it back, when a child is added to a DOM element via Rails AJAX

Setup: Routes, Controllers and Views

I'm not going through the code in detail as it would make the blog lengthy.

First,

We define a route to new and search actions

Rails.application.routes.draw do
  root to: 'accounts#new'

  resources :accounts do
    collection do
      post 'search'
    end
  end
end

Enter fullscreen mode Exit fullscreen mode

Second,

Rendering js when a search action has occurred and sleep is added to delay the backend fetching time to see the loading spinner.

class AccountsController < ApplicationController

  def new
    @accounts = Account.new
  end

  def search
    @accounts = Account.where("name LIKE ?", "%#{new_params[:name]}%")
    sleep(2)
    respond_to do |format|
      format.js
    end
  end

  private

  def new_params
    params.require(:account).permit(:name)
  end

end

Enter fullscreen mode Exit fullscreen mode

Third,

The View will make three things: one a search js, the other a partial and the last one for the main view

1) The partial _accounts.html.erb will be added as a child to the main DOM element via AJAX.

<table class="table">
  <thead class="thead-dark">
    <tr>
      <th scope="col">Account Name</th>
      <th scope="col">Country</th>
        <th scope="col"> State</th>

    </tr>
  </thead>

  <tbody>
    <% @accounts.each do |acct,v| %>
      <tr>
        <td scope="row"><%=acct.name%></td> 
        <td scope="row"><%=acct.country%></td> 
        <td scope="row"><%=acct.state%></td> 
      </tr>
    <% end %>
  </tbody>
</table>

Enter fullscreen mode Exit fullscreen mode

2) The main view new.html.erb, will have an input field, a search button wrapped in a form with remote: true option, and an empty div accounts-details for displaying the accounts list

<h1 class="text-info">Find Accounts</h1>

<div class="shadow-sm p-3 mb-5 bg-white rounded">
<%= form_for( @accounts, url: search_accounts_path, method: :post, remote: true ) do |f| %>

    <div class="form-group">
         <%= label_tag(:name, 'Account Name') %>
         <%= f.text_field :name, required: true%>
    </div>   

    <div class="form-group form-check"> 
        <div class="actions" class="btn btn-info">
            <%= f.submit("Search") %>
        </div>
    </div>

<% end %>

</div>

<!-- List will added as child when a search button is clicked--> 
<div id="accounts-details"></div>
Enter fullscreen mode Exit fullscreen mode

3) The search.js.erb will do the magic by adding the partial ***_accounts.html.erb* as a child to an empty div accounts-details in the main view.

$("#accounts-details").html("<%= j render partial: 'accounts', locals: { accounts: @accounts } %>");
Enter fullscreen mode Exit fullscreen mode

4) A modal dialog box to hold the loading spinner

  <body>
    <div class="container-md">
        <%= yield %>

      <div id="myModal" class="modal">
        <div class="text-center">
          <div class="spinner-border" role="status">
            <span class="sr-only">Loading...</span>
          </div>
        </div>
      </div>
    </div>
  </body>
Enter fullscreen mode Exit fullscreen mode

Finally a loading spinner

a. Display a loading spinner on submit event listener


// Get the Id of the form
  const searchButton = document.getElementById('new_account');
// Get the Id of a loading spinner modal
  const modal = document.getElementById('myModal');

  const displayModal = (e) => {
    //e.preventDefault();
    modal.style.display =  'block' ;
  }

// Listen the submit click on the form and call display spinner
searchButton.addEventListener('submit', displayModal);

Enter fullscreen mode Exit fullscreen mode

b. Hiding a loading spinner on when a DOM element is added by using Mutation observer

// Get the ID of empty div
  const accountsDiv = document.getElementById('accounts-details');

// Get the Id of a loading spinner modal
  const modal = document.getElementById('myModal');

 const hideModal = (e) => {
    modal.style.display = 'none';
  }

// callback function will return the list of things changed an entry DIV. // In our case, a *childList* will be returned true, as we as adding partial.
  const callback = mutations => {
    mutations.forEach( (e) => {
            if (e.type === 'childList' ) {
              hideModal();
            };
    }); 
  };

// Configuration for the observer to listen to the DOM changes
  const config = {
    attributes: true,
    subtree: true,
    childList: true
  };

// Initializing a mutation observer
  const observer = new MutationObserver(callback);

// The main Listener to see DOM element changes
  observer.observe(accountsDiv, config);

Enter fullscreen mode Exit fullscreen mode

Note: As the event listener 'DOMNodeInserted' has drawbacks and so are using Mutation observer

 accountsDiv.addEventListener('DOMNodeInserted', hideModal);
Enter fullscreen mode Exit fullscreen mode

and Finally, Wrap all the logic in DOMContentLoaded event listener.

  window.addEventListener('DOMContentLoaded', domLoaded)

Enter fullscreen mode Exit fullscreen mode

Complete logic

GitHub logo kattak2k / ajax-rails-js-observer

loading spinner with ajax form

That's all, Thanks for reading!!

References

https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
https://developer.mozilla.org/en-US/docs/Web/API/EventListener
https://www.rubyguides.com/2019/03/rails-ajax/

Top comments (0)