Skip to content

Commit d94595e

Browse files
Custom Error Handling (#1024)
* first pass at customizing form validation error * showing custom error when single field is invalid * comments and notes * adding custom error to all required fields * title triggers error state when empty * directory path triggers error state when empty adding aria polite label to the error states * js cleanup * first pass at linting * second pass at rspec tests * fixing timezone check * testing for custom error validation extracting the custom error svg into its own file --------- Co-authored-by: Hector Correa <[email protected]>
1 parent 85e9f7f commit d94595e

File tree

7 files changed

+132
-37
lines changed

7 files changed

+132
-37
lines changed

app/assets/images/custom_error.svg

Lines changed: 3 additions & 0 deletions
Loading

app/assets/stylesheets/_settings.scss

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,37 @@
232232
margin-bottom: 10px;
233233
}
234234

235+
.custom_error{
236+
display: none;
237+
238+
.error_message{
239+
color: var(--Secondary-Red-Dark, #B00002);
240+
margin-top: 2px;
241+
242+
/* Body XS */
243+
font-family: "Libre Franklin";
244+
font-size: 14px;
245+
font-style: normal;
246+
font-weight: 400;
247+
line-height: 21px; /* 150% */
248+
}
249+
}
250+
235251
input:valid {
236-
background-color: palegreen;
252+
background-color: rgb(255, 255, 255);
237253
}
238254

239255
input:invalid {
240-
background-color: lightpink;
256+
outline-color: red;
241257
}
258+
259+
// write css so that the text displayed on invalid input is red and appears to the right of the div instead of below it
260+
span {
261+
color: red;
262+
display: inline;
263+
margin-left: 10px;
264+
}
265+
242266
h2 {// Headings for roles and description
243267
text-decoration: underline;
244268
text-decoration-thickness: 3px;

app/javascript/entrypoints/application.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,47 @@ function emulate() {
200200
});
201201
}
202202

