-
Notifications
You must be signed in to change notification settings - Fork 37
Leveraging javascript in Rails: the easy way
This is a case study of the use of javascript in a rails app, fully exploiting the capabilities provided by Rails helpers and server-side JS processing. This uses some of the capabilities defined in the rails guide Working with JavaScript in Rails.
The "SHF" app has the concept of a membership application ("application"), submitted by a user ("applicant") who wants to become a member of the organization. An organization Admin then reviews the application.
In some situations, the Admin may decide that she needs more information added to the application, and moves the application to ask applicant for info status. As part of the that, the Admin identifies what kind of information is needed - this can be done by selecting a pre-defined description of the required information, or by entering free-form text which describes the required information.
In the screenshot below, the Admin has selected clicked the ask applicant for info button (moving it to that status). This results in a POST action to the controller (to set the new state), followed by a rendering of the application-review view, which now displays a subform for specifying the required information:
<screenshot 1>
Then, the Admin selects "Other (enter the reason)". At that point, a text-entry field (labelled "other reason") appears, and the Admin enter a description of the required information:
<screenshot 2>
When the Admin leaves the text-entry field, the information is sent to the server.
We will first look at the "before" code, where the javascript-to-server integration was not fully leveraging the rails capabilities, to see how this is implemented. Then, we'll look at the "after" code - which has been converted to more fully leverage rails capabilities.
.row
.container
.reason-waiting-information
= label_tag :member_app_waiting_reasons,
t('membership_applications.need_info.reason_title')
- collection = AdminOnly::MemberAppWaitingReason.all.to_a
- collection << AdminOnly::MemberAppWaitingReason
.new(id: -1,
"name_#{I18n.locale.to_s}": "#{@other_waiting_reason_text}")
- selected = ! @membership_application.custom_reason_text.blank? ? -1 :
@membership_application.member_app_waiting_reasons_id
= select_tag(:member_app_waiting_reasons,
options_from_collection_for_select(collection,
:id, reason_name_method,
selected),
{ include_blank: t('membership_applications.need_info.select_a_reason'),
class: 'reason-waiting-list' })
.row
.container
#other-text-field
= label_tag :custom_reason_text,
t('membership_applications.need_info.other_reason_label')
= text_field_tag :custom_reason_text,
@membership_application.custom_reason_text
:javascript
var reasons_list = $('#member_app_waiting_reasons'); // the select HTML element (list)
var custom_text_info = $('#other-text-field');
var custom_text_field = $('#custom_reason_text');
// this option is added to the list of reasons and is only used so we can
// show/hide the custom reason text field
var other_reason_text = "#{@other_waiting_reason_text}";
var other_reason_option = document.createElement("option");
other_reason_option.text = other_reason_text;
other_reason_option.value = -1; // some value that will not be in the database or use by Rails
function selected_is_customOtherReason() {
return $('#member_app_waiting_reasons option:selected').text() === other_reason_option.text;
}
// send the reason selected to the server unless the 'custom/other' was selected
function changed_reason() {
// do not send a request if the reason selected is the
// "Other/custom... enter text" reason. Wait until the text field
// with the custom reason is entered to send that data
if (!selected_is_customOtherReason()) {
custom_text_field.val('');
send_updated_reason( #{@membership_application.id},
$('#member_app_waiting_reasons').find('option:selected').val() , null );
}
hideOrShowCustomReasonElements();
}
// Set the waiting_reason to nil because we instead have the info in the custom text field
function changed_custom_text() {
send_updated_reason( #{@membership_application.id},
null , custom_text_field.val() );
custom_text_info.show();
}
var hideOrShowCustomReasonElements = function() {
if ( custom_text_field.val() || selected_is_customOtherReason()) {
custom_text_info.show();
reasons_list.value = other_reason_option.value; // make sure this option is selected; important when first displaying the view
}
else {
// clear out any custom reason if the selected reason is not the custom reason
$('#custom_reason_text').val("");
custom_text_info.hide();
}
};
// Send information to the server
// no need to do anything if it was successful
var send_updated_reason = function(app_id, reason_id, custom_text) {
$.ajax({
url: "#{membership_application_path}",
type: "PUT",
data: { membership_application: { member_app_waiting_reasons_id: reason_id,
custom_reason_text: custom_text },
id: app_id }
}).fail(function(evt, xhr, status, err) {
alert( "#{I18n.t('membership_applications.update.error')}: " +
'Status: ' + evt.statusText );
});
};
var initialize = (function() {
// reasons_list.options.add(other_reason_option); // add the other_reason to the list
hideOrShowCustomReasonElements();
$('#member_app_waiting_reasons').on('change', changed_reason);
$('#custom_reason_text').on('change', changed_custom_text)
});
$(document).ready(initialize);
The first section of this code (before javascript:
) sets up a select field and a text-entry field - the former for selecting the description type of information we are waiting for, and the latter for entering custom description text if a pre-defined description is not appropriate for this particular application.
So, what's going on here? At a high level:
-
On page load, callback functions are associated with the select and text-entry fields in the view, triggered by the "onchange" event. Also, the custom information description is either hidden or shown, depending on whether a custom description has been assigned to this application.
-
When the Admin selects a reason for waiting for information, the selected reason is sent to the server unless it is "Other (enter the reason)", in which case the text-entry field is made visible.
-
When the Admin enters the reason for waiting for information, and exists the text-entry field (thereby triggering an "onchange" event), the entered text is sent to the server.
-
Sending the waiting-reason, and custom text (reason for waiting) is handled by an AJAX call to the server, which puts up an error message if the call fails or times out.