Additional changes, refactoring and improvements.

This commit is contained in:
Matej Zrimšek 2016-07-22 13:29:01 +02:00
parent 01aad1764c
commit 86880f0299
12 changed files with 235 additions and 237 deletions

View file

@ -158,24 +158,6 @@ $(document.body).ready(function () {
});
});
/*
* Truncate long strings where is necessary
*/
function truncateLongString( el, chars ) {
var input = $.trim(el.text());
if( input.length >= chars){
var newText = el.text().slice(0, chars);
for( var i = newText.length; i > 0; i--){
if(newText[i] === ' '){
newText = newText.slice(0, i);
break;
}
}
el.text(newText + '...');
}
}
$(document).ready(function(){
$('.tree-link a').each( function(){
truncateLongString( $(this), 30);

View file

@ -102,10 +102,10 @@
exports.directUpload = function (form, origId, signUrl, cb, cbErr, errKey) {
var fileInputs = $(form).find("input[type=file]");
var file = fileInputs.get(0).files[0];
var $fileInputs = $(form).find("input[type=file]");
var file = $fileInputs.get(0).files[0];
var isValid = checkFilesValidity(fileInputs);
var isValid = filesValidator($fileInputs);
if (!isValid) {
cbErr();

View file

@ -185,9 +185,6 @@ function formEditAjax($form) {
reorderCheckboxData(this);
});
})
.on("ajax:send", function(e, data) {
selectedTabIndex = $form.find("li.active").index() + 1;
})
.on("ajax:success", function(e, data) {
$(this).after(data.html);
var $new_step = $(this).next();
@ -212,23 +209,19 @@ function formEditAjax($form) {
var $form = $(this).next();
$(this).remove();
$errInput = $form.find(".form-group.has-error").first().find("input");
renderFormError($errInput);
formCallback($form);
initEditableHandsOnTable($form);
applyCancelCallBack();
formEditAjax($form);
tabsPropagateErrorClass($form);
//Rerender tables
$form.find("[data-role='step-hot-table']").each(function() {
renderTable($(this));
});
// Select the same tab pane as before
$form.find("ul.nav-tabs li.active").removeClass("active");
$form.find(".tab-content div.active").removeClass("active");
$form.find("ul.nav-tabs li:nth-child(" + selectedTabIndex + ")").addClass("active");
$form.find(".tab-content div:nth-child(" + selectedTabIndex + ")").addClass("active");
animateSpinner(null, false);
});
}
@ -262,10 +255,12 @@ function formNewAjax($form) {
var $form = $(this).next();
$(this).remove();
$errInput = $form.find(".form-group.has-error").first().find("input");
renderFormError($errInput);
formCallback($form);
formNewAjax($form);
applyCancelOnNew();
tabsPropagateErrorClass($form);
animateSpinner(null, false);
});
@ -583,89 +578,37 @@ $("[data-action='new-step']").on("ajax:success", function(e, data) {
});
});
function nameValidator(event) {
var $form = $(event.target.form);
var nameTooShort = $( "#step_name" ).val().length === 0;
var nameTooLong = $( "#step_name" ).val().length > 50;
var errMsg;
if (nameTooShort) {
errMsg = I18n.t("devise.names.not_blank");
} else if (nameTooLong) {
errMsg = I18n.t("devise.names.length_too_long", { max_length: 50 });
animateSpinner(null,false);
}
var hasErrors = !_.isUndefined(errMsg);
if (hasErrors) {
renderError($("#step_name"), errMsg);
}
return !hasErrors;
}
function checklistsValidator(event, editMode) {
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();
anyChecklistItemFilled = false;
// For every ckecklist item input
$(" .checklist-item-text", $(this)).each(function() {
if($(this).val()) {
anyChecklistItemFilled = true;
} else {
// Remove empty checklist item
$(this).closest("fieldset").remove();
}
})
if(checklistNameEmpty) {
if(anyChecklistItemFilled || editMode) {
// In edit mode, name can't be blank
var errMsg = I18n.t("devise.names.not_blank");
renderError($checklistNameInput, errMsg);
noErrors = false;
} else {
// Hide empty checklist
$(this).hide();
}
}
});
$(event.target).blur();
return noErrors;
}
// Needed because server-side validation failure clears locations of
// files to be uploaded and checklist's items etc. Also user
// experience is improved
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
// tables tab, so we can't use this function
clearBlankTables($form)
}
clearBlankFileForms($form);
// TODO File type check
var fileSizeValid = uploadFileSizeCheck(event);
var checklistsValid = checklistsValidator(event, editMode);
var nameValid = nameValidator(event);
function stepValidator(ev, editMode, forS3) {
var $form = $(ev.target.form);
if(fileSizeValid && checklistsValid && nameValid) {
$form.clear_form_errors();
$tables = $form.find(".nested_step_tables");
removeBlankExcelTables($tables, editMode);
removeBlankFileForms($form);
var $fileInputs = $form.find("input[type=file]");
var filesValid = filesValidator($fileInputs);
var $checklists = $form.find(".nested_step_checklists");
var checklistsValid = checklistsValidator($checklists, editMode);
var $nameInput = $form.find("#step_name");
var nameValid = nameValidator($nameInput);
if(filesValid && checklistsValid && nameValid) {
if(forS3) {
// Needed to redirect uploaded files to S3
startFileUpload(event, event.target);
startFileUpload(ev, ev.target);
} else {
// Files are saved locally
// Validations passed, so animate spinner for possible file uploading
// (startFileUpload already calls it)
animateSpinner();
}
} else {
// Don't submit form
ev.preventDefault();
}
}
@ -727,8 +670,6 @@ function startFileUpload(ev, btn) {
var inputPos = 0;
var inputPointer = 0;
$form.clear_form_errors();
clearBlankFileForms(form);
animateSpinner();
function processFile () {
@ -763,7 +704,7 @@ function startFileUpload(ev, btn) {
var $el = $(el);
$form.clear_form_errors();
renderError($el, assetError);
renderFormError($el, assetError);
} else {
tabsPropagateErrorClass($form);
}
@ -777,30 +718,3 @@ function startFileUpload(ev, btn) {
ev.preventDefault();
return noErrors;
}
// Remove empty file forms in step
function clearBlankFileForms(form) {
$(form).find("input[type='file']").each(function () {
if (!this.files[0]) {
$(this).closest("fieldset").remove();
}
});
}
// Remove empty tables in step
function clearBlankTables(form) {
$(form).find(".nested_step_tables").each(function () {
if (!$(this).find("td:not(:empty)").length) {
$(this).closest("fieldset").remove();
}
});
}
// This checks if the ctarget param exist in the
// rendered url and opens the comment tab
$(document).ready(function(){
if( getParam('ctarget') ){
var target = "#"+ getParam('ctarget');
$(target).find('a.comment-tab-link').click();
}
});

View file

@ -3,7 +3,7 @@ $("#new-result-asset").on("ajax:success", function(e, data) {
var $form = $(data.html);
$("#results").prepend($form);
$form.add_upload_file_size_check();
$form.files_validator();
formAjaxResultAsset($form);
// Cancel button
@ -30,7 +30,7 @@ function applyEditResultAssetCallback() {
$result.after($form);
$result.remove();
$form.add_upload_file_size_check();
$form.files_validator();
formAjaxResultAsset($form);
// Cancel button

View file

@ -1,27 +0,0 @@
function checkFilesValidity(fileInputs) {
function getFileError(file) {
if (!file) {
return ;
}
var size = parseInt(file.size);
<% sizeLimit = FILE_SIZE_LIMIT %>;
if (size > <%= sizeLimit.megabytes %>) {
return "<%= I18n.t 'general.file_size_exceeded', file_size: sizeLimit %>";
}
};
var isValid = true;
_.each(fileInputs, function(fileInput) {
var file = fileInput.files[0];
var assetError = getFileError(file);
var input = $(fileInput);
input.closest('.form-group').removeClass('has-error');
input.parent().find("[data-error='file-size']").remove();
if (assetError) {
renderError(input, assetError, "data-error='file-size'");
isValid = false;
}
});
return isValid;
}

View file

@ -0,0 +1,35 @@
$.fn.clear_form_errors = function() {
$(this).find('.nav.nav-tabs li').removeClass('has-error');
$(this).find('.form-group').removeClass('has-error');
$(this).find('span.help-block').remove();
};
$.fn.clear_form_fields = function() {
$(this).find("input")
.not("button")
.not('input[type="submit"], input[type="reset"], input[type="hidden"]')
.not('input[type="radio"]') // Leave out radios as this messes up Bootstrap btn-groups
.val('')
.removeAttr('checked')
.removeAttr('selected');
};
function removeBlankFileForms(form) {
$(form).find("input[type='file']").each(function () {
if (!this.files[0]) {
$(this).closest("fieldset").remove();
}
});
}
// Not the actual Excel tables, but are similar
function removeBlankExcelTables(tables, editMode) {
if(!editMode) {
// In edit mode, tables can't be blank
tables.each(function () {
if (!$(this).find("td:not(:empty)").length) {
$(this).closest("fieldset").remove();
}
});
}
}

View file

@ -36,73 +36,26 @@ $.fn.render_form_errors_no_clear = function(model_name, errors, input_group) {
});
};
$.fn.clear_form_errors = function() {
$(this).find('.nav.nav-tabs li').removeClass('has-error');
$(this).find('.form-group').removeClass('has-error');
$(this).find('span.help-block').remove();
};
$.fn.clear_form_fields = function() {
$(this).find("input")
.not("button")
.not('input[type="submit"], input[type="reset"], input[type="hidden"]')
.not('input[type="radio"]') // Leave out radios as this messes up Bootstrap btn-groups
.val('')
.removeAttr('checked')
.removeAttr('selected');
};
// Add JavaScript client-side upload file size checking
// 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);
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.
// Show error message and mark error input (if errMsg is defined)
// and, if present, mark and show the tab where the error occured,
// and go to the input, if it is the most upper one or if errMsg is
// undefined
// 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, errAttributes) {
var $errMsgSpan = $(nameInput).next(".help-block");
if(!$errMsgSpan.length) {
errAttributes = (_.isUndefined(errAttributes)) ? "" : " " + errAttributes;
function renderFormError(nameInput, errMsg, errAttributes) {
if(!_.isUndefined(errMsg)) {
var $errMsgSpan = $(nameInput).next(".help-block");
errAttributes = _.isUndefined(errAttributes) ? "" : " " + errAttributes;
if (!$errMsgSpan.length) {
$(nameInput).closest(".form-group").addClass("has-error");
}
$(nameInput).after("<span class='help-block'" + errAttributes + ">" + errMsg + "</span>");
$(nameInput).closest(".form-group").addClass("has-error");
} else {
$errMsgSpan.html(errMsg);
}
$form = $(nameInput).closest("form");
$tab = $(nameInput).closest(".tab-pane");
if($tab.length) {
if ($tab.length) {
tabsPropagateErrorClass($form);
$parent = $tab;
} else {
@ -110,27 +63,27 @@ function renderError(nameInput, errMsg, errAttributes) {
}
// Focus and scroll to the error if it is the first (most upper) one
if($parent.find(".form-group.has-error").length === 1) {
if ($parent.find(".form-group.has-error").length === 1 || _.isUndefined(errMsg)) {
goToFormElement(nameInput);
}
event.preventDefault();
}
// 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 any of tabs (if exist) has errors, mark parent tab
// navigation link and show the tab (if not already)
function tabsPropagateErrorClass($form) {
var $contents = $form.find("div.tab-pane");
_.each($contents, function(tab) {
var $tab = $(tab);
var $errorFields = $tab.find(".has-error");
if ($errorFields.length > 0) {
if ($errorFields.length) {
var id = $tab.attr("id");
var navLink = parent.find("a[href='#" + id + "'][data-toggle='tab']");
if (navLink.parent().length > 0) {
var navLink = $form.find("a[href='#" + id + "'][data-toggle='tab']");
if (navLink.parent().length) {
navLink.parent().addClass("has-error");
}
}
});
$(".nav-tabs .has-error:first:not(.active) > a", parent).tab("show");
$(".nav-tabs .has-error:first:not(.active) > a", $form).tab("show");
}

View file

@ -1,13 +1,13 @@
// Scroll to and focus on element
function goToFormElement(el) {
function goToFormElement(input) {
$("html, body").animate(
{
scrollTop: $(el).closest(".form-group").offset().top
scrollTop: $(input).closest(".form-group").offset().top
- ($(".navbar-fixed-top").outerHeight(true)
+ $(".navbar-secondary").outerHeight(true)
+ $(".alert-dismissable").outerHeight(true))
},
"slow",
function() { $(el).focus(); }
function() { $(input).focus(); }
);
}

View file

@ -0,0 +1,25 @@
// Truncate long strings where is necessary
function truncateLongString( el, chars ) {
var input = $.trim(el.text());
if( input.length >= chars){
var newText = el.text().slice(0, chars);
for( var i = newText.length; i > 0; i--){
if(newText[i] === ' '){
newText = newText.slice(0, i);
break;
}
}
el.text(newText + '...');
}
}
// Usefull for converting locals messages to form
// format (i.e. lower cased capital and no dot)
String.prototype.strToFormFormat = function() {
var length = this.length;
if (this[length - 1] === ".") {
length -= 1;
}
return this.charAt(0).toLowerCase() + this.slice(1, length);
}

View file

@ -0,0 +1,116 @@
function nameValidator($nameInput) {
var nameTooShort = $nameInput.val().length === 0;
var nameTooLong = $nameInput.val().length > 50;
var errMsg;
if (nameTooShort) {
errMsg = I18n.t("devise.names.not_blank");
} else if (nameTooLong) {
errMsg = I18n.t("devise.names.length_too_long", { max_length: 50 });
}
var hasErrors = !_.isUndefined(errMsg);
if (hasErrors) {
renderFormError($nameInput, errMsg);
}
return !hasErrors;
}
function checklistsValidator(checklists, editMode) {
var noErrors = true;
// For every visible (i.e. not removed) checklist
checklists.each(function() {
$checklist = $(this);
if ($checklist.css('display') != 'none') {
var $checklistNameInput = $checklist.find(".checklist_name");
anyChecklistItemFilled = false;
// For every ckecklist item input
$(" .checklist-item-text", $checklist).each(function() {
$checklistItemInput = $(this);
if ($checklistItemInput.val()) {
anyChecklistItemFilled = true;
} else {
// Remove empty checklist item input
$checklistItemInput.closest("fieldset").remove();
}
})
if (!$checklistNameInput.val()) {
if (anyChecklistItemFilled || editMode) {
// In edit mode, checklist's name can't be blank
var errMsg = I18n.t("devise.names.not_blank");
renderFormError($checklistNameInput, errMsg);
noErrors = false;
} else {
// Hide empty checklist (remove would break logic)
$checklist.hide();
}
}
}
});
return noErrors;
}
// Add JavaScript client-side upload file checking
// Callback function can be provided to be called
// any time at least one file size is not valid
$.fn.files_validator = function(callback) {
var $form = $(this);
if ($form.length) {
$form.submit(function (ev) {
var $fileInputs = $form.find("input[type=file]");
filesValidator($fileInputs, callback);
});
}
};
function filesValidator(fileInputs, callback) {
var filesSizeValid = true;
if (fileInputs.length) {
var filesSizeValid = filesSizeValidator(fileInputs);
// TODO File content check
if (!filesSizeValid && callback) {
callback();
}
}
return filesSizeValid;
}
function filesSizeValidator(fileInputs) {
function getFileTooBigError(file) {
if (!file) {
return ;
}
var size = parseInt(file.size);
<% sizeLimit = FILE_SIZE_LIMIT %>;
if (size > <%= sizeLimit.megabytes %>) {
return "<%= I18n.t 'general.file_size_exceeded', file_size: sizeLimit %>".strToFormFormat();
}
};
// Check if any file exceeds allowed size limit
var fileSizeValid = true;
_.each(fileInputs, function(fileInput) {
var file = fileInput.files[0];
var assetError = getFileTooBigError(file);
var input = $(fileInput);
if (assetError) {
renderFormError(input, assetError, "data-error='file-size'");
fileSizeValid = false;
}
});
if(fileSizeValid) {
// Check if there is enough free space for the files
fileSizeValid = enaughSpaceValidator(fileInputs);
}
return fileSizeValid;
}
// Overriden in billing module for checking
// whether enough organization space is free
function enaughSpaceValidator(fileInputs) {
return true;
}

View file

@ -63,7 +63,7 @@ forms
});
// Add upload file size checking
$("form[data-for='avatar']").add_upload_file_size_check();
$("form[data-for='avatar']").files_validator();
// S3 direct uploading
function startFileUpload(ev, btn) {
@ -103,7 +103,7 @@ function startFileUpload(ev, btn) {
var $el = $form.find("input[type=file]");
$form.clear_form_errors();
renderError($el, avatarError);
renderFormError($el, avatarError);
}
}, "avatar");

View file

@ -19,8 +19,8 @@ en:
title: "Forgot your password?"
submit: "Send me reset password instructions"
names:
not_blank: "Name can't be blank"
length_too_long: "Name is too long (maximum is %{max_length} characters)"
not_blank: "name can't be blank"
length_too_long: "name is too long (maximum is %{max_length} characters)"
registrations:
password_changed: "Password successfully updated."
sessions: