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: FWDekker
GPG Key ID: B1B567AF58D6EE0F
1 changed files with 161 additions and 37 deletions

View File

@ -29,6 +29,10 @@
margin-bottom: 15px;
}
summary b {
cursor: pointer;
}
.success-message {
color: var(--success-color);
@ -85,33 +89,39 @@
<!-- Input -->
<section class="container">
<form>
<div class="row">
<div class="column column-90">
<label for="century-input" id="century-title-label">Century</label>
<input type="text" id="century-input" autocomplete="off" autofocus />
<details open id="century-details">
<summary><b id="century-title-label">Century</b></summary>
<div class="row">
<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 class="column column-10 quiz-button-column">
<button type="button" class="quiz-button" id="century-submit">Check</button>
</div>
</div>
</details>
<div class="row">
<div class="column column-90">
<label for="year-input" id="year-title-label">Year</label>
<input type="text" id="year-input" autocomplete="off" />
<details open id="year-details">
<summary><b id="year-title-label">Year</b></summary>
<div class="row">
<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 class="column column-10 quiz-button-column">
<button type="button" class="quiz-button" id="year-submit">Check</button>
</div>
</div>
</details>
<div class="row">
<div class="column column-90">
<label for="day-input" id="day-title-label">Day</label>
<input type="text" id="day-input" autocomplete="off" />
</div>
<div class="column column-10 quiz-button-column">
<button type="button" class="quiz-button" id="day-submit">Check</button>
<div>
<b id="day-title-label" style="margin-left: 17px;">Day</b>
<div class="row">
<div class="column column-90">
<input type="text" id="day-input" autocomplete="off" />
</div>
<div class="column column-10 quiz-button-column">
<button type="button" class="quiz-button" id="day-submit">Check</button>
</div>
</div>
</div>
@ -134,7 +144,7 @@
<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>.
<div style="float: right;">v1.2.8</div>
<div style="float: right;">v1.3.0</div>
</section>
</footer>
</main>
@ -142,6 +152,8 @@
<!-- Scripts -->
<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>
/**
* Returns a number between `min` (inclusive) and `max` (inclusive).
@ -158,14 +170,18 @@
* An input that can be validated.
*
* 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 {
/**
* Constructs a new validatable input and registers event listeners.
*
* @param input the input that is validatable
* @param titleLabel the label with the `labelFor` property for the input given as the first parameter
* @param button the submission button that activates validation
* @param input {HTMLInputElement} the input that is validatable
* @param titleLabel {HTMLElement} the label of which to update the text
* @param button {HTMLButtonElement} the submission button that activates validation
*/
constructor(input, titleLabel, button) {
this.input = input;
@ -204,7 +220,8 @@
*
* 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) {
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.
*
* @property {Date} the underlying date
*/
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) {
this.date = date;
@ -303,13 +394,17 @@
/**
* Returns the number of this `DoomsdayDate`'s century.
*
* @return {number} the number of this `DoomsdayDate`'s century
*/
getCentury() {
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() {
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() {
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() {
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`.
*
* @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) {
switch (dayNumber % 7) {
@ -363,7 +463,8 @@
*
* 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) {
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
* `DoomsdayDate` object.
*
* @return {DoomsdayDate} a random date
*/
static random() {
// TODO Give custom dates to this method
@ -401,6 +504,19 @@
doAfterLoad(() => {
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 {
isValid(value) {
console.log("# Validate century");
@ -412,7 +528,10 @@
onValidInput() {
this.input.value = DoomsdayDate.expandDayString(this.input.value);
yearInput.selectInput();
if (yearDetails.isOpened())
yearInput.selectInput();
else
dayInput.selectInput();
}
onInvalidInput() {
@ -491,7 +610,12 @@
centuryInput.reset();
yearInput.reset();
dayInput.reset();
centuryInput.selectInput();
if (centuryDetails.isOpened())
centuryInput.selectInput();
else if (yearDetails.isOpened())
yearInput.selectInput();
else
dayInput.selectInput();
}
// Let the fun begin