Allow skipping of select inputs
This commit is contained in:
parent
8957cd7533
commit
5abae8b11d
198
index.html
198
index.html
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue