tiny-essentials
Version:
Collection of small, essential scripts designed to be used across various projects. These simple utilities are crafted for speed, ease of use, and versatility.
229 lines (228 loc) • 9.66 kB
JavaScript
import TinyHtmlTemplate from './TinyHtmlTemplate.mjs';
/**
* TinyHtmlForm is a helper for creating and managing <form> elements
* with full attribute support and validation.
*
* @example
* const form = new TinyHtmlForm({
* action: '/submit',
* method: 'post',
* enctype: 'multipart/form-data',
* autocomplete: 'on',
* novalidate: true,
* target: '_blank',
* tags: ['form', 'signup'],
* mainClass: 'primary-form'
* });
*
* @extends TinyHtmlTemplate<HTMLFormElement>
*/
class TinyHtmlForm extends TinyHtmlTemplate {
/**
* Creates a new TinyHtmlForm instance.
*
* The constructor accepts a single `config` object with named options to
* configure the created `<form>` element. Validation is performed for each
* option and a `TypeError` is thrown when an invalid type or value is
* provided. Deprecated attributes are accepted but will emit a console.warn.
*
* @param {Object} [config={}] - Configuration object.
* @param {string} [config.action=""] - The URL that processes the form submission.
* If omitted the form will submit to the current document URL.
* @param {'get'|'post'|'dialog'} [config.method='get'] - The HTTP method to submit the form with.
* - `get` (default): form data appended to the action URL.
* - `post`: form data sent in request body; `enctype` is relevant.
* - `dialog`: when the form is inside a `<dialog>`, closes the dialog and fires a submit event without sending data.
* @param {'application/x-www-form-urlencoded'|'multipart/form-data'|'text/plain'} [config.enctype]
* The encoding type for form submission (only used when method is `post`).
* @param {string} [config.acceptCharset] - The character encoding accepted by the server (e.g. "UTF-8").
* The specification recommends "UTF-8".
* @param {'none'|'off'|'sentences'|'on'|'words'|'characters'} [config.autocapitalize]
* Controls automatic capitalization for text inputs inside the form.
* @param {'on'|'off'} [config.autocomplete] - Hint to the browser whether autofill is allowed for controls in the form.
* @param {string} [config.name] - The form name. Must be a non-empty string when provided.
* @param {string} [config.rel] - Space-separated relationship tokens describing the form's link semantics.
* @param {boolean} [config.novalidate=false] - When true, disables built-in form validation on submit.
* @param {string} [config.target] - Target browsing context for the response.
* Can be one of `_self`, `_blank`, `_parent`, `_top`, `_unfencedTop` or a valid browsing context name
* (pattern: starts with a letter/underscore, then alphanumeric/`-`/`_`).
* @param {string|string[]|Set<string>} [config.tags=[]] - Initial CSS classes to apply to the form element.
* @param {string} [config.mainClass=''] - Main CSS class to append to the element.
*
* @throws {TypeError} If `action` is provided and is not a string.
* @throws {TypeError} If `method` is not one of 'get', 'post' or 'dialog'.
* @throws {TypeError} If `enctype` is provided and is not one of the allowed encodings.
* @throws {TypeError} If `acceptCharset` is provided and is not a string.
* @throws {TypeError} If `autocapitalize` is provided and is not one of the allowed tokens.
* @throws {TypeError} If `autocomplete` is provided and is not 'on' or 'off'.
* @throws {TypeError} If `name` is provided and is not a non-empty string.
* @throws {TypeError} If `rel` is provided and is not a string.
* @throws {TypeError} If `novalidate` is not a boolean.
* @throws {TypeError} If `target` is not a valid target keyword or a valid browsing context name.
*/
constructor({ action = '', method = 'get', enctype, acceptCharset, autocapitalize, autocomplete, name, rel, novalidate = false, target, tags = [], mainClass = '', } = {}) {
super(document.createElement('form'), tags, mainClass);
// action
this.action = action;
// method
this.method = method;
// enctype
if (enctype !== undefined)
this.enctype = enctype;
// accept-charset
if (acceptCharset !== undefined)
this.acceptCharset = acceptCharset;
// autocapitalize
if (autocapitalize !== undefined)
this.autocapitalize = autocapitalize;
// autocomplete
if (autocomplete !== undefined)
this.autocomplete = autocomplete;
// name
if (name !== undefined)
this.name = name;
// rel
if (rel !== undefined)
this.rel = rel;
// novalidate
this.novalidate = novalidate;
// target
if (target !== undefined)
this.target = target;
}
/** @param {string} action */
set action(action) {
if (typeof action !== 'string')
throw new TypeError('TinyForm: "action" must be a string.');
if (action)
this.setAttr('action', action);
}
/** @returns {string|null} */
get action() {
return this.attrString('action');
}
/** @param {'get'|'post'|'dialog'} method */
set method(method) {
if (typeof method !== 'string')
throw new TypeError('TinyForm: "method" must be a string.');
const valid = ['get', 'post', 'dialog'];
const norm = method.toLowerCase();
if (!valid.includes(norm))
throw new TypeError(`TinyForm: "method" must be one of ${valid.join(', ')}.`);
this.setAttr('method', norm);
}
/** @returns {string|null} */
get method() {
return this.attrString('method');
}
/** @param {'application/x-www-form-urlencoded'|'multipart/form-data'|'text/plain'} enctype */
set enctype(enctype) {
const valid = ['application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain'];
if (!valid.includes(enctype))
throw new TypeError(`TinyForm: "enctype" must be one of ${valid.join(', ')}.`);
this.setAttr('enctype', enctype);
}
/** @returns {string|null} */
get enctype() {
return this.attrString('enctype');
}
/** @param {string} charset */
set acceptCharset(charset) {
if (typeof charset !== 'string')
throw new TypeError('TinyForm: "acceptCharset" must be a string.');
if (charset.toUpperCase() !== 'UTF-8')
console.warn('TinyForm: Only "UTF-8" is recommended for accept-charset.');
this.setAttr('accept-charset', charset);
}
/** @returns {string|null} */
get acceptCharset() {
return this.attrString('accept-charset');
}
/** @param {'none'|'off'|'sentences'|'on'|'words'|'characters'} value */
set autocapitalize(value) {
const valid = ['none', 'off', 'sentences', 'on', 'words', 'characters'];
if (!valid.includes(value))
throw new TypeError(`TinyForm: "autocapitalize" must be one of ${valid.join(', ')}.`);
this.setAttr('autocapitalize', value);
}
/** @returns {string|null} */
get autocapitalize() {
return this.attrString('autocapitalize');
}
/** @param {'on'|'off'} value */
set autocomplete(value) {
if (!['on', 'off'].includes(value))
throw new TypeError('TinyForm: "autocomplete" must be "on" or "off".');
this.setAttr('autocomplete', value);
}
/** @returns {string|null} */
get autocomplete() {
return this.attrString('autocomplete');
}
/** @param {string} name */
set name(name) {
if (typeof name !== 'string')
throw new TypeError('TinyForm: "name" must be a string.');
if (name.trim() === '')
throw new TypeError('TinyForm: "name" cannot be empty.');
this.setAttr('name', name);
}
/** @returns {string|null} */
get name() {
return this.attrString('name');
}
/** @param {string} rel */
set rel(rel) {
if (typeof rel !== 'string')
throw new TypeError('TinyForm: "rel" must be a string.');
this.setAttr('rel', rel);
}
/** @returns {string|null} */
get rel() {
return this.attrString('rel');
}
/** @param {boolean} novalidate */
set novalidate(novalidate) {
if (typeof novalidate !== 'boolean')
throw new TypeError('TinyForm: "novalidate" must be a boolean.');
if (novalidate)
this.addProp('novalidate');
else
this.removeProp('novalidate');
}
/** @returns {boolean} */
get novalidate() {
return this.hasProp('novalidate');
}
/** @param {string} target */
set target(target) {
if (typeof target !== 'string')
throw new TypeError('TinyForm: "target" must be a string.');
const validTargets = ['_self', '_blank', '_parent', '_top', '_unfencedTop'];
const validName = /^[a-zA-Z_][\w-]*$/;
if (!validTargets.includes(target) && !validName.test(target))
throw new TypeError(`TinyForm: "target" must be a valid context name or one of ${validTargets.join(', ')}.`);
this.setAttr('target', target);
}
/** @returns {string|null} */
get target() {
return this.attrString('target');
}
/**
* Programmatically submits the form.
* @returns {this}
*/
submit() {
this.el.submit();
return this;
}
/**
* Resets the form.
* @returns {this}
*/
reset() {
this.el.reset();
return this;
}
}
export default TinyHtmlForm;