diff --git a/Gruntfile.js b/Gruntfile.js index c6c41a3..dec8374 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -14,33 +14,23 @@ module.exports = grunt => { }, }, focus: { - deploy: { - include: ["css", "storage", "template", "validation"], + dev: { + include: ["css", "ts"], + }, + }, + watch: { + css: { + files: ["src/main/**/*.css"], + tasks: ["cssmin"], + }, + ts: { + files: ["src/main/**/*.ts"], + tasks: ["webpack:dev"], }, }, webpack: { - storage: { - entry: "./src/main/js/Storage.ts", - module: { - rules: [ - { - test: /\.ts$/, - use: "ts-loader", - exclude: /node_modules/, - }, - ], - }, - resolve: { - extensions: [".ts"], - }, - output: { - filename: "storage.js", - path: path.resolve(__dirname, "dist/"), - }, - mode: "production", - }, - template: { - entry: "./src/main/js/Template.ts", + options: { + entry: "./src/main/js/Main.ts", module: { rules: [ { @@ -55,49 +45,17 @@ module.exports = grunt => { }, output: { filename: "template.js", - path: path.resolve(__dirname, "dist"), - }, - 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/"), }, + }, + dev: { + mode: "development", + devtool: "inline-source-map", + }, + deploy: { mode: "production", }, }, - watch: { - css: { - files: ["src/main/**/*.css"], - tasks: ["cssmin"], - }, - storage: { - files: ["src/main/js/Storage.ts"], - tasks: ["webpack:storage"], - }, - template: { - files: ["src/main/js/Template.ts"], - tasks: ["webpack:template"], - }, - validation: { - files: ["src/main/js/Validation.ts"], - tasks: ["webpack:validation"], - }, - }, }); grunt.loadNpmTasks("grunt-contrib-clean"); @@ -106,8 +64,9 @@ module.exports = grunt => { grunt.loadNpmTasks("grunt-focus"); grunt.loadNpmTasks("grunt-webpack"); - grunt.registerTask("deploy", ["webpack:storage", "webpack:template", "webpack:validation", "cssmin"]); - grunt.registerTask("deploy:server", ["deploy", "focus:deploy"]); + grunt.registerTask("dev", ["clean", "webpack:dev", "cssmin"]); + grunt.registerTask("dev:server", ["dev", "focus:dev"]); + grunt.registerTask("deploy", ["clean", "webpack:deploy", "cssmin"]); - grunt.registerTask("default", ["deploy"]); + grunt.registerTask("default", ["dev"]); }; diff --git a/README.md b/README.md index 9548507..d871350 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,6 @@ The base template for pages on fwdekker.com. This module contains templating functions (e.g. `nav`, `footer`), CSS libraries, and some common utility methods that are used on nearly all pages anyway. -The main functionality is provided in `template.js` and `template.css`. -There also exist optional modules for easily reusing common code. -Modules can be used stand-alone. -If `template.js` is used, modules should be loaded after `template.js`. -Current available modules are: -* `storage.js` for interfacing with local storage, and - Main module optional. -* `validation.js` for form validation. - Requires main module. - ## Development ### Requirements @@ -27,8 +17,10 @@ $> npm ci ### Building ```shell script -# Build the template in `dist/` for deployment +# Build the tool in `dist/` for development +$> npm run dev +# Same as above, but automatically rerun it whenever files are changed +$> npm run dev:server +# Build the tool in `dist/` for deployment $> npm run deploy -# Run the `deploy` task and automatically rerun it whenever files are changed -$> npm run deploy:server ``` diff --git a/package.json b/package.json index 7840445..d5afec9 100644 --- a/package.json +++ b/package.json @@ -14,14 +14,14 @@ }, "browser": "template.js", "files": [ - "dist/storage.js", "dist/template.js", "dist/template.css" ], "scripts": { "clean": "grunt clean", - "deploy": "grunt deploy", - "deploy:server": "grunt deploy:server" + "dev": "grunt dev", + "dev:server": "grunt dev:server", + "deploy": "grunt deploy" }, "dependencies": { "@picocss/pico": "^1.5.6" diff --git a/src/main/js/Main.ts b/src/main/js/Main.ts new file mode 100644 index 0000000..8d33e61 --- /dev/null +++ b/src/main/js/Main.ts @@ -0,0 +1,7 @@ +import * as template from "./Template"; +import * as storage from "./Storage"; +import * as validation from "./Validation"; + +(window as any).fwdekker = template; +(window as any).fwdekker.storage = storage; +(window as any).fwdekker.validation = validation; diff --git a/src/main/js/Storage.ts b/src/main/js/Storage.ts index eb2a1ba..ad7e333 100644 --- a/src/main/js/Storage.ts +++ b/src/main/js/Storage.ts @@ -5,7 +5,7 @@ export interface Storage { /** * Removes the data from storage. */ - clear(): void + clear(): void; /** * Retrieves an array from storage. @@ -13,7 +13,7 @@ export interface Storage { * @param name the name of the array to retrieve * @param def the value to return if no array is stored with the given name */ - getArray(name: string, def: any[]): any[] + getArray(name: string, def: any[]): any[]; /** * Stores an array. @@ -22,7 +22,7 @@ export interface Storage { * @param value the array to store under the given name * @protected */ - setArray(name: string, value: any[]): void + setArray(name: string, value: any[]): void; /** * Retrieves a boolean from storage. @@ -31,7 +31,7 @@ export interface Storage { * @param def the value to return if no boolean is stored with the given name * @protected */ - getBoolean(name: string, def: boolean): boolean + getBoolean(name: string, def: boolean): boolean; /** * Stores a boolean. @@ -40,7 +40,7 @@ export interface Storage { * @param value the boolean to store under the given name * @protected */ - setBoolean(name: string, value: boolean): void + setBoolean(name: string, value: boolean): void; /** * Retrieves a number from storage. @@ -49,7 +49,7 @@ export interface Storage { * @param def the value to return if no number is stored with the given name * @protected */ - getNumber(name: string, def: number): number + getNumber(name: string, def: number): number; /** * Stores a number. @@ -58,7 +58,7 @@ export interface Storage { * @param value the number to store under the given name * @protected */ - setNumber(name: string, value: number): void + setNumber(name: string, value: number): void; } /** @@ -176,9 +176,3 @@ export class MemoryStorage implements Storage { return this.storage[name] ?? def; } } - - -// Export to `window` -(window as any).fwdekker = (window as any).fwdekker ?? {}; -(window as any).fwdekker.storage = {Storage, LocalStorage, MemoryStorage}; -export {}; diff --git a/src/main/js/Template.ts b/src/main/js/Template.ts index 0c91e25..7fe0217 100644 --- a/src/main/js/Template.ts +++ b/src/main/js/Template.ts @@ -5,7 +5,7 @@ * @param query the type of element to return * @returns the HTML element described by the given string, or `null` if `query` did not match any element */ -function stringToHtml(string: string, query: string): HTMLElement | null { +export function stringToHtml(string: string, query: string): HTMLElement | null { return (new DOMParser()).parseFromString(string, "text/html").body.querySelector(query); } @@ -16,7 +16,7 @@ function stringToHtml(string: string, query: string): HTMLElement | null { * @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 */ -function $(query: string | null | undefined, root?: HTMLElement): HTMLElement | null { +export function $(query: string | null | undefined, root?: HTMLElement): HTMLElement | null { if (query == null) return null; return root === undefined ? document.querySelector(query) : root.querySelector(query); } @@ -28,7 +28,7 @@ function $(query: string | null | undefined, root?: HTMLElement): HTMLElement | * @param root the element to start searching in, or `undefined` if searching should start in `document` * @returns the elements identified by `query` in `root` */ -function $a(query: string, root?: HTMLElement): NodeListOf { +export function $a(query: string, root?: HTMLElement): NodeListOf { return root === undefined ? document.querySelectorAll(query) : root.querySelectorAll(query); } @@ -40,7 +40,7 @@ function $a(query: string, root?: HTMLElement): NodeListOf { * * @param fun the function to run */ -function doAfterLoad(fun: () => void): void { +export function doAfterLoad(fun: () => void): void { if (document.readyState === "complete") { fun(); return; @@ -63,7 +63,7 @@ function doAfterLoad(fun: () => void): void { * @return the `content` attribute of the `` tag identified by `name`, or `null` if the meta tag has no content, * or `undefined` if the meta tag does not exist */ -function getMetaProperty(name: string): string | null | undefined { +export function getMetaProperty(name: string): string | null | undefined { const metaTag = $(`meta[name="${name}"]`); return metaTag == null ? undefined : metaTag.getAttribute("content"); } @@ -252,8 +252,3 @@ doAfterLoad(() => { ); } }); - - -// Export to `window` -(window as any).fwdekker = {$, $a, doAfterLoad, getMetaProperty, stringToHtml}; -export {}; diff --git a/src/main/js/Validation.ts b/src/main/js/Validation.ts index f7f6889..6a52d37 100644 --- a/src/main/js/Validation.ts +++ b/src/main/js/Validation.ts @@ -1,5 +1,4 @@ -if ((window as any).fwdekker == null) throw new Error("Validation module requires main module."); -const {$, $a, doAfterLoad, getMetaProperty} = (window as any).fwdekker; +import {$, $a, doAfterLoad, getMetaProperty} from "./Template"; /** @@ -7,9 +6,13 @@ const {$, $a, doAfterLoad, getMetaProperty} = (window as any).fwdekker; * * @param form the form to hide validation information from */ -function clearFormValidity(form: HTMLFormElement): void { +export function clearFormValidity(form: HTMLFormElement): void { clearMessageStatus(form); - $a("input", form).forEach((input: HTMLInputElement) => clearInputValidity(input)); + $a("input", form).forEach((input: Element) => { + if (!(input instanceof HTMLInputElement)) return; + + clearInputValidity(input); + }); } @@ -20,22 +23,21 @@ function clearFormValidity(form: HTMLFormElement): void { * @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."); - } +export function showMessageType(card: HTMLElement | HTMLFormElement, + message?: string, + type?: "error" | "info" | "success" | "warning"): void { + if (card instanceof HTMLFormElement) + card = $(`article[data-status-for="${card.id}"]`)!; + const output = $("output", card)!; card.classList.remove("hidden", "error", "info", "success", "warning"); if (message == null || type == null) { card.classList.add("hidden"); - $("output", card).innerText = ""; + output.innerText = ""; } else { card.classList.add(type); - $("output", card).innerText = message; + output.innerText = message; } } @@ -44,7 +46,7 @@ function showMessageType(card: HTMLElement | HTMLFormElement, * * @param card the card to clear the message from */ -function clearMessageStatus(card: HTMLElement): void { +export function clearMessageStatus(card: HTMLElement): void { showMessageType(card); } @@ -54,7 +56,7 @@ function clearMessageStatus(card: HTMLElement): void { * @param card the card to show `message` in * @param message the error message to show in `card` */ -function showMessageError(card: HTMLElement, message: string): void { +export function showMessageError(card: HTMLElement, message: string): void { showMessageType(card, message, "error"); } @@ -64,7 +66,7 @@ function showMessageError(card: HTMLElement, message: string): void { * @param card the card to show `message` in * @param message the message to show in `card` */ -function showMessageInfo(card: HTMLElement, message: string): void { +export function showMessageInfo(card: HTMLElement, message: string): void { showMessageType(card, message, "info"); } @@ -74,7 +76,7 @@ function showMessageInfo(card: HTMLElement, message: string): void { * @param card the card to show `message` in * @param message the success message to show in `card` */ -function showMessageSuccess(card: HTMLElement, message: string): void { +export function showMessageSuccess(card: HTMLElement, message: string): void { showMessageType(card, message, "success"); } @@ -84,7 +86,7 @@ function showMessageSuccess(card: HTMLElement, message: string): void { * @param card the card to show `message` in * @param message the success message to show in `card` */ -function showMessageWarning(card: HTMLElement, message: string): void { +export function showMessageWarning(card: HTMLElement, message: string): void { showMessageType(card, message, "warning"); } @@ -94,7 +96,7 @@ function showMessageWarning(card: HTMLElement, message: string): void { * * @param input */ -function clearInputValidity(input: HTMLInputElement): void { +export function clearInputValidity(input: HTMLInputElement): void { input.classList.remove("valid", "invalid"); input.removeAttribute("aria-invalid"); input.removeAttribute("aria-errormessage"); @@ -118,7 +120,7 @@ function clearInputValidity(input: HTMLInputElement): void { * @param input the input to show as invalid * @param message the message explaining what is invalid */ -function showInputInvalid(input: HTMLInputElement, message?: string): void { +export function showInputInvalid(input: HTMLInputElement, message?: string): void { clearInputValidity(input); input.classList.add("invalid"); @@ -145,7 +147,7 @@ function showInputInvalid(input: HTMLInputElement, message?: string): void { * @param input the input to show as valid * @param message the message to show at the input */ -function showInputValid(input: HTMLInputElement, message?: string): void { +export function showInputValid(input: HTMLInputElement, message?: string): void { clearInputValidity(input); input.classList.add("valid"); @@ -170,7 +172,9 @@ function showInputValid(input: HTMLInputElement, message?: string): void { doAfterLoad(() => { if (getMetaProperty("fwd:validation:load-forms") === undefined) return; - $a(".status-card .close").forEach((close: HTMLElement) => { + $a(".status-card .close").forEach((close: Element) => { + if (!(close instanceof HTMLElement)) return; + close.addEventListener("click", (event: MouseEvent) => { event.preventDefault(); @@ -184,13 +188,3 @@ doAfterLoad(() => { 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 {}; diff --git a/src/test/index.html b/src/test/index.html index 95019b4..4e84f0a 100644 --- a/src/test/index.html +++ b/src/test/index.html @@ -95,9 +95,6 @@ - - -