203+
function showValidationError() {
204+
const sponsor = document.getElementById('sponsor_error');
205+
const manager = document.getElementById('manager_error');
206+
const title = document.getElementById('title_error');
207+
const directory = document.getElementById('directory_error');
208+
209+
$('#data_sponsor').on('invalid', (inv) => {
210+
const element = inv;
211+
element.preventDefault();
212+
// sponsor.style.display += '-webkit-inline-box', 'block';
213+
// add webkit inline box and block to the sponsor style display in two separate lines
214+
sponsor.style.display += '-webkit-inline-box';
215+
sponsor.style.display += 'block';
216+
});
217+
218+
$('#data_manager').on('invalid', (inv) => {
219+
const element = inv;
220+
element.preventDefault();
221+
manager.style.display += '-webkit-inline-box';
222+
manager.style.display += 'block';
223+
});
224+
225+
$('#title').on('change', (inv) => {
226+
const element = inv;
227+
element.preventDefault();
228+
if (element.target.value === '') {
229+
title.style.display += '-webkit-inline-box';
230+
title.style.display += 'block';
231+
}
232+
});
233+
234+
$('#project_directory').on('change', (inv) => {
235+
const element = inv;
236+
element.preventDefault();
237+
if (element.target.value === '') {
238+
directory.style.display += '-webkit-inline-box';
239+
directory.style.display += 'block';
240+
}
241+
});
242+
}
243+
203244
function initPage() {
204245
$('#test-jquery').click((event) => {
205246
setTargetHtml(event, 'jQuery works!');
@@ -211,6 +252,7 @@ function initPage() {
211252
showMoreLessContent();
212253
showMoreLessSysAdmin();
213254
emulate();
255+
showValidationError();
214256
}
215257

216258
window.addEventListener('load', () => initPage());

app/views/projects/_edit_form.html.erb

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<%= render 'form_errors' %>
22
<%= render 'data_list' %>
3-
<div>
3+
<div id="edit">
44
<h2>Project Roles</h2>
55

66
<div class="row ">
@@ -10,10 +10,16 @@
1010
</div>
1111
<div class="col-md-9">
1212
<% if current_user.superuser? %>
13-
<input type="text" list="sponsor-users" id="data_sponsor" aria-label="data sponsor" name="data_sponsor" required value="<%= @project.metadata_model.data_sponsor %>" /></input>
13+
<input type="text" list="sponsor-users" id="data_sponsor" aria-label="data sponsor" name="data_sponsor" required oninvalid="this.setCustomValidity('')"
14+
oninput="this.setCustomValidity('')" value="<%= @project.metadata_model.data_sponsor %>" /></input>
15+
<div class="custom_error" id="sponsor_error" aria-live="polite">
16+
<%= image_tag("custom_error.svg", alt: "This field is required") %>
17+
<div class="error_message">This field is required. </div>
18+
</div>
1419
<% else %>
15-
<input type="hidden" id="data_sponsor" name="data_sponsor" value="<%= @project.metadata_model.data_sponsor || current_user.uid %>">
16-
<span id="non-editable-data-sponsor"><%= @project.metadata_model.data_sponsor || current_user.uid %></span>
20+
<input type="hidden" id="data_sponsor" name="data_sponsor" oninvalid="this.setCustomValidity('')"
21+
oninput="this.setCustomValidity('')" value="<%= @project.metadata_model.data_sponsor || current_user.uid %>">
22+
<span id="non-editable-data-sponsor"><%= @project.metadata_model.data_sponsor || current_user.uid %></span>
1723
<% end %>
1824
</div>
1925
</div>
@@ -25,7 +31,13 @@
2531
</div>
2632
<div class="col-md-9">
2733
<% if !@project.persisted? || @project.metadata_model.data_sponsor == current_user.uid %>
28-
<input type="text" list="manager-users" id="data_manager" aria-label="data manager" name="data_manager" required value="<%= @project.metadata_model.data_manager %>" /></input>
34+
<input type="text" list="manager-users" id="data_manager" aria-label="data manager" name="data_manager" required oninvalid="this.setCustomValidity('')"
35+
oninput="this.setCustomValidity('')" value="<%= @project.metadata_model.data_manager %>" /></input>
36+
<div class="custom_error" id="manager_error" aria-live="polite">
37+
<%= image_tag("custom_error.svg", alt: "This field is required") %>
38+
<div class="error_message">This field is required. </div>
39+
</div>
40+
</div>
2941
<% else %>
3042
<input type="hidden" id="data_manager" aria-label="data manager" name="data_manager" value="<%= @project.metadata_model.data_manager %>">
3143
<span id="non-editable-data-manager"><%= @project.metadata_model.data_manager %></span>
@@ -75,25 +87,31 @@
7587
</div>
7688

7789
<h2>Project Description</h2>
78-
<% if current_user.superuser? %>
90+
<% if current_user.superuser? %>
7991
<div class="row ">
80-
<div class="col-md-3">
81-
<label for="project_id">Project ID</label>
82-
</div>
83-
<div class="col-md-9">
84-
<input type="text" id="project_id" aria-label="project id" name="project_id" value="<%= @project.metadata_model.project_id %>" /></input>
85-
</div>
92+
<div class="col-md-3">
93+
<label for="project_id">Project ID</label>
94+
</div>
95+
<div class="col-md-9">
96+
<input type="text" id="project_id" aria-label="project id" name="project_id" value="<%= @project.metadata_model.project_id %>" /></input>
97+
</div>
8698
</div>
87-
<% end %>
88-
<div class="row ">
89-
<div class="col-md-3">
90-
<label for="title">Title</label>
91-
<div class="required-field">Required</div>
92-
</div>
93-
<div class="col-md-9">
94-
<input type="text" id="title" name="title" aria-label="project title" required value="<%= @project.metadata_model.title %>" />
95-
</div>
99+
<% end %>
100+
<div class="row ">
101+
<div class="col-md-3">
102+
<label for="title">Title</label>
103+
<div class="required-field">Required</div>
96104
</div>
105+
<div class="col-md-9">
106+
<div id="edit">
107+
<input type="text" id="title" name="title" aria-label="project title" required oninvalid="this.setCustomValidity('')" oninput="this.setCustomValidity('')" value="<%= @project.metadata_model.title %>" />
108+
<div class="custom_error" id="title_error" aria-live="polite">
109+
<%= image_tag("custom_error.svg", alt: "This field is required") %>
110+
<div class="error_message">This field is required. </div>
111+
</div>
112+
</div>
113+
</div>
114+
</div>
97115

98116
<% if @project.in_mediaflux? %>
99117
<!--
@@ -103,7 +121,7 @@
103121
<div class="row ">
104122
<div class="col-md-3">
105123
<label for="project_directory">Project Directory <%= @project.project_directory_parent_path %>/</label>
106-
124+
107125
</div>
108126
<div class="col-md-9">
109127
<input type="text" aria-label="project directory" id="project_directory" name="project_directory" readonly value="<%= @project.project_directory_short %>" />
@@ -112,28 +130,33 @@
112130

113131
<% if (current_user.superuser? || current_user.eligible_sysadmin?) %>
114132
<div class="row ">
115-
<div class="col-md-3">
116-
<label>MediaFlux ID</label>
133+
<div class="col-md-3">
134+
<label>MediaFlux ID</label>
117135

118136

119137
</div>
120138
<div class="col-md-9">
121139
<input readonly value="<%= @project.mediaflux_id %>" />
122140
<p class="mediaflux-tooltip">This project has already been saved to Mediaflux and the project_directory cannot be changed</p>
123-
124141
</div>
125-
</div>
142+
126143
<% end %>
127144

128145
<% end %>
129146

130147
<div class="row ">
131148
<div class="col-md-3">
132149
<label for="project_directory">Directory Path</label>
133-
<div class="required-field">Required</div>
150+
<div class="required-field">Required</div>
134151
</div>
135152
<div class="col-md-9">
136-
<span class="path-info"><%= @project.project_directory_parent_path %>/ </span><input type="text" aria-label="project directory" id="project_directory" name="project_directory" required value="<%= @project.project_directory_short %>" pattern="[\w\p{L}\-]{1,64}" />
153+
<div id="edit">
154+
<span class="path-info"><%= @project.project_directory_parent_path %>/ </span>
155+
<input type="text" aria-label="project directory" id="project_directory" name="project_directory" required oninvalid="this.setCustomValidity('')" oninput="this.setCustomValidity('')" value="<%= @project.project_directory_short %>" pattern="[\w\p{L}\-]{1,64}" />
156+
<div class="custom_error" id="directory_error" aria-live="polite">
157+
<%= image_tag("custom_error.svg", alt: "This field is required") %>
158+
<div class="error_message">This field is required. </div>
159+
</div>
137160
</div>
138161
</div>
139162

@@ -157,7 +180,7 @@
157180
</select>
158181
</div>
159182
</div>
160-
183+
</div>
161184

162185
<div class="row ">
163186
<div class="col-md-3">
@@ -187,5 +210,5 @@
187210
</div>
188211

189212

190-
191-
</div>
213+
214+
</div>

spec/models/mediaflux/time_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
initial_tz = xml_snip.xpath("./@tz").text
2424

2525
final_tz = instance.convert(xml_snip:)
26-
expect(final_tz).to include("-04:00").or include("-05:00") #America/New_York changes based on daylights savings time
26+
expect(["-04:00", "-05:00"].any? { |tz| final_tz.include?(tz) }).to be_truthy #America/New_York changes based on daylights savings time
2727
end
2828
end
2929
describe "date formatting" do

spec/system/project_roles_spec.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,8 @@
2929
fill_in "data_manager", with: "xxx"
3030
page.find("body").click
3131
expect(page.find("button[value=Submit]")).to be_disabled
32-
expect(page.find("#data_manager").native.attribute("validationMessage")).to eq "Please select a valid value."
3332
fill_in "data_manager", with: ""
3433
expect(page.find("button[value=Submit]")).to be_disabled
35-
expect(page.find("#data_manager").native.attribute("validationMessage")).to eq "Please select a valid value."
3634
fill_in "data_manager", with: data_manager.uid
3735
page.find("body").click
3836
click_on "Submit"

spec/system/project_spec.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,13 @@
249249
visit "/"
250250
click_on "New Project"
251251
expect(page.find("#non-editable-data-sponsor").text).to eq sponsor_user.uid
252+
252253
fill_in "data_manager", with: "xxx"
253-
expect(page.find("#data_manager").native.attribute("validationMessage")).to eq "Please select a valid value."
254+
# expect page to have the custom validation error message
255+
# Without removing the focus from the form field, the "change" event is not propagated for the DOM
256+
page.find("body").click
257+
expect(page).to have_content("This field is required.")
258+
254259
fill_in "ro-user-uid-to-add", with: read_only.uid
255260
# Without removing the focus from the form field, the "change" event is not propagated for the DOM
256261
page.find("body").click

0 commit comments

Comments
 (0)