Skip to content

Commit c8fffdf

Browse files
committed
added event validation in client
1 parent 700d866 commit c8fffdf

File tree

4 files changed

+146
-19
lines changed

4 files changed

+146
-19
lines changed

static/js/event-management.js

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,45 @@ document.addEventListener("DOMContentLoaded", function () {
2323
}
2424
});
2525

26+
// icon validation
27+
iconInput.addEventListener("input", () => {
28+
// invalid if the iconPreview is phosphor logo
29+
if (iconInput.value === "" || !iconPreview.className.includes("ph-phosphor-logo")) {
30+
iconInput.setCustomValidity("");
31+
} else {
32+
iconInput.setCustomValidity("Please provide a valid Phosphor Icon name or one of: " + "{{ ', '.join(icons) }}");
33+
}
34+
});
35+
2636
// update colour preview
2737
const colourPicker = document.getElementById("color_colour");
2838
const colourText = document.getElementById("text_colour");
2939

3040
function syncColourInputs(fromText) {
3141
if (fromText) {
32-
colourPicker.value = "#" + colourText.value;
33-
}
34-
else {
42+
if (colourText.value.startsWith("#")) {
43+
colourPicker.value = colourText.value; // set the colour picker to the text value
44+
} else {
45+
colourPicker.value = "#" + colourText.value; // prepend '#' if not present
46+
}
47+
} else {
3548
colourText.value = colourPicker.value.substring(1); // remove the '#' character
3649
}
3750
}
3851

3952
colourPicker.addEventListener("input", () => syncColourInputs(false));
4053
colourText.addEventListener("input", () => syncColourInputs(true));
4154

55+
// colour validation
56+
colourText.addEventListener("input", () => {
57+
const isHex = /^#?[0-9A-Fa-f]{6}$/;
58+
if (colourText.value === "" || colourText.value.match(isHex)) {
59+
colourText.setCustomValidity("");
60+
} else {
61+
colourText.setCustomValidity("Please provide a valid hex color code");
62+
}
63+
});
64+
4265
// update duration/end time
4366
const endTimeInput = document.getElementById("end_time");
4467
const durationInput = document.getElementById("duration");
@@ -104,4 +127,52 @@ document.addEventListener("DOMContentLoaded", function () {
104127
});
105128
durationInput.addEventListener("input", () => updateEndTime());
106129
endTimeInput.addEventListener("input", () => updateDuration());
130+
131+
// check if end time is after start time
132+
endTimeInput.addEventListener("input", () => {
133+
if (startTimeInput.value && endTimeInput.value) {
134+
const startTime = new Date(startTimeInput.value);
135+
const endTime = new Date(endTimeInput.value);
136+
137+
if (endTime <= startTime) {
138+
endTimeInput.setCustomValidity("End time must be after start time");
139+
} else {
140+
endTimeInput.setCustomValidity("");
141+
}
142+
}
143+
});
144+
145+
// check if endtime = starttime + duration
146+
endTimeInput.addEventListener("input", () => {
147+
if (startTimeInput.value && durationInput.value) {
148+
const startTime = new Date(startTimeInput.value);
149+
const [days, hours, minutes] = durationInput.value.split(':').map(Number);
150+
startTime.setDate(startTime.getDate() + days);
151+
startTime.setHours(startTime.getHours() + hours);
152+
startTime.setMinutes(startTime.getMinutes() + minutes);
153+
const endTime = new Date(endTimeInput.value);
154+
if (endTime.getTime() !== startTime.getTime()) {
155+
endTimeInput.setCustomValidity("End time does not match duration");
156+
} else {
157+
endTimeInput.setCustomValidity("");
158+
}
159+
}
160+
});
161+
162+
163+
// form validation
164+
const form = document.querySelector("form");
165+
form.addEventListener("submit", function (event) {
166+
if (!form.checkValidity()) {
167+
event.preventDefault();
168+
event.stopPropagation();
169+
}
170+
form.classList.add("was-validated");
171+
172+
// prepend "#" to text colour
173+
// TODO: custom colour acceptance
174+
if (colourText.value && !colourText.value.startsWith("#")) {
175+
colourText.value = "#" + colourText.value;
176+
}
177+
}, false);
107178
});

static/sass/custom/_custom.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
@import "form";
12
@import "nav";

static/sass/custom/_form.scss

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// no validation field https://stackoverflow.com/questions/48312772/skip-validation-feedback-on-certain-bootstrap-4-inputs
2+
3+
.no-validate-checkbox:valid {
4+
border-color: $input-border-color !important;
5+
background-color: $input-bg !important;
6+
}
7+
8+
.no-validate-checkbox:valid:checked {
9+
background-color: $uwcs-blue !important;
10+
}
11+
12+
.no-validate-checkbox:valid:focus {
13+
box-shadow: none !important;
14+
}
15+
16+
.no-validate-colour:valid {
17+
width: 3rem !important;
18+
padding: $input-padding-y !important;
19+
}
20+
21+
.no-validate:valid {
22+
border-color: $input-border-color !important;
23+
background-color: $uwcs-blue !important;
24+
background-image: none !important;
25+
background: $input-bg !important;
26+
box-shadow: none !important;
27+
}
28+
29+
.last-border {
30+
border-top-right-radius: var(--bs-border-radius) !important;
31+
border-bottom-right-radius: var(--bs-border-radius) !important;
32+
}

templates/events/form.html

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ <h1>Create Event</h1>
1313

1414
<div class="container y-4">
1515
<div class="y-4 border rounded p-4">
16-
<form method="{{ method }}" action="{{ url_for(action) }}" class="row g-3 align-items-center">
16+
<form method="{{ method }}" action="{{ url_for(action) }}" class="row g-3 align-items-center needs-validation" novalidate>
1717

1818
{% with messages = get_flashed_messages() %}
1919
{% if messages %}
@@ -29,36 +29,49 @@ <h1>Create Event</h1>
2929
<div class="form-floating col-md-6 offset-md-3">
3030
<input type="text" name="name" id="name" class="form-control" placeholder="Event Name" required>
3131
<label for="name">Event Name</label>
32+
<div class="invalid-feedback">Please provide a name</div>
33+
<div class="valid-feedback">Looks good!</div>
3234
</div>
3335

3436
<!-- description -->
3537
<div class="form-floating col-12">
3638
<textarea name="description" id="description" class="form-control" placeholder="Event Description" style="height: 10rem;" required></textarea>
3739
<label for="description">Event Description (Markdown)</label>
40+
<div class="invalid-feedback">Please provide a description</div>
41+
<div class="valid-feedback">Looks good!</div>
3842
</div>
3943

