Implement validation module
This commit is contained in:
parent
858e577422
commit
70465ef7bd
37
Gruntfile.js
37
Gruntfile.js
|
@ -15,7 +15,7 @@ module.exports = grunt => {
|
||||||
},
|
},
|
||||||
focus: {
|
focus: {
|
||||||
deploy: {
|
deploy: {
|
||||||
include: ["css", "storage", "template"],
|
include: ["css", "storage", "template", "validation"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
webpack: {
|
webpack: {
|
||||||
|
@ -44,7 +44,8 @@ module.exports = grunt => {
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.js$/i,
|
test: /\.ts$/,
|
||||||
|
use: "ts-loader",
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -53,13 +54,31 @@ module.exports = grunt => {
|
||||||
extensions: [".ts"],
|
extensions: [".ts"],
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
library: "fwdekker-template",
|
|
||||||
libraryTarget: "umd",
|
|
||||||
filename: "template.js",
|
filename: "template.js",
|
||||||
path: path.resolve(__dirname, "dist"),
|
path: path.resolve(__dirname, "dist"),
|
||||||
},
|
},
|
||||||
mode: "production",
|
mode: "production",
|
||||||
},
|
},
|
||||||
|
validation: {
|
||||||
|
entry: "./src/main/js/Validation.ts",
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.ts$/,
|
||||||
|
use: "ts-loader",
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: [".ts"],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: "validation.js",
|
||||||
|
path: path.resolve(__dirname, "dist/"),
|
||||||
|
},
|
||||||
|
mode: "production",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
css: {
|
css: {
|
||||||
|
@ -67,13 +86,17 @@ module.exports = grunt => {
|
||||||
tasks: ["cssmin"],
|
tasks: ["cssmin"],
|
||||||
},
|
},
|
||||||
storage: {
|
storage: {
|
||||||
files: ["src/main/**/*.ts"],
|
files: ["src/main/js/Storage.ts"],
|
||||||
tasks: ["webpack:storage"],
|
tasks: ["webpack:storage"],
|
||||||
},
|
},
|
||||||
template: {
|
template: {
|
||||||
files: ["src/main/**/*.js"],
|
files: ["src/main/js/Template.ts"],
|
||||||
tasks: ["webpack:template"],
|
tasks: ["webpack:template"],
|
||||||
},
|
},
|
||||||
|
validation: {
|
||||||
|
files: ["src/main/js/Validation.ts"],
|
||||||
|
tasks: ["webpack:validation"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -83,7 +106,7 @@ module.exports = grunt => {
|
||||||
grunt.loadNpmTasks("grunt-focus");
|
grunt.loadNpmTasks("grunt-focus");
|
||||||
grunt.loadNpmTasks("grunt-webpack");
|
grunt.loadNpmTasks("grunt-webpack");
|
||||||
|
|
||||||
grunt.registerTask("deploy", ["webpack:storage", "webpack:template", "cssmin"]);
|
grunt.registerTask("deploy", ["webpack:storage", "webpack:template", "webpack:validation", "cssmin"]);
|
||||||
grunt.registerTask("deploy:server", ["deploy", "focus:deploy"]);
|
grunt.registerTask("deploy:server", ["deploy", "focus:deploy"]);
|
||||||
|
|
||||||
grunt.registerTask("default", ["deploy"]);
|
grunt.registerTask("default", ["deploy"]);
|
||||||
|
|
|
@ -8,7 +8,11 @@ The main functionality is provided in `template.js` and `template.css`.
|
||||||
There also exist optional modules for easily reusing common code.
|
There also exist optional modules for easily reusing common code.
|
||||||
Modules can be used stand-alone.
|
Modules can be used stand-alone.
|
||||||
If `template.js` is used, modules should be loaded after `template.js`.
|
If `template.js` is used, modules should be loaded after `template.js`.
|
||||||
Currently, the only module is `storage.js` for interfacing with local storage.
|
Current available modules are:
|
||||||
|
* `storage.js` for interfacing with local storage, and
|
||||||
|
Main module optional.
|
||||||
|
* `validation.js` for form validation.
|
||||||
|
Requires main module.
|
||||||
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
@import "../../../node_modules/@picocss/pico/css/pico.css";
|
@import "../../../node_modules/@picocss/pico/css/pico.css";
|
||||||
|
@import "snippets/overrides.css";
|
||||||
|
|
||||||
@import "snippets/colors.css";
|
@import "snippets/colors.css";
|
||||||
|
|
||||||
@import "snippets/common.css";
|
@import "snippets/common.css";
|
||||||
@import "snippets/nav.css";
|
@import "snippets/nav.css";
|
||||||
@import "snippets/overrides.css";
|
@import "snippets/validation.css";
|
||||||
|
|
|
@ -67,7 +67,7 @@ noscript.fwd-js-notice p {
|
||||||
|
|
||||||
|
|
||||||
/* Header */
|
/* Header */
|
||||||
header a[href="."] {
|
header.fwd-header a[href="."] {
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
.status-card {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card .close {
|
||||||
|
float: right;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
margin: 0 0 calc(var(--block-spacing-vertical) / 2) calc(var(--block-spacing-vertical) / 2);
|
||||||
|
|
||||||
|
background-image: var(--icon-close);
|
||||||
|
background-position: center;
|
||||||
|
background-size: auto 1rem;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
opacity: .5;
|
||||||
|
transition: opacity var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card .close:is([aria-current], :hover, :active, :focus) {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card.info {
|
||||||
|
/* Colors taken from https://isabelcastillo.com/error-info-messages-css */
|
||||||
|
background-color: #bde5f8;
|
||||||
|
color: #00529b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card.error {
|
||||||
|
background-color: var(--form-element-invalid-focus-color);
|
||||||
|
color: var(--form-element-invalid-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card.success {
|
||||||
|
background-color: var(--form-element-valid-focus-color);
|
||||||
|
color: var(--form-element-valid-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card.warning {
|
||||||
|
/* Colors taken from https://isabelcastillo.com/error-info-messages-css */
|
||||||
|
background-color: #feefb3;
|
||||||
|
color: #9f6000;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
label.invalid,
|
||||||
|
input.invalid,
|
||||||
|
*[data-hint-for].invalid {
|
||||||
|
color: var(--form-element-invalid-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.valid,
|
||||||
|
input.valid,
|
||||||
|
*[data-hint-for].valid {
|
||||||
|
color: var(--form-element-valid-border-color) !important;
|
||||||
|
}
|
|
@ -181,3 +181,4 @@ export class MemoryStorage implements Storage {
|
||||||
// Export to `window`
|
// Export to `window`
|
||||||
(window as any).fwdekker = (window as any).fwdekker ?? {};
|
(window as any).fwdekker = (window as any).fwdekker ?? {};
|
||||||
(window as any).fwdekker.storage = {Storage, LocalStorage, MemoryStorage};
|
(window as any).fwdekker.storage = {Storage, LocalStorage, MemoryStorage};
|
||||||
|
export {};
|
||||||
|
|
|
@ -12,11 +12,12 @@ function stringToHtml(string: string, query: string): HTMLElement | null {
|
||||||
/**
|
/**
|
||||||
* Alias for `root.querySelector(query)`.
|
* Alias for `root.querySelector(query)`.
|
||||||
*
|
*
|
||||||
* @param query the query string
|
* @param query the query string, or `undefined` or `null` if `null` should be returned
|
||||||
* @param root the element to start searching in, or `undefined` if searching should start in `document`
|
* @param root the element to start searching in, or `undefined` if searching should start in `document`
|
||||||
* @returns the element identified by `query` in `root`, or `null` if that element could not be found
|
* @returns the element identified by `query` in `root`, or `null` if that element could not be found
|
||||||
*/
|
*/
|
||||||
function $(query: string, root?: HTMLElement): HTMLElement | null {
|
function $(query: string | null | undefined, root?: HTMLElement): HTMLElement | null {
|
||||||
|
if (query == null) return null;
|
||||||
return root === undefined ? document.querySelector(query) : root.querySelector(query);
|
return root === undefined ? document.querySelector(query) : root.querySelector(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,19 +227,17 @@ function footerLink(prefix: string, text: string | null | undefined, url: string
|
||||||
* not a valid value as a parameter for that function, its value is considered `undefined`.
|
* not a valid value as a parameter for that function, its value is considered `undefined`.
|
||||||
*/
|
*/
|
||||||
doAfterLoad(() => {
|
doAfterLoad(() => {
|
||||||
const navTargetQuery = getMetaProperty("fwd:nav:target");
|
const navTarget = $(getMetaProperty("fwd:nav:target"));
|
||||||
if (navTargetQuery != null) {
|
if (navTarget != null) {
|
||||||
const navTarget = $(navTargetQuery);
|
navTarget.parentElement?.replaceChild(
|
||||||
navTarget?.parentElement?.replaceChild(
|
|
||||||
nav(getMetaProperty("fwd:nav:highlight-path") ?? undefined),
|
nav(getMetaProperty("fwd:nav:highlight-path") ?? undefined),
|
||||||
navTarget
|
navTarget
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const footerTargetQuery = getMetaProperty("fwd:footer:target");
|
const footerTarget = $(getMetaProperty("fwd:footer:target"));
|
||||||
if (footerTargetQuery != null) {
|
if (footerTarget != null) {
|
||||||
const footerTarget = $(footerTargetQuery);
|
footerTarget.parentElement?.replaceChild(
|
||||||
footerTarget?.parentElement?.replaceChild(
|
|
||||||
footer({
|
footer({
|
||||||
author: getMetaProperty("fwd:footer:author"),
|
author: getMetaProperty("fwd:footer:author"),
|
||||||
authorURL: getMetaProperty("fwd:footer:author-url"),
|
authorURL: getMetaProperty("fwd:footer:author-url"),
|
||||||
|
@ -256,4 +255,5 @@ doAfterLoad(() => {
|
||||||
|
|
||||||
|
|
||||||
// Export to `window`
|
// Export to `window`
|
||||||
(window as any).fwdekker = {stringToHtml, $, $a, doAfterLoad};
|
(window as any).fwdekker = {$, $a, doAfterLoad, getMetaProperty, stringToHtml};
|
||||||
|
export {};
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
if ((window as any).fwdekker == null) throw new Error("Validation module requires main module.");
|
||||||
|
const {$, $a, doAfterLoad, getMetaProperty} = (window as any).fwdekker;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all validation-related information from `form`.
|
||||||
|
*
|
||||||
|
* @param form the form to hide validation information from
|
||||||
|
*/
|
||||||
|
function clearFormValidity(form: HTMLFormElement): void {
|
||||||
|
clearMessageStatus(form);
|
||||||
|
$a("input", form).forEach((input: HTMLInputElement) => clearInputValidity(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a `type` message in `card`.
|
||||||
|
*
|
||||||
|
* @param card the card to show `message` in, or `form` to show the `message` in the form's status card
|
||||||
|
* @param message the message to show in `card`, or `undefined` if `card` should be hidden
|
||||||
|
* @param type the type of message to show in `card`, or `undefined` if `card` should be hidden
|
||||||
|
*/
|
||||||
|
function showMessageType(card: HTMLElement | HTMLFormElement,
|
||||||
|
message?: string,
|
||||||
|
type?: "error" | "info" | "success" | "warning"): void {
|
||||||
|
if (card instanceof HTMLFormElement) {
|
||||||
|
card = $(`article[data-status-for="${card.id}"]`);
|
||||||
|
if (card == null) throw new Error("Could not find status card.");
|
||||||
|
}
|
||||||
|
|
||||||
|
card.classList.remove("hidden", "error", "info", "success", "warning");
|
||||||
|
|
||||||
|
if (message == null || type == null) {
|
||||||
|
card.classList.add("hidden");
|
||||||
|
$("output", card).innerText = "";
|
||||||
|
} else {
|
||||||
|
card.classList.add(type);
|
||||||
|
$("output", card).innerText = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the message in `card`, hiding it in the process.
|
||||||
|
*
|
||||||
|
* @param card the card to clear the message from
|
||||||
|
*/
|
||||||
|
function clearMessageStatus(card: HTMLElement): void {
|
||||||
|
showMessageType(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows an error message in `card`.
|
||||||
|
*
|
||||||
|
* @param card the card to show `message` in
|
||||||
|
* @param message the error message to show in `card`
|
||||||
|
*/
|
||||||
|
function showMessageError(card: HTMLElement, message: string): void {
|
||||||
|
showMessageType(card, message, "error");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows an information message in `card`.
|
||||||
|
*
|
||||||
|
* @param card the card to show `message` in
|
||||||
|
* @param message the message to show in `card`
|
||||||
|
*/
|
||||||
|
function showMessageInfo(card: HTMLElement, message: string): void {
|
||||||
|
showMessageType(card, message, "info");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a success message in `card`.
|
||||||
|
*
|
||||||
|
* @param card the card to show `message` in
|
||||||
|
* @param message the success message to show in `card`
|
||||||
|
*/
|
||||||
|
function showMessageSuccess(card: HTMLElement, message: string): void {
|
||||||
|
showMessageType(card, message, "success");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a warning message in `card`.
|
||||||
|
*
|
||||||
|
* @param card the card to show `message` in
|
||||||
|
* @param message the success message to show in `card`
|
||||||
|
*/
|
||||||
|
function showMessageWarning(card: HTMLElement, message: string): void {
|
||||||
|
showMessageType(card, message, "warning");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks `input` as neither valid nor invalid.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
*/
|
||||||
|
function clearInputValidity(input: HTMLInputElement): void {
|
||||||
|
input.classList.remove("valid", "invalid");
|
||||||
|
input.removeAttribute("aria-invalid");
|
||||||
|
input.removeAttribute("aria-errormessage");
|
||||||
|
|
||||||
|
const label = $(`label[for="${input.id}"]`);
|
||||||
|
if (label != null)
|
||||||
|
label.classList.remove("valid", "invalid");
|
||||||
|
|
||||||
|
const hint = $(`*[data-hint-for="${input.id}"]`);
|
||||||
|
if (hint != null) {
|
||||||
|
hint.classList.remove("valid", "invalid");
|
||||||
|
|
||||||
|
hint.role = null;
|
||||||
|
hint.innerText = hint.dataset["hint"] ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows to the user that `input` is invalid.
|
||||||
|
*
|
||||||
|
* @param input the input to show as invalid
|
||||||
|
* @param message the message explaining what is invalid
|
||||||
|
*/
|
||||||
|
function showInputInvalid(input: HTMLInputElement, message?: string): void {
|
||||||
|
clearInputValidity(input);
|
||||||
|
|
||||||
|
input.classList.add("invalid");
|
||||||
|
input.setAttribute("aria-invalid", "true");
|
||||||
|
input.focus();
|
||||||
|
|
||||||
|
const label = $(`label[for="${input.id}"]`);
|
||||||
|
if (label != null)
|
||||||
|
label.classList.add("invalid");
|
||||||
|
|
||||||
|
const hint = $(`*[data-hint-for="${input.id}"]`);
|
||||||
|
if (hint != null && message != null) {
|
||||||
|
hint.classList.add("invalid");
|
||||||
|
|
||||||
|
input.setAttribute("aria-errormessage", hint.id);
|
||||||
|
hint.role = "alert";
|
||||||
|
hint.innerText = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows to the user that `input` is valid.
|
||||||
|
*
|
||||||
|
* @param input the input to show as valid
|
||||||
|
* @param message the message to show at the input
|
||||||
|
*/
|
||||||
|
function showInputValid(input: HTMLInputElement, message?: string): void {
|
||||||
|
clearInputValidity(input);
|
||||||
|
|
||||||
|
input.classList.add("valid");
|
||||||
|
input.setAttribute("aria-invalid", "false");
|
||||||
|
|
||||||
|
const label = $(`label[for="${input.id}"]`);
|
||||||
|
if (label != null)
|
||||||
|
label.classList.add("valid");
|
||||||
|
|
||||||
|
const hint = $(`*[data-hint-for="${input.id}"]`);
|
||||||
|
if (hint != null) {
|
||||||
|
hint.classList.add("valid");
|
||||||
|
|
||||||
|
if (message != null) hint.innerText = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the `fwd:validation:load-hints` meta property has been set, loads hints and implements close buttons for forms.
|
||||||
|
*/
|
||||||
|
doAfterLoad(() => {
|
||||||
|
if (getMetaProperty("fwd:validation:load-forms") === undefined) return;
|
||||||
|
|
||||||
|
$a(".status-card .close").forEach((close: HTMLElement) => {
|
||||||
|
close.addEventListener("click", (event: MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
close.parentElement!.classList.add("hidden");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$a("input + small[data-hint]").forEach((hint: Element) => {
|
||||||
|
if (!(hint instanceof HTMLElement)) return;
|
||||||
|
|
||||||
|
hint.innerText = hint.dataset["hint"] ?? "";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Export to `window`
|
||||||
|
(window as any).fwdekker = (window as any).fwdekker ?? {};
|
||||||
|
(window as any).fwdekker.validation = {
|
||||||
|
clearFormValidity,
|
||||||
|
clearMessageStatus, showMessageError, showMessageInfo, showMessageSuccess, showMessageWarning,
|
||||||
|
clearInputValidity, showInputInvalid, showInputValid
|
||||||
|
};
|
||||||
|
export {};
|
|
@ -13,6 +13,7 @@
|
||||||
<meta name="fwd:footer:target" content="#footer" />
|
<meta name="fwd:footer:target" content="#footer" />
|
||||||
<meta name="fwd:footer:vcs-url" content="https://git.fwdekker.com/FWDekker/fwdekker-template/" />
|
<meta name="fwd:footer:vcs-url" content="https://git.fwdekker.com/FWDekker/fwdekker-template/" />
|
||||||
<meta name="fwd:footer:version" content="vTEST" />
|
<meta name="fwd:footer:version" content="vTEST" />
|
||||||
|
<meta name="fwd:validation:load-forms" />
|
||||||
|
|
||||||
<title>Template test | FWDekker</title>
|
<title>Template test | FWDekker</title>
|
||||||
|
|
||||||
|
@ -48,6 +49,11 @@
|
||||||
</hgroup>
|
</hgroup>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<article id="global-status-card" class="status-card hidden">
|
||||||
|
<output></output>
|
||||||
|
<a class="close" href="#" aria-label="Close"></a>
|
||||||
|
</article>
|
||||||
|
|
||||||
<h1>Test</h1>
|
<h1>Test</h1>
|
||||||
<p>This <a href="./" target="_blank">is an external link</a> in a sentence.</p>
|
<p>This <a href="./" target="_blank">is an external link</a> in a sentence.</p>
|
||||||
<p>These are some more contents.</p>
|
<p>These are some more contents.</p>
|
||||||
|
@ -61,20 +67,20 @@
|
||||||
<h3>Already have an account? Welcome back!</h3>
|
<h3>Already have an account? Welcome back!</h3>
|
||||||
</hgroup>
|
</hgroup>
|
||||||
</header>
|
</header>
|
||||||
<form id="login-form" novalidate>
|
<form id="test-form" novalidate>
|
||||||
<article class="form-validation-info">
|
<article class="status-card hidden" data-status-for="test-form">
|
||||||
<output>Congrats!</output>
|
<output>Congrats!</output>
|
||||||
<button type="button" class="close-button">×</button>
|
<a class="close" href="#" aria-label="Close"></a>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="test-email">Email</label>
|
<label for="test-email">Email</label>
|
||||||
<input id="test-email" type="email" name="email" autocomplete="on" />
|
<input id="test-email" type="email" name="email" autocomplete="on" />
|
||||||
<small data-for="test-email" data-hint></small>
|
<small id="test-email-hint" data-hint-for="test-email" data-hint="Test hint"></small>
|
||||||
|
|
||||||
<label for="test-password">Password</label>
|
<label for="test-password">Password</label>
|
||||||
<input id="test-password" type="password" name="password" />
|
<input id="test-password" type="password" name="password" />
|
||||||
<small data-for="login-password" data-hint></small>
|
<small id="test-password-hint" data-hint-for="test-password"></small>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
@ -90,15 +96,33 @@
|
||||||
<!--suppress HtmlUnknownTarget -->
|
<!--suppress HtmlUnknownTarget -->
|
||||||
<script src="../../dist/template.js"></script>
|
<script src="../../dist/template.js"></script>
|
||||||
<script src="../../dist/storage.js"></script>
|
<script src="../../dist/storage.js"></script>
|
||||||
|
<script src="../../dist/validation.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const {$} = window.fwdekker;
|
const {$} = window.fwdekker;
|
||||||
|
|
||||||
const storage = new window.fwdekker.storage.MemoryStorage();
|
const storage = new window.fwdekker.storage.MemoryStorage();
|
||||||
storage.setNumber("test-key", 11);
|
storage.setNumber("test-key", 11);
|
||||||
|
|
||||||
console.log("Expected: 11. Actual: " + storage.getNumber("test-key", 0) + ".");
|
console.log("Expected: 11. Actual: " + storage.getNumber("test-key", 0) + ".");
|
||||||
console.log($("#footer"));
|
|
||||||
|
const validation = window.fwdekker.validation;
|
||||||
|
const testForm = $("#test-form");
|
||||||
|
testForm.addEventListener("submit", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
validation.clearFormValidity(testForm);
|
||||||
|
|
||||||
|
const emailInput = $("#test-email");
|
||||||
|
if (emailInput.value.includes("@")) {
|
||||||
|
validation.showInputValid(emailInput);
|
||||||
|
validation.showMessageSuccess(testForm, "Yay!");
|
||||||
|
} else if (emailInput.value.trim() !== "") {
|
||||||
|
validation.showInputInvalid(emailInput, "Enter a valid email address.");
|
||||||
|
validation.showMessageError(testForm, "Oh no!");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
validation.showMessageWarning($("#global-status-card"), ":D");
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue