-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from Travelopia/feature/form
Form Component
- Loading branch information
Showing
14 changed files
with
589 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/** | ||
* Internal dependencies. | ||
*/ | ||
import { TPFormFieldElement } from './tp-form-field'; | ||
|
||
/** | ||
* Form Validator. | ||
*/ | ||
export interface TPFormValidator { | ||
validate: { ( field: TPFormFieldElement ): boolean }; | ||
getErrorMessage: { ( field: TPFormFieldElement ): string }; | ||
} | ||
|
||
/** | ||
* Window. | ||
*/ | ||
declare global { | ||
interface Window { | ||
tpFormValidators: { | ||
[ key: string ]: TPFormValidator; | ||
} | ||
tpFormErrors: { | ||
[ key: string ]: string; | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0"> | ||
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | ||
<title>Web Component: Form</title> | ||
|
||
<link rel="stylesheet" href="../../dist/form/style.css" media="all"> | ||
<script type="module" src="../../dist/form/index.js"></script> | ||
|
||
<style> | ||
form { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 10px; | ||
max-width: 600px; | ||
margin: 40px auto; | ||
} | ||
|
||
tp-form-field, | ||
label { | ||
display: block; | ||
} | ||
|
||
input, | ||
select, | ||
textarea { | ||
width: 100%; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<main> | ||
<tp-form prevent-submit="yes"> | ||
<form action="#"> | ||
<tp-form-field required="yes"> | ||
<label>Field 1</label> | ||
<input type="text" name="field_1"> | ||
</tp-form-field> | ||
<tp-form-field required="yes" email="yes"> | ||
<label>Field 2</label> | ||
<input type="email" name="field_2"> | ||
</tp-form-field> | ||
<tp-form-field required="yes"> | ||
<label>Field 3</label> | ||
<select type="text" name="field_3"> | ||
<option value="">Select value</option> | ||
<option value="value_1">Value 1</option> | ||
<option value="value_2">Value 2</option> | ||
<option value="value_3">Value 3</option> | ||
</select> | ||
</tp-form-field> | ||
<tp-form-field min-length="4" max-length="8"> | ||
<label>Field 4</label> | ||
<textarea name="field_4"></textarea> | ||
</tp-form-field> | ||
<tp-form-submit submitting-text="Submitting..."> | ||
<button type="submit">Submit</button> | ||
</tp-form-submit> | ||
</form> | ||
</tp-form> | ||
</main> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/** | ||
* Styles. | ||
*/ | ||
import './style.scss'; | ||
|
||
/** | ||
* Validators. | ||
*/ | ||
import { TPFormValidator } from './definitions'; | ||
import * as required from './validators/required'; | ||
import * as email from './validators/email'; | ||
import * as minLength from './validators/min-length'; | ||
import * as maxLength from './validators/max-length'; | ||
|
||
const validators = [ | ||
required, | ||
email, | ||
minLength, | ||
maxLength, | ||
]; | ||
|
||
/** | ||
* Register Validators and Errors. | ||
*/ | ||
window.tpFormValidators = {}; | ||
window.tpFormErrors = {}; | ||
|
||
validators.forEach( ( | ||
{ name, validator, errorMessage }: { name: string, validator: TPFormValidator, errorMessage: string } | ||
): void => { | ||
window.tpFormValidators[ name ] = validator; | ||
window.tpFormErrors[ name ] = errorMessage; | ||
} ); | ||
|
||
/** | ||
* Components. | ||
*/ | ||
import { TPFormElement } from './tp-form'; | ||
import { TPFormFieldElement } from './tp-form-field'; | ||
import { TPFormErrorElement } from './tp-form-error'; | ||
import { TPFormSubmitElement } from './tp-form-submit'; | ||
|
||
/** | ||
* Register Components. | ||
*/ | ||
customElements.define( 'tp-form', TPFormElement ); | ||
customElements.define( 'tp-form-field', TPFormFieldElement ); | ||
customElements.define( 'tp-form-error', TPFormErrorElement ); | ||
customElements.define( 'tp-form-submit', TPFormSubmitElement ); | ||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/** | ||
* TP Form Error. | ||
*/ | ||
export class TPFormErrorElement extends HTMLElement { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
/** | ||
* Internal dependencies. | ||
*/ | ||
import { TPFormErrorElement } from './tp-form-error'; | ||
|
||
/** | ||
* TP Form Field. | ||
*/ | ||
export class TPFormFieldElement extends HTMLElement { | ||
/** | ||
* Connected callback. | ||
*/ | ||
connectedCallback(): void { | ||
const field = this.getField(); | ||
field?.addEventListener( 'keyup', this.handleFieldChanged.bind( this ) ); | ||
field?.addEventListener( 'change', this.handleFieldChanged.bind( this ) ); | ||
} | ||
|
||
/** | ||
* Update validation when the field has changed. | ||
*/ | ||
handleFieldChanged(): void { | ||
if ( this.getAttribute( 'valid' ) || this.getAttribute( 'error' ) ) { | ||
this.validate(); | ||
} | ||
} | ||
|
||
/** | ||
* Get observed attributes. | ||
* | ||
* @return {Array} List of observed attributes. | ||
*/ | ||
static get observedAttributes(): string[] { | ||
return [ 'valid', 'error' ]; | ||
} | ||
|
||
/** | ||
* Attribute changed callback. | ||
* | ||
* @param {string} name Attribute name. | ||
* @param {string} oldValue Old value. | ||
* @param {string} newValue New value. | ||
*/ | ||
attributeChangedCallback( name: string = '', oldValue: string = '', newValue: string = '' ): void { | ||
if ( ( 'valid' === name || 'error' === name ) && oldValue !== newValue ) { | ||
this.dispatchEvent( new CustomEvent( 'validate', { bubbles: true } ) ); | ||
} | ||
this.update(); | ||
} | ||
|
||
/** | ||
* Update component. | ||
*/ | ||
update(): void { | ||
const { tpFormValidators } = window; | ||
if ( ! tpFormValidators ) { | ||
return; | ||
} | ||
|
||
const error: string = this.getAttribute( 'error' ) ?? ''; | ||
if ( '' !== error && error in tpFormValidators && 'function' === typeof tpFormValidators[ error ].getErrorMessage ) { | ||
this.setErrorMessage( tpFormValidators[ error ].getErrorMessage( this ) ); | ||
} else { | ||
this.removeErrorMessage(); | ||
} | ||
} | ||
|
||
/** | ||
* Get the associated field. | ||
* | ||
* @return {HTMLElement} The associated field for this component. | ||
*/ | ||
getField(): HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | null { | ||
return this.querySelector( 'input,select,textarea' ); | ||
} | ||
|
||
/** | ||
* Validate this field. | ||
* | ||
* @return {boolean} Whether this field passed validation. | ||
*/ | ||
validate(): boolean { | ||
// Look for validators. | ||
const { tpFormValidators } = window; | ||
if ( ! tpFormValidators ) { | ||
return true; | ||
} | ||
|
||
// Prepare error and valid status. | ||
let valid: boolean = true; | ||
let error: string = ''; | ||
const allAttributes: string[] = this.getAttributeNames(); | ||
|
||
// Traverse all attributes to see if we find a matching validator. | ||
allAttributes.every( ( attributeName: string ): boolean => { | ||
if ( attributeName in tpFormValidators && 'function' === typeof tpFormValidators[ attributeName ].validate ) { | ||
// We found one, lets validate the field. | ||
const isValid: boolean = tpFormValidators[ attributeName ].validate( this ); | ||
|
||
// Looks like we found an error! | ||
if ( false === isValid ) { | ||
valid = false; | ||
error = attributeName; | ||
return false; | ||
} | ||
} | ||
|
||
// No error found, all good. | ||
return true; | ||
} ); | ||
|
||
// Check if the field is valid or not. | ||
if ( valid ) { | ||
this.setAttribute( 'valid', 'yes' ); | ||
this.removeAttribute( 'error' ); | ||
} else { | ||
this.removeAttribute( 'valid' ); | ||
this.setAttribute( 'error', error ); | ||
} | ||
|
||
// Return validity. | ||
return valid; | ||
} | ||
|
||
/** | ||
* Set the error message. | ||
* | ||
* @param {string} message Error message. | ||
*/ | ||
setErrorMessage( message: string = '' ): void { | ||
const error: TPFormErrorElement | null = this.querySelector( 'tp-form-error' ); | ||
if ( error ) { | ||
error.innerHTML = message; | ||
} else { | ||
const errorElement: TPFormErrorElement = document.createElement( 'tp-form-error' ); | ||
errorElement.innerHTML = message; | ||
this.appendChild( errorElement ); | ||
} | ||
|
||
this.dispatchEvent( new CustomEvent( 'validation-error', { bubbles: true } ) ); | ||
} | ||
|
||
/** | ||
* Remove the error message. | ||
*/ | ||
removeErrorMessage(): void { | ||
this.querySelector( 'tp-form-error' )?.remove(); | ||
this.dispatchEvent( new CustomEvent( 'validation-success', { bubbles: true } ) ); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/** | ||
* TP Form Submit. | ||
*/ | ||
export class TPFormSubmitElement extends HTMLElement { | ||
/** | ||
* Get observed attributes. | ||
* | ||
* @return {Array} List of observed attributes. | ||
*/ | ||
static get observedAttributes(): string[] { | ||
return [ 'submitting-text', 'original-text', 'submitting' ]; | ||
} | ||
|
||
/** | ||
* Attribute changed callback. | ||
* | ||
* @param {string} _name Attribute name. | ||
* @param {string} oldValue Old value. | ||
* @param {string} newValue New value. | ||
*/ | ||
attributeChangedCallback( _name: string = '', oldValue: string = '', newValue: string = '' ): void { | ||
if ( oldValue !== newValue ) { | ||
this.update(); | ||
} | ||
} | ||
|
||
/** | ||
* Update this component. | ||
*/ | ||
update(): void { | ||
// Get submit button. | ||
const submitButton: HTMLButtonElement | null = this.querySelector( 'button[type="submit"]' ); | ||
if ( ! submitButton ) { | ||
return; | ||
} | ||
|
||
// Prepare submit button text. | ||
const submittingText: string = this.getAttribute( 'submitting-text' ) ?? ''; | ||
const originalText: string = this.getAttribute( 'original-text' ) ?? submitButton.innerHTML; | ||
|
||
// Check if we are submitting. | ||
if ( 'yes' === this.getAttribute( 'submitting' ) ) { | ||
submitButton.setAttribute( 'disabled', 'disabled' ); | ||
this.setAttribute( 'original-text', originalText ); | ||
submitButton.innerHTML = submittingText; | ||
} else { | ||
submitButton.removeAttribute( 'disabled' ); | ||
this.removeAttribute( 'submitting' ); | ||
this.removeAttribute( 'original-text' ); | ||
submitButton.innerHTML = originalText; | ||
} | ||
} | ||
} |
Oops, something went wrong.