4044
<!-- location -->
4145
<!-- TODO: maybe update location url automatically?-->
46+
<!-- known issue with bootstrap (https://github.com/twbs/bootstrap/pull/34527#pullrequestreview-830171528) means form-floating needs to be a div when using input-groups -->
4247
<div class="input-group col-12">
4348
<div class="input-group-text">
4449
<i class="ph-bold ph-map-pin"></i>
4550
</div>
46-
<div class="form-floating">
51+
52+
<fieldset class="form-floating">
4753
<input type="text" name="location" id="location" class="form-control" placeholder="Event Location" required>
4854
<label for="location">Event Location</label>
49-
</div>
50-
<div class="form-floating">
51-
<input type="text" name="location_url" id="location_url" class="form-control" placeholder="https://example.com/">
55+
</fieldset>
56+
<div class="invalid-feedback" style="position: absolute; top: 70%;">Please provide a location</div>
57+
<div class="valid-feedback" style="position: absolute; top: 70%;">Looks good!</div>
58+
59+
<fieldset class="form-floating">
60+
<input type="text" name="location_url" id="location_url" class="form-control last-border" placeholder="https://example.com/" pattern="^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?$">
5261
<label for="location_url">Location URL</label>
53-
</div>
62+
</fieldset>
63+
<div class="invalid-feedback" style="position: absolute; top: 70%; left: 52%;">Must be a URL</div>
64+
<div class="valid-feedback" style="position: absolute; top: 70%; left: 52%;">Looks good!</div>
65+
<!-- zero-width space to add padding -->
66+
<div class="valid-feedback invalid-feedback"></div>
5467
</div>
5568

5669
<!-- draft -->
5770
<div class="col-md-4">
5871
<!-- same height calculation as form-floating to stay same size -->
5972
<div class="input-group" style="height: calc(3.5rem + calc(var(--bs-border-width) * 2));">
6073
<div class="input-group-text">
61-
<input type="checkbox" name="draft" id="draft" class="form-check-input">
74+
<input type="checkbox" name="draft" id="draft" class="form-check-input no-validate-checkbox">
6275
</div>
6376
<label for="draft" class="form-control d-flex align-items-center">Draft</label>
6477
</div>
@@ -71,49 +84,59 @@ <h1>Create Event</h1>
7184
<div class="input-group-text">
7285
<i class="ph-bold ph-phosphor-logo" id="icon-preview"></i>
7386
</div>
74-
<div class="form-floating">
75-
<input type="text" name="icon" id="icon" class="form-control" placeholder="ph-calendar">
87+
<fieldset class="form-floating">
88+
<input type="text" name="icon" id="icon" class="form-control last-border" placeholder="ph-calendar">
7689
<label for="icon">Event Phosphor Icon</label>
77-
</div>
90+
</fieldset>
91+
<div class="invalid-feedback">Please provide a valid <a href="https://phosphoricons.com/" target="_blank">Phosphor Icon</a> name or one of: {{ ", ".join(icons) }}</div>
92+
<div class="valid-feedback">Looks good!</div>
7893
</div>
7994
</div>
8095

8196
<!-- colour -->
8297
<div class="col-md-4">
8398
<div class="input-group">
8499
<div class="input-group-text">
85-
<input type="color" name="color_colour" id="color_colour" class="form-control form-control-color">
100+
<input type="color" name="color_colour" id="color_colour" class="form-control form-control-color no-validate no-validate-colour">
86101
</div>
87102
<div class="input-group-text">#</div>
88-
<div class="form-floating">
89-
<input type="text" name="text_colour" id="text_colour" class="form-control" placeholder="#000000">
103+
<fieldset class="form-floating">
104+
<input type="text" name="text_colour" id="text_colour" class="form-control last-border" placeholder="#000000">
90105
<label for="text_colour">Event Color</label>
91-
</div>
106+
</fieldset>
107+
<div class="invalid-feedback">Please provide a valid hex color code or one of: {{ ", ".join(colors) }}</div>
108+
<div class="valid-feedback">Looks good!</div>
92109
</div>
93110
</div>
94111

95112
<!-- start -->
96113
<div class="form-floating col-md-4">
97114
<input type="datetime-local" name="start_time" id="start_time" class="form-control" required>
98115
<label for="start_time">Start Time</label>
116+
<div class="invalid-feedback">Please provide a start time</div>
117+
<div class="valid-feedback">Looks good!</div>
99118
</div>
100119

101120
<!-- end -->
102121
<div class="form-floating col-md-4">
103122
<input type="datetime-local" name="end_time" id="end_time" class="form-control">
104123
<label for="end_time">End Time</label>
124+
<div class="invalid-feedback">Endtime must be after start time and match the duration</div>
125+
<div class="valid-feedback">Looks good!</div>
105126
</div>
106127

107128
<!-- duration -->
108129
<div class="form-floating col-md-4">
109-
<input type="text" name="duration" id="duration" class="form-control" pattern="^\d{2}:(?:[01]\d|2[0-3]):[0-5]\d$" placeholder="DD:HH:MM">
130+
<input type="text" name="duration" id="duration" class="form-control" pattern="\d{2}:(?:[01]\d|2[0-3]):[0-5]\d" placeholder="DD:HH:MM">
110131
<label for="duration">Duration (DD:HH:MM)</label>
132+
<div class="invalid-feedback">Please provide a duration in the format DD:HH:MM</div>
133+
<div class="valid-feedback">Looks good!</div>
111134
</div>
112135

113136
<!-- tags -->
114137
<!-- badge and created tags? -->
115138
<div class="form-floating col-md-8 offset-md-2">
116-
<input type="text" name="tags" id="tags" class="form-control" placeholder="tag1,tag2,tag3">
139+
<input type="text" name="tags" id="tags" class="form-control no-validate" placeholder="tag1,tag2,tag3">
117140
<label for="tags">Tags</label>
118141
</div>
119142

0 commit comments

Comments
 (0)