Skip to content

Instantly share code, notes, and snippets.

@guiseek
Created April 17, 2023 02:00
Show Gist options
  • Save guiseek/bd6f0d925fa2cba7e45f261b27bc162e to your computer and use it in GitHub Desktop.
Save guiseek/bd6f0d925fa2cba7e45f261b27bc162e to your computer and use it in GitHub Desktop.
Criar formulários com decorators
import 'reflect-metadata'
type InputType =
| 'checkbox'
| 'color'
| 'datetime-local'
| 'email'
| 'file'
| 'image'
| 'month'
| 'number'
| 'password'
| 'radio'
| 'range'
| 'tel'
| 'text'
| 'time'
| 'url'
type InputAutocomplete =
| 'name'
| 'honorific-prefix'
| 'given-name'
| 'additional-name'
| 'family-name'
| 'honorific-suffix'
| 'nickname'
| 'organization-title'
| 'username'
| 'new-password'
| 'current-password'
| 'one-time-code'
| 'organization'
| 'street-address'
| 'address-line1'
| 'address-line2'
| 'address-line3'
| 'address-level4'
| 'address-level3'
| 'address-level2'
| 'address-level1'
| 'country'
| 'country-name'
| 'postal-code'
| 'cc-name'
| 'cc-given-name'
| 'cc-additional-name'
| 'cc-family-name'
| 'cc-number'
| 'cc-exp'
| 'cc-exp-month'
| 'cc-exp-year'
| 'cc-csc'
| 'cc-type'
| 'transaction-currency'
| 'transaction-amount'
| 'language'
| 'bday'
| 'bday-day'
| 'bday-month'
| 'bday-year'
| 'sex'
| 'url'
| 'photo'
| 'tel'
| 'tel-country-code'
| 'tel-national'
| 'tel-area-code'
| 'tel-local'
| 'tel-local-prefix'
| 'tel-local-suffix'
| 'tel-extension'
| 'email'
| 'impp'
type InputMode =
| 'verbatim'
| 'latin'
| 'latin-name'
| 'latin-prose'
| 'full-width-latin'
| 'kana'
| 'katakana'
| 'numeric'
| 'tel'
| 'email'
| 'url'
interface Input<T = unknown> extends Partial<HTMLInputElement> {
label?: string
type: InputType | 'select'
autocomplete?: InputAutocomplete
required?: boolean
readOnly?: boolean
maxLength?: number
minLength?: number
value?: T | any
}
interface InputText<T = string> extends Input<T> {
type: 'text' | 'password' | 'email' | 'url'
inputmode?: InputMode
value?: T
}
interface InputNumber<T = number> extends Input<T> {
type: 'number'
min: string
max: string
value?: T
}
interface InputDate<T = Date> extends Input<T> {
value?: T
}
interface InputRadio<T = 'on'> extends Input<T> {
type: 'radio'
checked: boolean
value?: T
}
interface InputCheckbox<T = 'on'> extends Input<T> {
type: 'checkbox'
checked?: boolean
value?: T
}
type InputControl<T = unknown> =
| Input<T>
| InputText<T>
| InputNumber<T>
| InputDate<T>
| InputRadio<T>
| InputCheckbox<T>
interface FormGroup {
[k: string]: InputControl<unknown>
}
const formsContainer = new Map()
export function Input<T>(config: InputControl<T>) {
return function (target: any, key: string) {
const metadataKey = `__control_${key}`
Reflect.defineMetadata(metadataKey, config, target)
let group: FormGroup = formsContainer.get(target.constructor.name)
if (!group) group = {}
group[key] = Reflect.getMetadata(metadataKey, new target.constructor())
formsContainer.set(target.constructor.name, group)
}
}
export function create<K extends keyof HTMLElementTagNameMap>(
name: K,
attributes: Partial<HTMLElementTagNameMap[K]> = {}
) {
return Object.assign(document.createElement(name), attributes)
}
export const createForm = <T extends object>(
name: string,
...footer: HTMLElement[]
) => {
const group = formsContainer.get(name) as Record<keyof T, InputControl>
const form = create('form')
const fieldset = create('fieldset')
fieldset.appendChild(create('legend', {innerText: 'Form'}))
if (group) {
for (const control in group) {
const section = create('section')
const {label, type, ...props} = group[control]
const id = `${name}-${control}`.toLowerCase()
let input =
type === 'select'
? create('select', {id, ...props} as HTMLSelectElement)
: create('input', {type, id, ...props} as HTMLInputElement)
const labelEl = create('label', {innerText: label})
labelEl.setAttribute('for', id)
section.append(labelEl, input)
form.append(section, ...footer)
}
}
return form
}
import {Input, create, createForm} from './control'
import './style.css'
class User {
@Input({
type: 'text',
label: 'Nome',
required: true,
})
name: string
@Input({
label: 'Endereço de e-mail',
type: 'email',
required: true,
})
email: string
@Input({
label: 'Nome de usuário',
type: 'text',
required: true,
})
username: string
@Input({
label: 'Senha',
type: 'password',
required: true,
minLength: 6,
})
password: string
@Input({
label: 'Apelido',
type: 'text',
maxLength: 10,
})
nickname: string
@Input({
label: 'Data de nascimento',
type: 'text',
autocomplete: 'bday',
})
birthday: Date
}
const button = create('button', {innerText: 'Enviar'})
document.body.appendChild(createForm('User', button))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment