Title, Description, People
위 3개의 인풋을 받는 form 태그를 렌더링하고 서브밋을 할 수 있는 앱을 ts 객체지향으로 만들어보자.
<body>
<template id="project-input">
<form>
<div class="form-control">
<label for="title">Title</label>
<input type="text" id="title" />
</div>
<div class="form-control">
<label for="description">Description</label>
<textarea id="description" rows="3"></textarea>
</div>
<div class="form-control">
<label for="people">People</label>
<input type="number" id="people" step="1" min="0" max="10" />
</div>
<button type="submit">ADD PROJECT</button>
</form>
</template>
<div id="app"></div>
</body>
class ProjectInput {
templateElement: HTMLTemplateElement;
hostElement: HTMLDivElement;
element: HTMLFormElement;
titleInputElement: HTMLInputElement;
descriptionInputElement: HTMLInputElement;
peopleInputElement: HTMLInputElement;
constructor() {
this.templateElement = document.getElementById('project-input')! as HTMLTemplateElement;
this.hostElement = document.getElementById('app')! as HTMLDivElement;
const importedNode = document.importNode(this.templateElement.content, true);
this.element = importedNode.firstElementChild as HTMLFormElement;
this.titleInputElement = this.element.querySelector('#title') as HTMLInputElement;
this.descriptionInputElement = this.element.querySelector('#description') as HTMLInputElement;
this.peopleInputElement = this.element.querySelector('#people') as HTMLInputElement;
this.attach();
}
private attach() {
this.hostElement.insertAdjacentElement('afterbegin', this.element);
}
}
const prjInput = new ProjectInput();
이렇게 돔을 렌더링 할 수 있다.
이제 서브밋 이벤트를 달아보자.
class ProjectInput {
templateElement: HTMLTemplateElement;
hostElement: HTMLDivElement;
element: HTMLFormElement;
titleInputElement: HTMLInputElement;
descriptionInputElement: HTMLInputElement;
peopleInputElement: HTMLInputElement;
constructor() {
this.templateElement = document.getElementById('project-input')! as HTMLTemplateElement;
this.hostElement = document.getElementById('app')! as HTMLDivElement;
const importedNode = document.importNode(this.templateElement.content, true);
this.element = importedNode.firstElementChild as HTMLFormElement;
this.element.id = 'user-input';
this.titleInputElement = this.element.querySelector('#title') as HTMLInputElement;
this.descriptionInputElement = this.element.querySelector('#description') as HTMLInputElement;
this.peopleInputElement = this.element.querySelector('#people') as HTMLInputElement;
this.configure();
this.attach();
}
private submitHandler(event: Event) {
event.preventDefault();
console.log(this.titleInputElement.value);
}
private configure() {
this.element.addEventListener('submit', this.submitHandler);
}
private attach() {
this.hostElement.insertAdjacentElement('afterbegin', this.element);
}
}
const prjInput = new ProjectInput();
서브밋을 하면
console.log(this.titleInputElement.value);
value를 찾을 수 없다는 에러가 발생한다.
private configure() {
this.element.addEventListener('submit', this.submitHandler.bind(this);
}
configure 함수를 위와 같이 수정하면 해결이 되지만
데코레이터를 이용해서 해결해보자.
function autobind(
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
const adjDescriptor: PropertyDescriptor = {
configurable: true,
get() {
const boundFn = originalMethod.bind(this);
return boundFn;
}
};
return adjDescriptor;
}
class ProjectInput {
// ...
@autobind
private submitHandler(event: Event) {
event.preventDefault();
console.log(this.titleInputElement.value);
}
}
다음은 사용자 입력을 가져오자.
class ProjectInput {
// ...
private gatherUserInput(): [string, string, number] | void {
const enteredTitle = this.titleInputElement.value;
const enteredDescription = this.descriptionInputElement.value;
const enteredPeople = this.peopleInputElement.value;
if (
// 여기서 validation 체크
) {
alert('Invalid input, please try again!');
return;
} else {
return [enteredTitle, enteredDescription, +enteredPeople];
}
}
private clearInput() {
this.titleInputElement.value = '';
this.descriptionInputElement.value = '';
this.peopleInputElement.value = '';
}
@autobind
private submitHandler(event: Event) {
event.preventDefault();
const userInput = this.gatherUserInput();
if (Array.isArray(userInput)) {
const [title, desc, people] = userInput;
console.log(title, desc, people);
this.clearInput();
}
}
}
gatherUserInput 함수는 유효검 검사를 통과하면 사용자 인풋 값인 튜플을 반환하고 그렇지 않을 때에는 빈 값을 반환한다.
사용자 인풋 값이 튜플인지를 알기 위해서는 배열인지를 확인하는 것으로 대체했다.
서브밋했을 때 인풋을 비워주는 clearInput도 추가했다.
유효성 검사를 위한 함수를 만들자.
interface Validatable {
value: string | number;
required?: boolean;
minLength?: number;
maxLength?: number;
min?: number;
max?: number;
}
function validate(validatableInput: Validatable) {
let isValid = true;
if (validatableInput.required) {
isValid = isValid && validatableInput.value.toString().trim().length !== 0;
}
if (validatableInput.minLength != null && typeof validatableInput.value === 'string') {
isValid = isValid && validatableInput.value.length > validatableInput.minLength;
}
return isValid;
}
class ProjectInput {
// ...
private gatherUserInput(): [string, string, number] | void {
const enteredTitle = this.titleInputElement.value;
const enteredDescription = this.descriptionInputElement.value;
const enteredPeople = this.peopleInputElement.value;
if (
validate({ value: enteredTitle, required: true, minLength: 5 }) &&
validate({ value: enteredDescription, required: true, minLength: 5 }) &&
validate({ value: enteredPeople, required: true, minLength: 5 })
) {
alert('Invalid input, please try again!');
return;
} else {
return [enteredTitle, enteredDescription, +enteredPeople];
}
}
}
minLength가 0이면 유효성 검사가 false가 되는 것을 막아보자.
validate 함수에서
validatableInput.minLength != null
위는 =가 하나로, null과 undefined를 모두 포함한다.
이렇게 하면 0으로 설정할 때 원하는 동작을 할 수 있다.
조금 더 정리를 하면
class ProjectInput {
// ...
private gatherUserInput(): [string, string, number] | void {
const enteredTitle = this.titleInputElement.value;
const enteredDescription = this.descriptionInputElement.value;
const enteredPeople = this.peopleInputElement.value;
const titleValidatable: Validatable = {
value: enteredTitle,
required: true
};
const descriptionValidatable: Validatable = {
value: enteredDescription,
required: true,
minLength: 5
};
const peopleValidatable: Validatable = {
value: +enteredPeople,
required: true,
min: 1,
max: 5
};
if (
!validate(titleValidatable) ||
!validate(descriptionValidatable) ||
!validate(peopleValidatable)
) {
alert('Invalid input, please try again!');
return;
} else {
return [enteredTitle, enteredDescription, +enteredPeople];
}
}
}