Allow skipping of select inputs

This commit is contained in:
Florine W. Dekker 2020-04-13 14:10:09 +02:00
parent 8957cd7533
commit 5abae8b11d
Signed by untrusted user: FWDekker
GPG Key ID: B1B567AF58D6EE0F
1 changed files with 161 additions and 37 deletions

View File

@ -29,6 +29,10 @@
margin-bottom: 15px; margin-bottom: 15px;
} }
summary b {
cursor: pointer;
}
.success-message { .success-message {
color: var(--success-color); color: var(--success-color);
@ -85,33 +89,39 @@
<!-- Input --> <!-- Input -->
<section class="container"> <section class="container">
<form> <form>
<div class="row"> <details open id="century-details">
<div class="column column-90"> <summary><b id="century-title-label">Century</b></summary>
<label for="century-input" id="century-title-label">Century</label> <div class="row">
<input type="text" id="century-input" autocomplete="off" autofocus /> <div class="column column-90">
<input type="text" id="century-input" autocomplete="off" autofocus />
</div>
<div class="column column-10 quiz-button-column">
<button type="button" class="quiz-button" id="century-submit">Check</button>
</div>
</div> </div>
<div class="column column-10 quiz-button-column"> </details>
<button type="button" class="quiz-button" id="century-submit">Check</button>
</div>
</div>
<div class="row"> <details open id="year-details">
<div class="column column-90"> <summary><b id="year-title-label">Year</b></summary>
<label for="year-input" id="year-title-label">Year</label> <div class="row">
<input type="text" id="year-input" autocomplete="off" /> <div class="column column-90">
<input type="text" id="year-input" autocomplete="off" />
</div>
<div class="column column-10 quiz-button-column">
<button type="button" class="quiz-button" id="year-submit">Check</button>
</div>
</div> </div>
<div class="column column-10 quiz-button-column"> </details>
<button type="button" class="quiz-button" id="year-submit">Check</button>
</div>
</div>
<div class="row"> <div>
<div class="column column-90"> <b id="day-title-label" style="margin-left: 17px;">Day</b>
<label for="day-input" id="day-title-label">Day</label> <div class="row">
<input type="text" id="day-input" autocomplete="off" /> <div class="column column-90">
</div> <input type="text" id="day-input" autocomplete="off" />
<div class="column column-10 quiz-button-column"> </div>
<button type="button" class="quiz-button" id="day-submit">Check</button> <div class="column column-10 quiz-button-column">
<button type="button" class="quiz-button" id="day-submit">Check</button>
</div>
</div> </div>
</div> </div>
@ -134,7 +144,7 @@
<a href="https://git.fwdekker.com/FWDekker/doomsday/src/branch/master/LICENSE">MIT License</a>. <a href="https://git.fwdekker.com/FWDekker/doomsday/src/branch/master/LICENSE">MIT License</a>.
Source code available on <a href="https://git.fwdekker.com/FWDekker/doomsday/">git</a>. Source code available on <a href="https://git.fwdekker.com/FWDekker/doomsday/">git</a>.
<div style="float: right;">v1.2.8</div> <div style="float: right;">v1.3.0</div>
</section> </section>
</footer> </footer>
</main> </main>
@ -142,6 +152,8 @@
<!-- Scripts --> <!-- Scripts -->
<script src="https://static.fwdekker.com/js/common.js" crossorigin="anonymous"></script> <script src="https://static.fwdekker.com/js/common.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-cookie/2.2.1/js.cookie.min.js"
integrity="sha256-oE03O+I6Pzff4fiMqwEGHbdfcW7a3GRRxlL+U49L5sA=" crossorigin="anonymous"></script>
<script> <script>
/** /**
* Returns a number between `min` (inclusive) and `max` (inclusive). * Returns a number between `min` (inclusive) and `max` (inclusive).
@ -158,14 +170,18 @@
* An input that can be validated. * An input that can be validated.
* *
* In particular, the century, year, and day inputs of the Doomsday test. * In particular, the century, year, and day inputs of the Doomsday test.
*
* @property input {HTMLInputElement} the input that is validatable
* @property titleLabel {HTMLElement} the label of which to update the text
* @property button {HTMLButtonElement} the submission button that activates validation
*/ */
class ValidatableInput { class ValidatableInput {
/** /**
* Constructs a new validatable input and registers event listeners. * Constructs a new validatable input and registers event listeners.
* *
* @param input the input that is validatable * @param input {HTMLInputElement} the input that is validatable
* @param titleLabel the label with the `labelFor` property for the input given as the first parameter * @param titleLabel {HTMLElement} the label of which to update the text
* @param button the submission button that activates validation * @param button {HTMLButtonElement} the submission button that activates validation
*/ */
constructor(input, titleLabel, button) { constructor(input, titleLabel, button) {
this.input = input; this.input = input;
@ -204,7 +220,8 @@
* *
* This method **must** be implemented by subclasses. * This method **must** be implemented by subclasses.
* *
* @param value the value of the input to validate * @param value {string} the value of the input to validate
* @return {boolean} `true` if and only if the input is valid
*/ */
isValid(value) { isValid(value) {
throw new Error("Implement this method."); throw new Error("Implement this method.");
@ -287,14 +304,88 @@
} }
} }
/**
* A wrapper around a `<details>` element that persists the state in a cookie.
*/
class ToggleableSection {
/**
* Constructs a new `ToggleableSection`.
*
* @param name {string} the name to identify this component with in persistent storage
* @param details {HTMLDetailsElement} the element that can be toggled
*/
constructor(name, details) {
this._name = name;
this._details = details;
this._details.addEventListener("toggle", () => this.onToggle(this.isOpened()));
this._loadToggle();
}
/**
* Returns `true` if and only if the component is currently open.
*
* @return {boolean} `true` if and only if the component is currently open.
*/
isOpened() {
return !!this._details.open;
}
/**
* Opens or closes the component.
*
* @param isOpened {boolean} whether to open the component
*/
setOpened(isOpened) {
this._details.open = isOpened;
}
/**
* This method is invoked whenever the component is toggled.
*
* @param isOpened {boolean} the new state of the component
*/
onToggle(isOpened) {
this._storeToggle();
}
/**
* Persists the state of this component.
*
* @private
*/
_storeToggle() {
Cookies.set(`toggle-${this._name}`, this.isOpened(), {expires: 365 * 10});
}
/**
* Reads the state of this component from persistent storage and applies it.
*
* @private
*/
_loadToggle() {
const target = Cookies.get(`toggle-${this._name}`);
if (target === undefined) {
this._storeToggle();
return;
}
this.setOpened(target === "true");
}
}
/** /**
* A wrapper around the good ol' `Date` class that provides a bunch of useful Doomsday-specific methods. * A wrapper around the good ol' `Date` class that provides a bunch of useful Doomsday-specific methods.
*
* @property {Date} the underlying date
*/ */
class DoomsdayDate { class DoomsdayDate {
/** /**
* Wraps a `DoomsdayDate` around the given `Date`. * Wraps a `DoomsdayDate` around the given date.
* *
* @param date the `Date` to be wrapped * @param date {Date} the date to be wrapped
*/ */
constructor(date) { constructor(date) {
this.date = date; this.date = date;
@ -303,13 +394,17 @@
/** /**
* Returns the number of this `DoomsdayDate`'s century. * Returns the number of this `DoomsdayDate`'s century.
*
* @return {number} the number of this `DoomsdayDate`'s century
*/ */
getCentury() { getCentury() {
return Math.floor(this.date.getFullYear() / 100); return Math.floor(this.date.getFullYear() / 100);
} }
/** /**
* Returns the day of the week of the anchor of this `DoomsdayDate`'s century as a string. * Returns the day of the week of the anchor of this `DoomsdayDate`'s century.
*
* @return {string} the day of the week of the anchor of this `DoomsdayDate`'s century
*/ */
getCenturyAnchorString() { getCenturyAnchorString() {
const centuryAnchorNumber = (5 * (this.getCentury() % 4)) % 7 + 2; const centuryAnchorNumber = (5 * (this.getCentury() % 4)) % 7 + 2;
@ -317,7 +412,9 @@
}; };
/** /**
* Returns the day of the week of the anchor day of this `DoomsdayDate`'s year as a string. * Returns the day of the week of the anchor day of this `DoomsdayDate`'s year.
*
* @return {string} the day of the week of the anchor day of this `DoomsdayDate`'s year
*/ */
getYearAnchorString() { getYearAnchorString() {
const anchorDate = new Date(this.date); const anchorDate = new Date(this.date);
@ -327,7 +424,9 @@
}; };
/** /**
* Returns the day of the week of this `DoomsdayDate` as a string. * Returns the day of the week of this `DoomsdayDate`.
*
* @return {string} the day of the week of this `DoomsdayDate`
*/ */
getWeekdayString() { getWeekdayString() {
return DoomsdayDate.dayNumberToString(this.date.getDay()); return DoomsdayDate.dayNumberToString(this.date.getDay());
@ -337,7 +436,8 @@
/** /**
* Returns the name of the day given its 0-based index, where 0 is `Sunday`. * Returns the name of the day given its 0-based index, where 0 is `Sunday`.
* *
* @param dayNumber the number of the day, as returned by `Date`'s `#getDay` function. * @param dayNumber {number} the number of the day, as returned by `Date`'s `#getDay` function.
* @return {string} the name of the day given its 0-based index, where 0 is `Sunday`
*/ */
static dayNumberToString(dayNumber) { static dayNumberToString(dayNumber) {
switch (dayNumber % 7) { switch (dayNumber % 7) {
@ -363,7 +463,8 @@
* *
* This is a convenience method for interpreting (incomplete) user inputs. * This is a convenience method for interpreting (incomplete) user inputs.
* *
* @param dayString the day of the week to expand * @param dayString {string} the day of the week to expand
* @return {string} the day of the week corresponding to the given string
*/ */
static expandDayString(dayString) { static expandDayString(dayString) {
dayString = dayString.toLowerCase(); dayString = dayString.toLowerCase();
@ -388,6 +489,8 @@
/** /**
* Returns a random date in the range `0001-01-01` (inclusive) to `9999-12-31` (inclusive), wrapped inside a * Returns a random date in the range `0001-01-01` (inclusive) to `9999-12-31` (inclusive), wrapped inside a
* `DoomsdayDate` object. * `DoomsdayDate` object.
*
* @return {DoomsdayDate} a random date
*/ */
static random() { static random() {
// TODO Give custom dates to this method // TODO Give custom dates to this method
@ -401,6 +504,19 @@
doAfterLoad(() => { doAfterLoad(() => {
let quizDate; let quizDate;
const centuryDetails = new class extends ToggleableSection {
onToggle(isOpened) {
super.onToggle(isOpened);
if (isOpened) centuryInput.selectInput();
}
}("century", $("#century-details"));
const yearDetails = new class extends ToggleableSection {
onToggle(isOpened) {
super.onToggle(isOpened);
if (isOpened) yearInput.selectInput();
}
}("year", $("#year-details"));
const centuryInput = new class extends ValidatableInput { const centuryInput = new class extends ValidatableInput {
isValid(value) { isValid(value) {
console.log("# Validate century"); console.log("# Validate century");
@ -412,7 +528,10 @@
onValidInput() { onValidInput() {
this.input.value = DoomsdayDate.expandDayString(this.input.value); this.input.value = DoomsdayDate.expandDayString(this.input.value);
yearInput.selectInput(); if (yearDetails.isOpened())
yearInput.selectInput();
else
dayInput.selectInput();
} }
onInvalidInput() { onInvalidInput() {
@ -491,7 +610,12 @@
centuryInput.reset(); centuryInput.reset();
yearInput.reset(); yearInput.reset();
dayInput.reset(); dayInput.reset();
centuryInput.selectInput(); if (centuryDetails.isOpened())
centuryInput.selectInput();
else if (yearDetails.isOpened())
yearInput.selectInput();
else
dayInput.selectInput();
} }
// Let the fun begin // Let the fun begin