Word wrapping and truncating added to whole application [fixes SCI-458]. Fixed some bugs which were related to long/too long text and validations. Modified parts of application to accomodate for longer text limits.

This commit is contained in:
Matej Zrimšek 2016-09-21 15:54:12 +02:00
parent c6a23aab98
commit 234918a76d
24 changed files with 118 additions and 209 deletions

View file

@ -3,6 +3,8 @@
// Create ajax hook on given 'element', which should return modal with 'id' =>
// show that modal
function initializeModal(element, id){
// Initialize new experiment modal listener
$(element)
.on("ajax:beforeSend", function(){
animateSpinner();
@ -66,7 +68,7 @@
// Initialize no description edit link
function initEditNoDescription(){
var modal = "#edit-experiment-modal-";
$.each($(".no-description-experiment"), function(){
$.each($(".experiment-no-description a"), function(){
var id = modal + $(this).data("id");
initializeModal($(this), id);
});

View file

@ -30,7 +30,7 @@ function initEditDescription() {
})
.on("ajax:error", function(ev2, data2, status2) {
// Display errors if needed
$(this).renderFormErrors("my_module", data.responseJSON);
$(this).renderFormErrors("my_module", data2.responseJSON);
});
// Show modal
@ -40,11 +40,6 @@ function initEditDescription() {
// TODO
});
editDescriptionModalSubmitBtn.on("click", function() {
// Submit the form inside the modal
editDescriptionModalBody.find("form").submit();
});
editDescriptionModal.on("hidden.bs.modal", function() {
editDescriptionModalBody.find("form").off("ajax:success ajax:error");
editDescriptionModalBody.html("");
@ -81,7 +76,7 @@ function bindEditDueDateAjax() {
})
.on("ajax:error", function(ev2, data2, status2) {
// Display errors if needed
$(this).renderFormErrors("my_module", data.responseJSON);
$(this).renderFormErrors("my_module", data2.responseJSON);
});
// Open modal

View file

@ -438,26 +438,6 @@ function toggleCanvasEvents(activate) {
hammertime.get('pinch').set({ enable: canHammer });
}
/**
* Validate the module/module group name.
* @param The value to be validated.
* @return 0 if valid; -1 if length too small/large; -2 if
* it contains invalid characters.
*/
function validateName(val) {
var result = NAME_VALID;
if (_.isUndefined(val) ||
val.length < 2 ||
val.length > 50) {
result = NAME_LENGTH_ERROR;
} else if (val.indexOf(SUBMIT_FORM_NAME_SEPARATOR) != -1) {
result = NAME_INVALID_CHARACTERS_ERROR;
} else if (/^\s+$/.test(val)){
result = NAME_WHITESPACES_ERROR;
}
return result;
}
/**
* Gets or sets the left CSS position of the element.
* @param el - The element.
@ -648,7 +628,7 @@ function bindFullZoomAjaxTabs() {
})
.on("ajax:error", function(ev2, data2, status2) {
// Display errors if needed
$(this).renderFormErrors("my_module", data.responseJSON);
$(this).renderFormErrors("my_module", data2.responseJSON);
});
// Disable canvas dragging events
@ -914,7 +894,7 @@ function bindEditDueDateAjax() {
})
.on("ajax:error", function(ev2, data2, status2) {
// Display errors if needed
$(this).renderFormErrors("my_module", data.responseJSON);
$(this).renderFormErrors("my_module", data2.responseJSON);
});
// Disable canvas dragging events
@ -1528,40 +1508,15 @@ function bindNewModuleAction(gridDistX, gridDistY) {
collided = false;
}
function handleNewNameConfirm() {
function handleNewNameConfirm(ev) {
var input = $("#new-module-name-input");
var error = false;
var message;
// Validate module name
var res = validateName(input.val());
if (res === NAME_LENGTH_ERROR) {
error = true;
message = modal.find(".module-name-length-error").html();
} else if (res === NAME_INVALID_CHARACTERS_ERROR) {
error = true;
message = modal.find(".module-name-invalid-error").html();
} else if (res === NAME_WHITESPACES_ERROR) {
error = true;
message = modal.find(".module-name-whitespaces-error").html();
}
if (error) {
// Style the form so it displays error
input.parent().addClass("has-error");
input.parent().find("span.help-block").remove();
var errorSpan = document.createElement("span");
$(errorSpan)
.addClass("help-block")
.html(message)
.appendTo(input.parent());
return false;
} else {
var moduleNameValid = textValidator(ev, input, TextLimitEnum.NAME_MIN_LENGTH, TextLimitEnum.NAME_MAX_LENGTH, true);
if (moduleNameValid) {
// Set the "clicked" property to true
modal.data("submit", "true");
return true;
}
return moduleNameValid;
}
var newModuleBtn = $("#canvas-new-module");
@ -1583,17 +1538,12 @@ function bindNewModuleAction(gridDistX, gridDistY) {
});
// Bind the confirm button on modal
modal.find("button[data-action='confirm']").on("click", function(event) {
if (!handleNewNameConfirm()) {
// Prevent modal from closing if errorous form
event.preventDefault();
event.stopPropagation();
return false;
}
modal.find("button[data-action='confirm']").on("click", function(ev) {
handleNewNameConfirm(ev);
});
// Also, bind on modal window open & close
modal.on("show.bs.modal", function(event) {
modal.on("show.bs.modal", function(ev) {
// Clear input
$(this).removeData("submit");
$(this).find("#new-module-name-input").val("");
@ -1605,7 +1555,7 @@ function bindNewModuleAction(gridDistX, gridDistY) {
// Bind onto input keypress (to prevent form from being submitted)
$(this).find("#new-module-name-input").keydown(function(ev) {
if (ev.keyCode == 13) {
if (handleNewNameConfirm()) {
if (handleNewNameConfirm(ev)) {
// Close modal
modal.modal("hide");
}
@ -1646,40 +1596,15 @@ function bindNewModuleAction(gridDistX, gridDistY) {
}
function initEditModules() {
function handleRenameConfirm(modal) {
function handleRenameConfirm(modal, ev) {
var input = modal.find("#edit-module-name-input");
var moduleId = modal.attr("data-module-id");
var moduleEl = $("#" + moduleId);
var error = false;
var message;
var newName;
// Validate module name
newName = input.val();
var res = validateName(newName);
if (res === NAME_LENGTH_ERROR) {
error = true;
message = modal.find(".module-name-length-error").html();
} else if (res === NAME_INVALID_CHARACTERS_ERROR) {
error = true;
message = modal.find(".module-name-invalid-error").html();
} else if (res === NAME_WHITESPACES_ERROR) {
error = true;
message = modal.find(".module-name-whitespaces-error").html();
}
if (error) {
// Style the form so it displays error
input.parent().addClass("has-error");
input.parent().find("span.help-block").remove();
var errorSpan = document.createElement("span");
$(errorSpan)
.addClass("help-block")
.html(message)
.appendTo(input.parent());
} else {
var moduleNameValid = textValidator(ev, input, TextLimitEnum.NAME_MIN_LENGTH, TextLimitEnum.NAME_MAX_LENGTH, true);
if (moduleNameValid) {
var newName = input.val();
var moduleId = modal.attr("data-module-id");
var moduleEl = $("#" + moduleId);
// Update the module's name in GUI
moduleEl.attr("data-module-name", newName);
moduleEl.find(".panel-heading .panel-title").html(newName);
@ -1722,7 +1647,7 @@ function initEditModules() {
input.keydown(function(ev) {
if (ev.keyCode == 13) {
// "Submit" modal
handleRenameConfirm(modal);
handleRenameConfirm(modal, ev);
// In any case, prevent form submission
ev.preventDefault();
@ -1747,9 +1672,9 @@ function initEditModules() {
});
// Bind the confirm button on modal
$("#modal-edit-module").find("button[data-action='confirm']").on("click", function(event) {
$("#modal-edit-module").find("button[data-action='confirm']").on("click", function(ev) {
var modal = $(this).closest(".modal");
handleRenameConfirm(modal);
handleRenameConfirm(modal, ev);
});
}
@ -1778,40 +1703,15 @@ editModuleHandler = function(ev) {
* Initialize editing of module groups.
*/
function initEditModuleGroups() {
function handleRenameConfirm(modal) {
function handleRenameConfirm(modal, ev) {
var input = modal.find("#edit-module-group-name-input");
var moduleId = modal.attr("data-module-id");
var moduleEl = $("#" + moduleId);
var error = false;
var message;
var newModuleGroupName;
// Validate module name
newModuleGroupName = input.val();
var res = validateName(newModuleGroupName);
if (res === NAME_LENGTH_ERROR) {
error = true;
message = modal.find(".module-name-length-error").html();
} else if (res === NAME_INVALID_CHARACTERS_ERROR) {
error = true;
message = modal.find(".module-name-invalid-error").html();
} else if (res === NAME_WHITESPACES_ERROR) {
error = true;
message = modal.find(".module-name-whitespaces-error").html();
}
if (error) {
// Style the form so it displays error
input.parent().addClass("has-error");
input.parent().find("span.help-block").remove();
var errorSpan = document.createElement("span");
$(errorSpan)
.addClass("help-block")
.html(message)
.appendTo(input.parent());
} else {
var moduleNameValid = textValidator(ev, input, TextLimitEnum.REQUIRED, TextLimitEnum.NAME_MAX_LENGTH, true);
if (moduleNameValid) {
var newModuleGroupName = input.val();
var moduleId = modal.attr("data-module-id");
var moduleEl = $("#" + moduleId);
// Update the module group name for all modules
// currently in the module group
var ids = connectedComponents(graph, moduleEl.attr("id"));
@ -1840,7 +1740,7 @@ function initEditModuleGroups() {
input.keydown(function(ev) {
if (ev.keyCode == 13) {
// "Submit" modal
handleRenameConfirm(modal);
handleRenameConfirm(modal, ev);
// In any case, prevent form submission
ev.preventDefault();
@ -1864,9 +1764,9 @@ function initEditModuleGroups() {
});
// Bind the confirm button on modal
$("#modal-edit-module-group").find("button[data-action='confirm']").on("click", function(event) {
$("#modal-edit-module-group").find("button[data-action='confirm']").on("click", function(ev) {
var modal = $(this).closest(".modal");
handleRenameConfirm(modal);
handleRenameConfirm(modal, ev);
});
}

View file

@ -203,17 +203,11 @@ function formEditAjax($form) {
});
})
.on("ajax:error", function(e, xhr, status, error) {
$(this).after(xhr.responseJSON.html);
var $form = $(this).next();
$(this).remove();
$errInput = $form.find(".form-group.has-error").first().find("input");
renderFormError(e, $errInput);
$form.renderFormErrors('step', xhr.responseJSON, true, e);
formCallback($form);
initEditableHandsOnTable($form);
applyCancelCallBack();
formEditAjax($form);
//Rerender tables
$form.find("[data-role='step-hot-table']").each(function() {
@ -505,8 +499,11 @@ function processStep(ev, editMode, forS3) {
var $nameInput = $form.find("#step_name");
var nameValid = textValidator(ev, $nameInput, TextLimitEnum.REQUIRED,
TextLimitEnum.NAME_MAX_LENGTH);
var $descrTextarea = $form.find("#step_description");
var descriptionValid = textValidator(ev, $descrTextarea,
TextLimitEnum.OPTIONAL, TextLimitEnum.TEXT_MAX_LENGTH);
if (filesValid && checklistsValid && nameValid) {
if (filesValid && checklistsValid && nameValid && descriptionValid) {
if (forS3) {
// Redirects file uploading to S3
var url = '/asset_signature.json';

View file

@ -26,7 +26,13 @@ var TextLimitEnum = Object.freeze({
TEXT_MAX_LENGTH: '<%= TEXT_MAX_LENGTH %>'
});
function textValidator(ev, textInput, textLimitEnumMin, textLimitEnumMax) {
/*
* @param {boolean} clearErr Set clearErr to true if this is the only
* error that can happen/show.
*/
function textValidator(ev, textInput, textLimitEnumMin, textLimitEnumMax, clearErr) {
clearErr = _.isUndefined(clearErr) ? false : clearErr;
var text = $(textInput).val().trim();
$(textInput).val(text);
var nameTooShort = text.length < textLimitEnumMin;
@ -45,7 +51,7 @@ function textValidator(ev, textInput, textLimitEnumMin, textLimitEnumMax) {
var noErrors = _.isUndefined(errMsg);
if (!noErrors) {
renderFormError(ev, textInput, errMsg);
renderFormError(ev, textInput, errMsg, clearErr);
}
return noErrors;
}

View file

@ -2,12 +2,6 @@
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
@import "colors";
.description-label {
word-wrap: break-word;
}
/* Results index page */
#results {

View file

@ -3,7 +3,6 @@
.tree {
height: 100%;
overflow-y: auto;
padding-bottom: 30px;
}
.tree > ul {

View file

@ -415,13 +415,26 @@ li.module-hover {
margin-bottom: 35px;
margin-top: 45px;
max-width: 700px;
display: flex;
flex-direction: column;
.experiment-description {
margin-top: 20px;
word-wrap: break-word;
.panel-body {
display: flex;
flex-direction: column;
flex: 1 1 auto;
}
.no-description {
.experiment-description {
display: flex;
flex: 1 1 auto;
margin-top: 10px;
textarea {
resize: none;
}
}
.experiment-no-description {
color: $color-alto;
display: block;
font-size: 18px;

View file

@ -1,7 +1,3 @@
// Place all the styles related to the StepComments controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
.step-panel-collapse-link {
word-wrap: break-word;
}

View file

@ -11,6 +11,15 @@ body,
min-width: 320px;
}
/** Word wrapping everywhere, except for table header of "datatables.js" */
body, table.dataTable td {
word-break: break-word;
text-overflow: ellipsis;
}
table:not(.dataTable) {
table-layout: fixed;
}
#alert-container {
margin-bottom: 20px;
}
@ -329,7 +338,6 @@ a[data-toggle="tooltip"] {
background-color: transparent;
padding: 15px;
margin-bottom: 0;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
@ -675,17 +683,19 @@ ul.double-line > li {
color: darken($color-silver-chalice, 15%);
}
.tab-content ul {
margin-bottom: 15px;
}
.tab-content {
ul {
margin-bottom: 15px;
}
.tab-content li {
padding-left: 15px;
padding-right: 15px;
}
li {
padding-left: 15px;
padding-right: 15px;
}
.tab-content .manage-users-link {
padding-left: 15px;
.manage-users-link {
padding-left: 15px;
}
}
.content-module-info {
@ -979,6 +989,8 @@ ul.content-module-activities {
/* Data table */
table.dataTable {
word-break: initial;
text-overflow: initial;
width: 100% !important;
background-color: $color-alabaster;

View file

@ -223,7 +223,7 @@ class MyModulesController < ApplicationController
render :edit
}
format.json {
render json: @project.errors,
render json: @my_module.errors,
status: :unprocessable_entity
}
end

View file

@ -220,7 +220,7 @@ class StepsController < ApplicationController
}
else
format.json {
render json: @step.errors, status: :bad_request
render json: @step.errors.to_json, status: :bad_request
}
end
end

View file

@ -95,6 +95,7 @@ class Users::RegistrationsController < Devise::RegistrationsController
prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)
resource_updated = update_resource(resource, account_update_params)
yield resource if block_given?
if resource_updated
# Set "needs confirmation" flash if neccesary

View file

@ -3,9 +3,10 @@ class MyModule < ActiveRecord::Base
before_create :create_blank_protocol
auto_strip_attributes :name, nullify: false
auto_strip_attributes :name, :description, nullify: false
validates :name,
length: { minimum: NAME_MIN_LENGTH, maximum: NAME_MAX_LENGTH }
validates :description, length: { maximum: TEXT_MAX_LENGTH }
validates :x, :y, :workflow_order, presence: true
validates :experiment, presence: true
validates :my_module_group, presence: true, if: "!my_module_group_id.nil?"

View file

@ -7,8 +7,7 @@ class MyModuleGroup < ActiveRecord::Base
belongs_to :experiment, inverse_of: :my_module_groups
belongs_to :created_by, foreign_key: 'created_by_id', class_name: 'User'
has_many :my_modules, inverse_of: :my_module_group,
dependent: :nullify
has_many :my_modules, inverse_of: :my_module_group, dependent: :nullify
def self.search(user, include_archived, query = nil, page = 1)
exp_ids =

View file

@ -3,9 +3,10 @@ class Organization < ActiveRecord::Base
# output in space_taken related functions
include ActionView::Helpers::NumberHelper
auto_strip_attributes :name, nullify: false
auto_strip_attributes :name, :description, nullify: false
validates :name,
length: { minimum: NAME_MIN_LENGTH, maximum: NAME_MAX_LENGTH }
validates :description, length: { maximum: TEXT_MAX_LENGTH }
validates :space_taken, presence: true
belongs_to :created_by, :foreign_key => 'created_by_id', :class_name => 'User'

View file

@ -1,5 +1,4 @@
class ProtocolProtocolKeyword < ActiveRecord::Base
after_create :increment_protocols_count
after_destroy :decrement_protocols_count

View file

@ -21,6 +21,8 @@ class User < ActiveRecord::Base
validates :initials,
presence: true,
length: { maximum: USER_INITIALS_MAX_LENGTH }
validates :email, presence: true, length: { maximum: EMAIL_MAX_LENGTH }
validates_attachment :avatar,
:content_type => { :content_type => ["image/jpeg", "image/png"] },
size: { less_than: AVATAR_MAX_SIZE.megabytes }

View file

@ -6,15 +6,6 @@
<h4 class="modal-title" id="modal-new-module-label"><%=t "experiments.canvas.edit.modal_new_module.title" %></h4>
</div>
<div class="modal-body">
<span class="module-name-length-error" style="display: none;">
<%=t "experiments.canvas.edit.modal_new_module.error_length" %>
</span>
<span class="module-name-invalid-error" style="display: none;">
<%=t "experiments.canvas.edit.modal_new_module.error_invalid" %>
</span>
<span class="module-name-whitespaces-error" style="display: none;">
<%=t "experiments.canvas.edit.modal_new_module.error_whitespaces" %>
</span>
<%= bootstrap_form_tag do |f| %>
<%= f.text_field t("experiments.canvas.edit.modal_new_module.name"), placeholder: t("experiments.canvas.edit.modal_new_module.name_placeholder"), id: "new-module-name-input" %>
<% end %>

View file

@ -31,8 +31,7 @@
<div class="workflowimg-container"
data-check-img="<%= updated_img_experiment_url(experiment) %>"
data-updated-img="<%= fetch_workflow_img_experiment_url(experiment) %>"
data-timestamp="<%= experiment.updated_at %>"
>
data-timestamp="<%= experiment.updated_at %>" >
</div>
<% else %>
<div class="no-workflowimg">
@ -50,17 +49,20 @@
<%= localize(experiment.created_at, format: t('time.formats.full_date')) %> - <%= localize(experiment.updated_at, format: t('time.formats.full_date')) %>
</span>
<% if experiment.description? %>
<p class="experiment-description"><%= experiment.description %></p>
<span class='experiment-description'>
<%= text_area :entry, :body, value: experiment.description, class: 'form-control', readonly: true %>
</span>
<% else %>
<% if can_edit_experiment(experiment) %>
<p class="experiment-description"><span class="no-description"><%= link_to t('experiments.edit.add-description'),
edit_project_experiment_url(@project, experiment),
remote: true,
data: { id: experiment.id },
class: 'no-description-experiment' %></span></p>
<% else %>
<p class="experiment-description"><span class="no-description"><%= t('experiments.edit.no-description') %></span></p>
<% end %>
<span class='experiment-no-description'>
<% if can_edit_experiment(experiment) %>
<%= link_to t('experiments.edit.add-description'),
edit_project_experiment_url(@project, experiment),
remote: true,
data: { id: experiment.id } %>
<% else %>
<p><%= t('experiments.edit.no-description') %></p>
<% end %>
</span>
<% end %>
</div>
</div>

View file

@ -22,13 +22,13 @@
</div>
<div class="form-group">
<%= label :organization, :name, t('users.invitations.edit.name_label') %>
<%= label :organization, :name, t('users.registrations.new.team_name_label') %>
<% if @org %>
<%= text_field :organization, :name, class: "form-control", value: @org.name %>
<% else %>
<%= text_field :organization, :name, class: "form-control" %>
<% end %>
<span class="help-block"><small><%= t 'users.invitations.edit.name_help' %></small></span>
<span><small><%= t 'users.registrations.new.team_name_help' %></small></span>
</div>
<div class="form-group">

View file

@ -29,13 +29,13 @@
</div>
<div class="form-group">
<%= label :organization, :name %>
<%= label :organization, :name, t('users.registrations.new.team_name_label') %>
<% if @org %>
<%= text_field :organization, :name, class: "form-control", value: @org.name %>
<% else %>
<%= text_field :organization, :name, class: "form-control" %>
<% end %>
<span class="help-block"><small><%= t'users.registrations.new.name_help' %></small></span>
<span><small><%= t 'users.registrations.new.team_name_help' %></small></span>
</div>
<div class="form-group">

View file

@ -30,7 +30,7 @@
<th class="hidden-xs"><%=t "users.settings.organizations.index.thead_created_at" %></th>
<th class="hidden-xs"><%=t "users.settings.organizations.index.thead_joined_on" %></th>
<th><%=t "users.settings.organizations.index.thead_members" %></th>
<th style="width: 1%;">&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>

View file

@ -1086,8 +1086,6 @@ en:
invitations:
edit:
head_title: "Accept invitation"
name_label: "Team name"
name_help: "Team name is required in order to create your own team. (min 4 characters)"
registrations:
edit:
head_title: "My profile"
@ -1113,7 +1111,8 @@ en:
new_password_2_label: "New password confirmation"
new:
head_title: "Sign up"
name_help: "Team name is required in order to create your own team. (min 4 characters)"
team_name_label: "Team name"
team_name_help: "Team name is required in order to create your own team."
settings:
navigation:
preferences: "My preferences"