//= require fe/fe.common.js // used by answer sheets window.fe = {}; fe.pageHandler = { initialize : function(page) { this.auto_save_frequency = 30; // seconds this.timer_id = null; this.current_page = page; $('#' + page).data('form_data', this.captureForm($('#' + page))); this.registerAutoSave(); this.page_validation = {}; // validation objects for each page this.enableValidation(page); $(document).trigger('feShowPage'); // allow other code to handle show page event by using $(document).on('feShowPage', function() { ... }); // this.background_load = false; // this.final_submission = false; }, // swap to a different page showPage : function(page) { // hide the old $('#' + this.current_page + '-li').removeClass('active'); $('#' + this.current_page).hide(); // HACK: Need to clear the main error message when returning to the submit page // It is very confusing to users to be there when they revisit the page $('#submit_message, .submit_message').hide(); $('#application_errors, .application_errors').html(''); // show the new // $('#' + page + '-li').removeClass('incomplete'); // $('#' + page + '-li').removeClass('valid'); $('#' + page + '-li').addClass('active'); $('#' + page).show(); this.current_page = page; this.registerAutoSave(page); fixGridColumnWidths(); $(document).trigger('feShowPage'); // allow other code to handle show page event by using $(document).on('feShowPage', function() { ... }); }, // callback onSuccess pageLoadedBackground : function(response, textStatus, jqXHR) { //this.pageLoaded(response, textStatus, jqXHR, true) fe.pageHandler.pageLoaded(response, textStatus, jqXHR, true) }, // callback onSuccess pageLoaded : function(response, textStatus, jqXHR, background_load) { background_load = typeof background_load !== 'undefined' ? background_load : false; // var response = new String(transport.responseText); let match = response.match(/
0) { $('#'+page).replaceWith(response); } else { $('#preview').append(response); } if (!background_load) { fe.pageHandler.showPage(page); } // show after load, unless loading in background setUpJsHelpers(); fe.pageHandler.enableValidation(page); if (background_load) { fe.pageHandler.validatePage(page, true); } $('#' + page).data('form_data', fe.pageHandler.captureForm($('#' + page))); } $('#page_ajax_spinner').hide(); $('.reference_send_invite').button(); updateTotals(); $(document).trigger('fePageLoaded'); // allow other code to handle page load event by using $(document).on('fePageLoaded', function() { ... }); }, loadPage : function(page, url, background_load, validate_current_page) { background_load = typeof background_load !== 'undefined' ? background_load : false; validate_current_page = typeof validate_current_page !== 'undefined' ? validate_current_page : true; let isValid; if (validate_current_page) { isValid = this.validatePage(this.current_page); // mark current page as valid (or not) as we're leaving } else { isValid = true } // Provide a way for enclosing apps to not go to the next page until the current page is valid // They can do that with this: // // $(document).on 'fePageLoaded', (evt, page) -> // $(".page > form").addClass('enforce-valid-before-next'); // if (!isValid && $('#' + this.current_page + "-form").hasClass('enforce-valid-before-next')) { // scroll up to where the error is scrollTo($(".help-block:visible")[0].closest("li")); return; } this.unregisterAutoSave(); // don't auto-save while loading/saving // will register auto-save on new page once loaded/shown this.savePage(); if (!background_load) { if ($('a[name="main"]').length == 1) { scrollTo('a[name="main"]'); } else { scrollTo('#main'); } } if (fe.pageHandler.isPageLoaded(page) && page.match('no_cache') == null) { // if already loaded (element exists) excluding pages that need reloading if (!background_load) { fe.pageHandler.showPage(page); } $('#page_ajax_spinner').hide(); } else { $.ajax({ url: url, type: 'GET', data: {'answer_sheet_type':answer_sheet_type}, success: background_load ? fe.pageHandler.pageLoadedBackground : fe.pageHandler.pageLoaded, error: function (xhr, status, error) { alert("There was a problem loading that page. We've been notified and will fix it as soon as possible. To work on other pages, please refresh the website."); document.location = document.location; }, beforeSend: function (xhr) { $('body').trigger('ajax:loading', xhr); }, complete: function (xhr) { $('body').trigger('ajax:complete', xhr); } }); } }, // save form if any changes were made savePage : function(page, force, blocking) { if (page == null) page = $('#' + this.current_page); if (typeof blocking == "undefined") blocking = false; // don't save more than once per second let timeNow = new Date(); let lastSave; if (typeof lastSave !== "undefined" && lastSave && !force && (timeNow - lastSave < 1000)) { return true; } lastSave = timeNow; let form_data = this.captureForm(page); if( form_data ) { if( page.data('form_data') == null || page.data('form_data').data !== form_data.data || force === true) { // if any changes page.data('form_data', form_data); $.ajax({url: form_data.url, type: 'put', data: form_data.data, beforeSend: function (xhr) { $('#spinner_' + page.attr('id')).show(); }, complete: function (xhr) { $('#spinner_' + page.attr('id')).hide(); }, success: function (xhr) { page.data('save_fails', 0) $(document).trigger('fePageSaved'); // allow other code to handle save event by using $(document).on('fePageSaved', function() { ... }); }, async: !blocking, error: function() { let save_fails = page.data('save_fails') == null ? 0 : page.data('save_fails'); save_fails += 1; page.data('save_fails', save_fails) if (save_fails >= 3) { alert("There was a problem saving that page. We've been notified and will fix it as soon as possible. This might happen if you logged out on another tab. The page will now reload."); document.location = document.location; } else { page.data('save_fails', save_fails + 1) page.data('form_data', null); // on error, force save for next call to save // WARNING: race conditions with load & show? // sort of a mess if save fails while another page is already loading!! } }}); } } // Update last saved stamp }, savePages : function(force) { $('.answer-page').each(function() {fe.pageHandler.savePage(null, force)}) }, // setup a timer to auto-save (only one timer, for the page being viewed) registerAutoSave: function(page) { this.timer_id = setInterval(this.savePages, this.auto_save_frequency * 1000); }, unregisterAutoSave: function() { if( this.timer_id != null ) { clearInterval(this.timer_id); this.timer_id = null; } }, // serialize form data and extract url to post to captureForm : function(page) { let form_el = $('#' + page.attr('id') + '-form'); if( form_el[0] == null ) return null; let form_all_el = form_el.find("input:not(.dont_submit), textarea:not(.dont_submit), select:not(.dont_submit)"); return {url: form_el.attr('action'), data: form_all_el.serialize() + '&answer_sheet_type=' + answer_sheet_type}; }, // enable form validation (when form is loaded) enableValidation : function(page) { // Provide a way for enclosing apps to not have validation continually as people fill things out // They can do that with this: // // $(document).on 'fePageLoaded', (evt, page) -> // $(".page > form").addClass('no-ongoing-validation'); // $('#' + page + '-form:not(.no-ongoing-validation)').validate({onsubmit:false, focusInvalid:true, onfocusout: function(element) { this.element(element);}}); $('#' + page + '-form:not(.no-ongoing-validation):input').change(function() { fe.pageHandler.validatePage(page, true); }); }, validatePage : function(page, page_classes_only) { page_classes_only = typeof page_classes_only !== 'undefined' ? page_classes_only : false; // Provide a way for enclosing apps to never validate a form // They can do that with this: // // $(document).on 'fePageLoaded', (evt, page) -> // $(".page > form").addClass('no-validation'); // if ($('#' + this.current_page + "-form").hasClass('no-validation')) { return; } try { let li = $('#' + page + '-li'); let form = $('#' + page + '-form'); let valid = form.valid(); if (!page_classes_only) { // Move radio button errors up $('input[type=radio].error').closest('tr').addClass('error'); $('.choice_field input[type=radio].error').removeClass('error') .closest('.choice_field') .addClass('error'); $('div.yesno label.error').hide(); } if (valid) { li.removeClass('incomplete'); li.addClass('complete'); $(document).trigger('fePageValid', page); // allow other code to handle show page event by using $(document).on('fePageValid', function() { ... }); } else { li.removeClass('complete'); li.addClass('incomplete'); $(document).trigger('fePageInvalid', page); // allow other code to handle show page event by using $(document).on('fePageInvalid', function() { ... }); } return valid; } catch(err) { // If the user clicks too quickly, sometimes the page element isn't properly defined yet. // If we don't catch the error, js stops execution. If we catch it, the user just has to click again. } $('page_ajax_spinner').hide(); }, // callback when falls to 0 active Ajax requests completeAll : function() { $('.page:visible #submit_button').attr('disabled', true) $('#submit_message, .submit_message').html(''); $('#submit_message, .submit_message').hide(); // validate all the pages $('.page_link').each(function(index, page) { fe.pageHandler.validatePage($(page).attr('data-page-id')); }); let all_valid = ($('#list-pages li.incomplete').length == 0); // Make sure any necessary payments are made let payments_made = $('.payment_question.required').length <= $('.payment').length if( payments_made) { // force an async save (so it finishes before submitting) in case any input fields on submit_page this.savePage(null, true, true); // submit the application if($('#submit_to')[0] != null) { let url = $('#submit_to').val(); // clear out pages array to force reload. This enables "frozen" apps // immediately after submission - :onSuccess (for USCM which stays in the application vs. redirecting to the dashboard) let curr = fe.pageHandler.current_page; $.ajax({url: url, dataType:'script', data: {answer_sheet_type: answer_sheet_type, a: $('input[type=hidden][name=a]').val()}, type:'post', beforeSend: function(xhr) { $('body').trigger('ajax:loading', xhr); }, success: function(xhr) { $('#list-pages a').each(function() { if ($(this).attr('data-page-id') != curr) $('#' + $(this).attr('data-page-id')).remove(); }) }, complete: function(xhr) { $('body').trigger('ajax:complete', xhr); let btn = $('#submit_button'); if (btn) { btn.attr('disabled', false); } } }); } } else { // some pages aren't valid $('#submit_message, .submit_message').html("Please make a payment"); $('#submit_message, .submit_message').show(); let btn = $('#submit_button'); if (btn) { btn.attr('disabled', false); } } }, // is page loaded? (useful for toggling enabled state of questions) isPageLoaded : function(page) { return $('#' + page)[0] != null }, checkConditional : function($element) { let vals; let matchable_answers = String($element.data('conditional_answer')).split(';').map(function(s) { return s.trim(); }) if ($element.hasClass('fe_choicefield') && ($element.hasClass('style_yes-no') || $element.hasClass('yes-no'))) { if ($(matchable_answers).filter([1, '1', true, 'true', 'yes', 'Yes']).length > 0) { matchable_answers = [1, '1', true, 'true', 'yes', 'Yes']; } if ($(matchable_answers).filter([0, '0', false, 'false', 'no', 'No']).length > 0) { matchable_answers = [0, '0', false, 'false', 'no', 'No']; } vals = $([$element.find("input[type=radio]:checked").val()]); } else if ($element.hasClass('fe_choicefield') && $element.hasClass('checkbox')) { vals = $element.find("input[type=checkbox]:checked").map(function(i, el) { return $(el).val(); }); } else if ($element.hasClass('fe_choicefield') && $element.hasClass('radio')) { vals = $([$element.find("input[type=radio]:checked").val()]); } else { vals = $([$element.find("input:visible, select:visible").val()]); } let match = $(matchable_answers).filter(vals).length > 0 || (matchable_answers == "" && vals.length == 0); switch ($element.data('conditional_type')) { case 'Fe::Element': if (match) { $("#element_" + $element.data('conditional_id')).show(); } else { $("#element_" + $element.data('conditional_id')).hide(); } break; case 'Fe::Page': let prefix = $element.data('answer_sheet_id_prefix'); let pg = prefix + '_' + $element.data('application_id') + '-fe_page_' + $element.data('conditional_id'); let li_id = 'li#'+pg+'-li'; li_id += ', li#'+pg+'-no_cache-li'; if (match) { $(li_id).show(); // load the page (in the background) to determine validity this.loadPage(pg, $(li_id).find('a').attr('href'), true); } else { $(li_id).hide(); } break; } }, next : function(validate_current_page) { validate_current_page = typeof validate_current_page !== 'undefined' ? validate_current_page : false; let curr_page_link = $('#'+fe.pageHandler.current_page+"-link"); //fe.pageHandler.loadPage('application_22544-fe_page_165-no_cache','/fe/answer_sheets/22544/page/165/edit'); return false; let page_link = curr_page_link .parents('.application_section') .nextAll() .filter(function() { return $(this).find('a.page_link:visible').length > 0 }) .first() .find('a.page_link'); $(".answer-page:visible div.buttons button").prop("disabled", true) fe.pageHandler.loadPage(page_link.data('page-id'), page_link.attr('href'), false, validate_current_page); }, prev : function() { let curr_page_link = $('#'+fe.pageHandler.current_page+"-link"); //fe.pageHandler.loadPage('application_22544-fe_page_165-no_cache','/fe/answer_sheets/22544/page/165/edit'); return false; let page_link = curr_page_link .parents('.application_section') .prevAll() .filter(function() { return $(this).find('a.page_link:visible').length > 0 }) .first() .find('a.page_link'); $(".answer-page:visible div.buttons button").prop("disabled", true) fe.pageHandler.loadPage(page_link.data('page-id'), page_link.attr('href')); } }; $(document).on('ready turbo:load', function () { <% if Fe.bootstrap %> // http://stackoverflow.com/questions/18754020/bootstrap-3-with-jquery-validation-plugin $.validator.setDefaults({ errorElement: "span", errorClass: "help-block", highlight: function (element, errorClass, validClass) { $(element).closest('.form-group').addClass('has-error'); }, unhighlight: function (element, errorClass, validClass) { $(element).closest('.form-group').removeClass('has-error'); }, errorPlacement: function (error, element) { if (element.parent('.input-group').length || element.prop('type') === 'checkbox' || element.prop('type') === 'radio') { error.appendTo(element.closest('div:not(.field')); } else { error.insertAfter(element); } } }); <% end %> $(document).on('click', '.save_button', function() { fe.pageHandler.savePage($(this).closest('.answer-page'), true); }); $(document).on('click', '.reference_send_invite', function() { let el = this; let form_elements = $(el).closest('form').find('input:not(.dont_submit), textarea:not(.dont_submit), select:not(.dont_submit)'); let data = form_elements.serializeArray(); data.push({name: 'answer_sheet_type', value: answer_sheet_type}); $.ajax({url: $(el).attr('href'), data: data, dataType: 'script', type: 'POST', beforeSend: function (xhr) { $('body').trigger('ajax:loading', xhr); }, complete: function (xhr) { $('body').trigger('ajax:complete', xhr); }, error: function (xhr, status, error) { $('body').trigger('ajax:failure', [xhr, status, error]); } }); return false; }); $(document).on('focus', 'textarea[maxlength]', function() { let max = parseInt($(this).attr('maxlength')); $(this).parent().find('.charsRemaining').html('You have ' + (max - $(this).val().length) + ' characters remaining'); }).on('keyup', 'textarea[maxlength]', function(){ let max = parseInt($(this).attr('maxlength')); if($(this).val().length > max){ $(this).val($(this).val().substr(0, $(this).attr('maxlength'))); } $(this).parent().find('.charsRemaining').html('You have ' + (max - $(this).val().length) + ' characters remaining'); }).on('blur', 'textarea[maxlength]', function() { $(this).parent().find('.charsRemaining').html(''); }); $(document).on('click', ".conditional input, .conditional select", function() { fe.pageHandler.checkConditional($(this).closest('.conditional')); }); $(document).on('keyup', ".conditional input, .conditional select", function() { $(this).click(); }); $(document).on('blug', ".conditional input, .conditional select", function() { $(this).click(); }); $(document).on('change', ".conditional select", function() { $(this).click(); }); $(document).on('keyup', 'textarea[maxlength]', function() { let maxlength = parseInt($(this).attr('maxlength')); let remaining = maxlength - $(this).val().length; $('#'+$(this).attr('id')+'_count').val(remaining); }); $(document).on('click', 'a[disabled]', function(event) { event.preventDefault(); }); }); $(function() { fixGridColumnWidths(); setUpCalendars(); }); function updateTotal(id) { try { let total = 0; $(".col_" + id).each(function(index, el) { total += Number($(el).val().replace(',','')); }); $('#total_' + id).val(total).trigger('totalChanged'); } catch(e) { } } function submitToFrame(id, url) { let form = $('
') let csrf_token = $('meta[name=csrf-token]').attr('content'), csrf_param = $('meta[name=csrf-param]').attr('content'), dom_id = '#attachment_field_' + id, metadata_input = '', file_field = '