HTML Form 마크업, Vanila JS 를 통한 인풋 검증 및 AJAX Submit 요령

JS에서의 AJAX 등과 같은 다양한 처리로 인해, Form 자체의 순수한 기능을 쓸 일은 거의 없어졌습니다. 그럼에도 불구하고 form 의 기능을 어느정도 사용하면 웹 표준을 준수함과 물론, 코드의 유지보수성이 향상되는 효과가 있는데요, 어떻게 작성하면 되는지 그 방법을 한번 공유해보고자 합니다.

기본적인 Form 의 구조

<form>
	<input type="text" name="name1" />
	<input type="text" name="name2" />
	...
	...
	<!-- (Toast UI Grid)외부 라이브러리로 동작하는 input 예시 -->
	<div class="tui-datepicker-input tui-datetime-input tui-has-focus">
		<input type="text" name="startDate" aria-label="Date" autocomplete="off" />
		<span class="tui-ico-date"></span>
		<div id="startpicker-container"></div>
	</div>
	
	<button type="submit">저장</div>
	<button type="reset">리셋</div>
</form>

AJAX 요청을 한 이상, method나 action 속성을 지정할 일이 거의 없어졌습니다. 따라서 form 태그를 구성하지 않고 input과 버튼만으로 구성하는 경우도 종종 볼 수 있는데요, 그럼에도 불구하고 Form 태그를 사용하는것이 저는 옳다고 생각합니다. 그 이유로는

  1. 웹표준을 준수하게 되어 SEO 등의 여러가지 측면에서 이득이 발생할 수 있습니다.
  2. input 입력 후 Enter를 눌러도 Submit 이벤트가 발생하여 ‘웹 접근성’을 쉽게 준수할 수가 있습니다. 굳이 input 에 키보드 이벤트를 바인딩 하지 않아도 되므로, 더욱 목적지향적으로 코드를 간결하게 작성할 수 있게됩니다.
  3. form 안에 name 속성을 가진 여러 input 이 있다는 연관관계가 손쉽게 형성됩니다. form submit 이벤트 핸들러에서 FormData 객체를 생성할 때, event target(form element)을 매개변수로 전달해주면, name-value 가 key-value 로 매핑돼있는 객체를 손쉽게 얻을 수 있습니다.
  4. ‘저장’과 ‘리셋’ 버튼 각각에 click 이벤트를 바인딩 하지 않아도, form 요소에만 submit, reset 이벤트를 바인딩하면 되므로, 코드 목적성을 분명히 함과 동시에 Form의 사용 철학 또한 분명히 할 수 있게 됩니다. 특히 '리셋'과 같은 경우는, 외부 라이브러리로 동작하는 input 이 아니라면 별다른 이벤트 리스너를 추가하지 않아도 손쉽게 리셋할 수가 있게 됩니다.

Form 이벤트 바인딩

const form = document.querySelector('form selector')
form.addEventListener('submit', handleSubmit);
form.addEventListener('reset', handleReset);

표준에 맞게 HTML 을 구성하면 이렇게 더욱 목적이 분명하게 이벤트를 바인딩 할 수가 있습니다.

Form 이벤트 핸들러 구조

submit

function handleSubmit(e) {
	e.preventDefault();

	var formElement = e.target;

	var formData = new FormData(formElement);
	var formDataList = Array.from(formData); // IE 미지원

  // Validation 검증 - ex) formDataList 를 순회하며 값을 체크한다.
	

	// formData 를 파라미터로 전달하여 AJAX 수행
}
  • Submit 핸들러에서는 필수적으로 preventDefault 해야 합니다. 폼의 기본 동작이 아닌 AJAX로 http request 보낼거니까요. (preventDefault, stopPropagation 의 자세한 동작을 알고 싶다면, 여기 를 참고해주시기 바랍니다)
  • html 에서 form 내부에 name 속성들이 있는 input 을 정의해줬으므로, e.target 을 통해 name-value 가 key-value 로 매핑된 객체를 손쉽게 만들 수 있습니다. Form 종류에 상관없이 그 Form 에 맞는 객체를 구성하므로, 유지보수에 탁월합니다. 만약 handleSubmit이것이 버튼 클릭 이벤트 핸들러였다면, document.querySelector 로 특정 form을 비효율적으로 가져와야 할 것입니다.
  • Validation 체크는 자유롭게 진행하시면 됩니다.
  • 마지막에 formData 객체를 파라미터로 전달하여 AJAX 수행하면 됩니다. 경우에 따라 AJAX API에서 요구하는 데이터 형태가 다를 수도 있는데 재량껏 변형하시면 됩니다.

preventDefault 위치는 아무데나 두셔도 되는데, 주로 읽기 편하라고 맨 위에 두는 편입니다. 아무데나 둬도 되는 이유는 어차피 이벤트가 모두 전파되고 난 마지막(root)에 default 이벤트가 발생하기 때문입니다. 현재 handleSubmit 함수는 이벤트가 모두 전파된 상태도 아닌 상태이기 때문에 코드 어디에서 preventDefault 해도 괜찮은 겁니다.

reset

function handleReset(e) {

	// preventDefault 하지 않는다.

  // Reset 시 따로 처리하고 싶은 로직 작성(ex. 변경하고 싶은 상태 변경)

	// 단순 비우기 reset이 아닌 초기값을 할당하고 싶은 것은 task queue 에 적재하여
	// 디폴트 이벤트보다 나중에 실행되도록 한다.
	window.setTimeout(customReset)
}

function customReset() { /* ... */ }
  • 저는 브라우저의 기능을 최대한 활용하고 싶어서 preventDefault 는 따로 하지 않았습니다.
  • handleReset 내부에는 디폴트 리셋이 발생하면서 처리하고 싶은 로직을 따로 자유롭게 구성하면 됩니다.
  • 브라우저에서 제공하는 단순 값 비우기 리셋이 아닌, 초기값을 할당하는 등의 커스텀 로직을 실행하고 싶으면 함수를 따로 빼서 그것을 호출하면 됩니다. 단, setTimeout 을 이용해서 Task Queue 에 적재되어 마지막에 이벤트루프가 실행하도록 설정해야 합니다. 원래는 디폴트 이벤트가 나중에 호출되어 커스텀 로직이 적용되지 않기 때문입니다.

이벤트루프에 관한 설명을 nhn meetup 에서 공부하시는 것을 추천드립니다. 매우 설명이 잘 돼있고 중요한 개념입니다!!

어떠한 submit 필요 없이 단순 input 만 필요한 경우에도 form 태그를 구성하는 것을 추천드립니다. 그 이유는 맨 앞서 말한 접근성 측면과 같은 이유와 같습니다.

좋은 웹페이지 즐겨찾기