Refreshing Input fields - StanfordBioinformatics/pulsar_lims GitHub Wiki

Overview

When filling out a form, there are often selection input fields containing items from another model. A selection can become stale, in which case it should be refreshed in order to allow the user to continue without having to retype all form inputs due to a manual, full-page refresh.

Why might the selection items become stale? As an example, the Biosample model links to the Donor model and the donor is a required input field. When entering a new biosample, the user will need to select the right donor. If the donor that is needed doesn't appear in the donor selection list, then the user will need to open a new tab, go to the Donors page, and add a new donor record. Then, the user can switch back to the other tab, and in order for the new donor to appear in the donor selection list, we can add some logic to allow the user to refresh this selection list.

How Pulsar implements refreshing an input field

If you guessed AJAX, then you'd be correct. A refresh icon is added right after any label tag that has the class "refresh". This icon tag of the label will also have the class "refresh", and this all happens in application.js:

$("label.refresh").after('<i class="refresh fa fa-refresh"></i>')

The tedious part though is that a custom click handler needs to be registered with each refresh icon. Extending the example above regarding a user interacting with the form for filling out a new biosample, the donor selection input looks like this:

<%= f.association :donor,
   collection: Donor.order(:name),
   label_method: :name,
   prompt: "Choose The Donor",
   label_html: {
       class: "refresh",
       "data-toggle": "tooltip",
       "title": "The donor of the biosample"
   }
%>

The important part in this stub is the addition of the "refresh" class to the label.

The next important task is to add the click handler. I did this in the biosamples.js.coffee file as follows:

#Refresh the donors list in the form when the refresh fa-icon is clicked:
$(document).on "click", ".biosample_donor i.refresh", (event) ->
  $.get "/donors/select_options", (responseText,status,jqXHR) ->
    $(".biosample_donor select").html(responseText)

Notice that I added biosample_donor in the jQuery selector to find the right icon tag with a "refresh" class. This is necessary to restrict the icon with which I register the click event callback. Why? Because there are other input fields in this form that may need to be refreshed in the same manner, i.e. the Biosample model also links to the Vendor model. The label tag for the selection input for vendors also has the "refresh" class, so it too gets a refresh icon, which needs to have its very own click event handler. Furthermore, this is necessary so as to not conflict with refresh icons in other forms. It's important to remember that all Javascript code gets compiled into a single file that is shared across the entire application.

To finish our refresh logic, we just need to add a new controller action and a new view. Continuing our example with a donors selection on a form for a new biosample, the controller action should be in the Donors controller, and the naming convention I use for this particular action is select_options. The body of this action will look pretty much like that of the index action, except that we render a dedicated view: app/views/donors/select_options.html.erb. The code for the action is:

def select_options
  #Called via ajax.
  #Typically called when the user selects the refresh icon in any form that has a donors input.
  @records = Donor.all
  render "application_partials/select_options", layout: false
end

And the code for the dedicated view is simply:

<% options = @records.order(:name).map {|rec| [rec.name, rec.id]} %>
<% if not defined?(@no_blank) %>
  <% options.unshift(["",""]) %>
<% end %>
<%= options_for_select(options) %>

This view only need be written once and is stored at views/application_partials/select_options.html.erb. Note that it is not a partial, rather an actual view, despite it being placed in the custom application_partials folder. Don't forget to add your new route as well.

Summary

You'll need to follow the convention outlined above to any selection input that may need to be refreshed. Quite a bit of boiler-plate code, unfortunately, and if I find a better way to do this then I'll implement that.

⚠️ **GitHub.com Fallback** ⚠️