From 01aad1764c61626dc894c8fa00eab4de6a8cfc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Zrim=C5=A1ek?= Date: Mon, 18 Jul 2016 13:16:41 +0200 Subject: [PATCH] Some hotfixes to previous commit. Client-side file validation added (except file type validation). Some refactoring. --- app/assets/javascripts/direct-upload.js | 105 ++++++++-------- app/assets/javascripts/my_modules/results.js | 8 +- app/assets/javascripts/protocols/steps.js | 70 +++++------ .../sitewide/file_upload_handling.js.erb | 5 +- .../javascripts/sitewide/form_errors.js | 117 +++++++++--------- app/assets/javascripts/sitewide/gestures.js | 13 ++ .../javascripts/users/registrations/edit.js | 9 +- app/views/steps/_edit.html.erb | 6 +- app/views/steps/_new.html.erb | 6 +- 9 files changed, 172 insertions(+), 167 deletions(-) create mode 100644 app/assets/javascripts/sitewide/gestures.js diff --git a/app/assets/javascripts/direct-upload.js b/app/assets/javascripts/direct-upload.js index db48e776e..353211ae6 100644 --- a/app/assets/javascripts/direct-upload.js +++ b/app/assets/javascripts/direct-upload.js @@ -14,7 +14,7 @@ var size; var offsetX = 0; var offsetY = 0; - + if (this.width > this.height) { size = this.height; offsetX = (this.width - this.height) / 2; @@ -46,7 +46,6 @@ data.push("file_name=" + file.name); data.push("file_size=" + file.size); data.push(csrfParam + "=" + encodeURIComponent(csrfToken)); - if (origId) { data.push("asset_id=" + origId); @@ -110,57 +109,61 @@ if (!isValid) { cbErr(); - return; + } else { + fetchUploadSignature(file, origId, signUrl, function (data) { + + function processPost(error) { + var postData = posts[postPosition]; + + if (error) { + var errObj = {}; + errKey = errKey|| "asset.file"; + errObj[errKey] = [error]; + + cbErr(errObj); + isValid = false; + return; + } + if (!postData) { + cb(data.asset_id); + isValid = false; + return; + } + + postData.fileName = file.name; + postPosition += 1; + var styleSize; + + if (postData.style_option) { + styleSize = parseStyleOption(postData.style_option); + + generateThumbnail(file, postData.mime_type, styleSize.width, + styleSize.height, function (blob) { + + postData.file = blob; + uploadData(postData, processPost); + }); + + } else { + postData.file = file; + uploadData(postData, processPost); + } + } + + if (!data || data.status === 'error') { + cbErr(data && data.errors); + isValid = false; + return; + } + + var posts = data.posts; + var postPosition = 0; + + processPost(); + }); } - fetchUploadSignature(file, origId, signUrl, function (data) { - - function processPost(error) { - var postData = posts[postPosition]; - - if (error) { - var errObj = {}; - errKey = errKey|| "asset.file"; - errObj[errKey] = [error]; - - cbErr(errObj); - return; - } - if (!postData) { - cb(data.asset_id); - return; - } - - postData.fileName = file.name; - postPosition += 1; - var styleSize; - - if (postData.style_option) { - styleSize = parseStyleOption(postData.style_option); - - generateThumbnail(file, postData.mime_type, styleSize.width, - styleSize.height, function (blob) { - - postData.file = blob; - uploadData(postData, processPost); - }); - - } else { - postData.file = file; - uploadData(postData, processPost); - } - } - - if (!data || data.status === 'error') { - cbErr(data && data.errors); - return; - } - - var posts = data.posts; - var postPosition = 0; - - processPost(); - }); + return isValid; }; }(this)); diff --git a/app/assets/javascripts/my_modules/results.js b/app/assets/javascripts/my_modules/results.js index 8ace1793f..d694d11fe 100644 --- a/app/assets/javascripts/my_modules/results.js +++ b/app/assets/javascripts/my_modules/results.js @@ -311,10 +311,10 @@ function startFileUpload(ev, btn) { var origAssetId = assetInput ? assetInput.value : null; var url = '/asset_signature.json'; - animateSpinner(); $form.clear_form_errors(); + animateSpinner(); - directUpload(form, origAssetId, url, function (assetId) { + var noErrors = directUpload(form, origAssetId, url, function (assetId) { // edit mode - input field has to be removed if (assetInput) { assetInput.value = assetId; @@ -334,7 +334,11 @@ function startFileUpload(ev, btn) { showResultFormErrors($form, errors); }); + if(!noErrors) { + animateSpinner(null, false); + } ev.preventDefault(); + return noErrors; } // This checks if the ctarget param exist in the diff --git a/app/assets/javascripts/protocols/steps.js b/app/assets/javascripts/protocols/steps.js index f3576db37..36f3c2be5 100644 --- a/app/assets/javascripts/protocols/steps.js +++ b/app/assets/javascripts/protocols/steps.js @@ -162,11 +162,6 @@ function formCallback($form) { } }); - // Add asset validation - $form.add_upload_file_size_check(function() { - tabsPropagateErrorClass($form); - }); - // Add hidden fields for tables $form.submit(function(){ $(this).find("[data-role='editable-table']").each(function() { @@ -589,7 +584,7 @@ $("[data-action='new-step']").on("ajax:success", function(e, data) { }); function nameValidator(event) { - var form = $(event.target.form); + var $form = $(event.target.form); var nameTooShort = $( "#step_name" ).val().length === 0; var nameTooLong = $( "#step_name" ).val().length > 50; var errMsg; @@ -602,20 +597,19 @@ function nameValidator(event) { var hasErrors = !_.isUndefined(errMsg); if (hasErrors) { - renderError($("#step_name"), errMsg, form); + renderError($("#step_name"), errMsg); } return !hasErrors; } function checklistsValidator(event, editMode) { - var form = event.target.form; - $(form).clear_form_errors(); + var $form = $(event.target.form); var noErrors = true; // For every visible (i.e. not removed) checklist - $(form).find(".nested_step_checklists[style!='display: none']").each(function() { - var checklistNameInput = $(this).find(".checklist_name"); - var checklistNameEmpty = !checklistNameInput.val(); + $form.find(".nested_step_checklists[style!='display: none']").each(function() { + var $checklistNameInput = $(this).find(".checklist_name"); + var checklistNameEmpty = !$checklistNameInput.val(); anyChecklistItemFilled = false; // For every ckecklist item input @@ -632,7 +626,7 @@ function checklistsValidator(event, editMode) { if(anyChecklistItemFilled || editMode) { // In edit mode, name can't be blank var errMsg = I18n.t("devise.names.not_blank"); - renderError(checklistNameInput, errMsg, $(form)); + renderError($checklistNameInput, errMsg); noErrors = false; } else { // Hide empty checklist @@ -648,28 +642,30 @@ function checklistsValidator(event, editMode) { // Needed because server-side validation failure clears locations of // files to be uploaded and checklist's items etc. Also user // experience is improved -function localStepValidator(event, editMode) { +function stepValidator(event, editMode, forS3) { + var $form = $(event.target.form); + $form.clear_form_errors(); if(!editMode) { // Most td's disappear when editing step and not pressing on - // edit tab, so we can't use this function - clearBlankTables(event.target.form) + // tables tab, so we can't use this function + clearBlankTables($form) } - clearBlankFileForms(event.target.form); + clearBlankFileForms($form); + // TODO File type check + var fileSizeValid = uploadFileSizeCheck(event); var checklistsValid = checklistsValidator(event, editMode); var nameValid = nameValidator(event); - var noErrors = checklistsValid && nameValid; - if(noErrors) { - // Validations passed, so animate spinner for possible file - // uploading - animateSpinner(); - } - return noErrors; -} - -function S3StepValidator(event, editMode) { - if(localStepValidator(event, editMode)) { - startFileUpload(event, event.target); + if(fileSizeValid && checklistsValid && nameValid) { + if(forS3) { + // Needed to redirect uploaded files to S3 + startFileUpload(event, event.target); + } else { + // Files are saved locally + // Validations passed, so animate spinner for possible file uploading + // (startFileUpload already calls it) + animateSpinner(); + } } } @@ -731,9 +727,9 @@ function startFileUpload(ev, btn) { var inputPos = 0; var inputPointer = 0; - animateSpinner(); $form.clear_form_errors(); clearBlankFileForms(form); + animateSpinner(); function processFile () { var fileInput = fileInputs.get(inputPos); @@ -743,17 +739,16 @@ function startFileUpload(ev, btn) { if (!fileInput) { btn.onclick = null; $(btn).click(); - return; + return false; } - directUpload(form, null, url, function (assetId) { + return directUpload(form, null, url, function (assetId) { fileInput.type = "hidden"; fileInput.name = fileInput.name.replace("[file]", "[id]"); fileInput.value = assetId; inputPointer -= 1; processFile(); - }, function (errors) { var assetError; @@ -768,16 +763,19 @@ function startFileUpload(ev, btn) { var $el = $(el); $form.clear_form_errors(); - $el.closest(".form-group").addClass("has-error"); - $el.parent().append("" + assetError + ""); + renderError($el, assetError); } else { tabsPropagateErrorClass($form); } }); } - processFile(); + var noErrors = processFile(); + if(!noErrors) { + animateSpinner(null, false); + } ev.preventDefault(); + return noErrors; } // Remove empty file forms in step diff --git a/app/assets/javascripts/sitewide/file_upload_handling.js.erb b/app/assets/javascripts/sitewide/file_upload_handling.js.erb index 61f48b524..b445a9477 100644 --- a/app/assets/javascripts/sitewide/file_upload_handling.js.erb +++ b/app/assets/javascripts/sitewide/file_upload_handling.js.erb @@ -19,10 +19,7 @@ function checkFilesValidity(fileInputs) { input.closest('.form-group').removeClass('has-error'); input.parent().find("[data-error='file-size']").remove(); if (assetError) { - input.closest('.form-group').addClass('has-error'); - input.parent().append( - "" + assetError + "" - ); + renderError(input, assetError, "data-error='file-size'"); isValid = false; } }); diff --git a/app/assets/javascripts/sitewide/form_errors.js b/app/assets/javascripts/sitewide/form_errors.js index 88095a07c..9566bf26a 100644 --- a/app/assets/javascripts/sitewide/form_errors.js +++ b/app/assets/javascripts/sitewide/form_errors.js @@ -14,7 +14,6 @@ $.fn.render_form_errors_input_group = function(model_name, errors) { $.fn.render_form_errors_no_clear = function(model_name, errors, input_group) { var form = $(this); - var firstErr = true; $.each(errors, function(field, messages) { input = $(_.filter(form.find('input, select, textarea'), function(el) { var name = $(el).attr('name'); @@ -34,17 +33,6 @@ $.fn.render_form_errors_no_clear = function(model_name, errors, input_group) { } else { input.parent().append(error_text); } - - if(firstErr) { - // Focus and scroll to the first error - input.focus(); - firstErr = false; - $('html, body').animate({ - scrollTop: input.closest(".form-group").offset().top - - ($(".navbar-fixed-top").outerHeight(true) - + $(".navbar-secondary").outerHeight(true)) - }, 2000); - } }); }; @@ -68,74 +56,81 @@ $.fn.clear_form_fields = function() { // Callback function can be provided to be called // any time at least one file size is too large $.fn.add_upload_file_size_check = function(callback) { - var form = $(this); + var $form = $(this); - if (form.length && form.length > 0) { - form.submit(function (ev) { - var fileInputs = $(this).find("input[type='file']"); - if (fileInputs.length && fileInputs.length > 0) { - var isValid = checkFilesValidity(fileInputs); - - if (!isValid) { - // Don't submit form - ev.preventDefault(); - ev.stopPropagation(); - - if (callback) { - callback(); - } - - return false; - } - } + if ($form.length && $form.length > 0) { + $form.submit(function (ev) { + uploadFileSizeCheck(ev, callback); }); } }; +function uploadFileSizeCheck(ev, callback) { + var $fileInputs = $(ev.target.form).find("input[type='file']"); + if ($fileInputs.length && $fileInputs.length > 0) { + var isValid = checkFilesValidity($fileInputs); + + if (!isValid) { + // Don't submit form + ev.preventDefault(); + ev.stopPropagation(); + + if (callback) { + callback(); + } + + return false; + } + } + return true; +} + // Show error message and mark error element and, if present, mark // and show the tab where the error occured. // NOTE: Similar to $.fn.render_form_errors, except here we process // one error at a time, which is not read from the form but is // specified manually. -function renderError(nameInput, errMsg, form) { - var errMsgSpan = nameInput.next(".help-block"); - if(!errMsgSpan.length) { - nameInput.after("" + errMsg + ""); - nameInput.closest(".form-group").addClass("has-error"); +function renderError(nameInput, errMsg, errAttributes) { + var $errMsgSpan = $(nameInput).next(".help-block"); + if(!$errMsgSpan.length) { + errAttributes = (_.isUndefined(errAttributes)) ? "" : " " + errAttributes; + $(nameInput).after("" + errMsg + ""); + $(nameInput).closest(".form-group").addClass("has-error"); } else { - errMsgSpan.html(errMsg); + $errMsgSpan.html(errMsg); + } + + $form = $(nameInput).closest("form"); + $tab = $(nameInput).closest(".tab-pane"); + if($tab.length) { + tabsPropagateErrorClass($form); + $parent = $tab; + } else { + $parent = $form; } - tabsPropagateErrorClass($(form)); // Focus and scroll to the error if it is the first (most upper) one - if($(form).find(".form-group.has-error").length === 1) { - nameInput.focus(); - $('html, body').animate({ - scrollTop: nameInput.closest(".form-group").offset().top - - ($(".navbar-fixed-top").outerHeight(true) - + $(".navbar-secondary").outerHeight(true)) - }, 2000); + if($parent.find(".form-group.has-error").length === 1) { + goToFormElement(nameInput); } event.preventDefault(); } -// If any of tabs has errors, add has-error class to -// parent tab navigation link +// If any of tabs (if exist) has errors, add has-error class to +// parent tab navigation link and show the tab (if not already) function tabsPropagateErrorClass(parent) { - var contents = parent.find("div.tab-pane"); - if(contents.length) { - _.each(contents, function(tab) { - var $tab = $(tab); - var errorFields = $tab.find(".has-error"); - if (errorFields.length > 0) { - var id = $tab.attr("id"); - var navLink = parent.find("a[href='#" + id + "'][data-toggle='tab']"); - if (navLink.parent().length > 0) { - navLink.parent().addClass("has-error"); - } + var $contents = parent.find("div.tab-pane"); + _.each($contents, function(tab) { + var $tab = $(tab); + var $errorFields = $tab.find(".has-error"); + if ($errorFields.length > 0) { + var id = $tab.attr("id"); + var navLink = parent.find("a[href='#" + id + "'][data-toggle='tab']"); + if (navLink.parent().length > 0) { + navLink.parent().addClass("has-error"); } - }); - $(".nav-tabs .has-error:first > a", parent).tab("show"); - } + } + }); + $(".nav-tabs .has-error:first:not(.active) > a", parent).tab("show"); } diff --git a/app/assets/javascripts/sitewide/gestures.js b/app/assets/javascripts/sitewide/gestures.js new file mode 100644 index 000000000..f897ff5a1 --- /dev/null +++ b/app/assets/javascripts/sitewide/gestures.js @@ -0,0 +1,13 @@ +// Scroll to and focus on element +function goToFormElement(el) { + $("html, body").animate( + { + scrollTop: $(el).closest(".form-group").offset().top + - ($(".navbar-fixed-top").outerHeight(true) + + $(".navbar-secondary").outerHeight(true) + + $(".alert-dismissable").outerHeight(true)) + }, + "slow", + function() { $(el).focus(); } + ); +} \ No newline at end of file diff --git a/app/assets/javascripts/users/registrations/edit.js b/app/assets/javascripts/users/registrations/edit.js index 0b77b6636..7e02bceb1 100644 --- a/app/assets/javascripts/users/registrations/edit.js +++ b/app/assets/javascripts/users/registrations/edit.js @@ -75,7 +75,7 @@ function startFileUpload(ev, btn) { $form.clear_form_errors(); animateSpinner($form); - directUpload(form, null, url, function (assetId) { + var noErrors = directUpload(form, null, url, function (assetId) { var file = fileInput.files[0]; fileInput.type = "hidden"; fileInput.name = fileInput.name.replace("[avatar]", "[avatar_file_name]"); @@ -103,12 +103,15 @@ function startFileUpload(ev, btn) { var $el = $form.find("input[type=file]"); $form.clear_form_errors(); - $el.closest(".form-group").addClass("has-error"); - $el.parent().append("" + avatarError + ""); + renderError($el, avatarError); } }, "avatar"); + if(!noErrors) { + animateSpinner(null, false); + } ev.preventDefault(); + return noErrors; } diff --git a/app/views/steps/_edit.html.erb b/app/views/steps/_edit.html.erb index 95bdd860e..d40cc6106 100644 --- a/app/views/steps/_edit.html.erb +++ b/app/views/steps/_edit.html.erb @@ -4,11 +4,7 @@
<%= render partial: "empty_step.html.erb", locals: {step: @step, f: f} %>
- <% if direct_upload %> - <%= f.submit t("protocols.steps.edit.edit_step"), class: 'btn btn-primary', onclick: 'S3StepValidator(event, true);' %> - <% else %> - <%= f.submit t("protocols.steps.edit.edit_step"), class: 'btn btn-primary', onclick: 'localStepValidator(event, true);' %> - <% end %> + <%= f.submit t("protocols.steps.edit.edit_step"), class: 'btn btn-primary', onclick: "stepValidator(event, true, #{direct_upload});" %> <%= t("general.cancel")%> diff --git a/app/views/steps/_new.html.erb b/app/views/steps/_new.html.erb index 3e1bdde7d..b91b4db6b 100644 --- a/app/views/steps/_new.html.erb +++ b/app/views/steps/_new.html.erb @@ -4,11 +4,7 @@
<%= render partial: "empty_step.html.erb", locals: {step: @step, f: f} %>
- <% if direct_upload %> - <%= f.submit t("protocols.steps.new.add_step"), class: 'btn btn-primary', onclick: 'S3StepValidator(event, false);' %> - <% else %> - <%= f.submit t("protocols.steps.new.add_step"), id: "create-step", class: 'btn btn-primary', onclick: 'localStepValidator(event, false);' %> - <% end %> + <%= f.submit t("protocols.steps.new.add_step"), class: 'btn btn-primary', onclick: "stepValidator(event, false, #{direct_upload});" %>