1. HTML Form

HTML 폼은 사용자와 웹사이트 또는 애플리케이션이 서로 상호 작용하는 것 중 중요한 기술 중 하나이다. 폼은 사용자가 웹사이트에 데이터를 전송하기 위해 사용한다.

폼은 하나 이상의 위젯(입력 양식 태그)으로 만들어진다.

  • input
  • textarea
  • button
  • select
  • checkbox
  • radio button
  • submit button

1-1. form

모든 HTML 폼은 <form> 엘리먼트로 시작된다. <form> 엘리먼트는 일반적으로 form과 form의 동작을 결정하는 attribute를 정의한다.

<form action="/my-handling-form-page" method="post">
  <!-- 위젯 -->
</form>

폼 엘리먼트를 사용할 때에는 폼이 동작하는 방식을 설정하는 일부 attribute들을 지정해야 하고, 대부분 선택적이지만 action, method는 필수적으로 설정해야 한다.

  • action : 데이터를 전송할 URL을 지정한다.
  • method : HTTP 전송 방식을 지정한다.(GET, POST)

GET

  • GET 방식은 전송 URL에 입력 데이터를 쿼리스트링으로 보내는 방식이다. 예) http://jsonplaceholder.typicode.com/posts?userId=1&id=1
  • REST API에서 GET 메서드는 모든 또는 특정 리소스의 조회를 요청한다.

POST

  • POST 방식은 Request Body에 담아 보내는 방식이다. 예) http://jsonplaceholder.typicode.com/posts
  • REST API에서 POST 메서드는 특정 리소스의 생성을 요청한다.

1-2. input

<input> 엘리먼트는 form 엘리먼트 중에서 가장 중요한 엘리먼트로 사용자로부터 데이터를 입력받기 위해 사용된다.

input 엘리먼트의 가장 중요한 attribute는 type이다.

type : input 엘리먼트가 어떻게 입력을 받을 정의한다.

사용할 수 있는 input type은 다음과 같다.

  • button
  • checkbox
  • color
  • date
  • datetime-local
  • email
  • file
  • hidden
  • image
  • month
  • number
  • password
  • radio
  • range
  • reset
  • search
  • submit
  • tel
  • text
  • time
  • url
  • week

공통 속성

  • autofocus : boolean 타입의 속성을 지정하여 페이지 로드 시 엘리먼트에 포커스가 자동으로 지정되도록 할 수 있다. document 내에서 오직 하나의 form 엘리먼트만이 이 속성을 지정할 수 있다. (default false)
  • disabled : boolean 타입의 속성을 지정하여 엘리먼트가 사용자와 상호작용을 할 수 있을지 여부를 지정할 수 있다. disabled 속성은 따로 지정하지 않으면 상속되고 기본적으로는 enabled 상태이다.(default false)
  • form : form 속성에 document 내에 다른 form 엘리먼트의 id 값을 입력하면, 다른 form 엘리먼트 안에 있더라도 form control을 외부에 있는 form과 연결할 수 있다.
  • name
  • value

Text

Text type field는 가장 기초적이고 많이 사용되는 폼 위젯이다. input 엘리먼트의 type 속성에 text를 입력하거나, type 속성을 생략하면(default) text type으로 동작한다.

Text input fields Text type을 포함하여 모든 text 입력하는 동작을 하는 폼 위젯들은 몇몇 공통된 속성을 가지고 있다.(text, password, email, hidden 등)

  • readonly : 입력된 값을 수정할 수 없지만 전송 가능하다.
  • disabled : 입력된 값을 수정할 수 없고, 전송할 수 없다.
  • placeholder : textbox에 표시되는 텍스트로 textbox의 목적을 간략하게 설명하는데 사용한다.
  • size : size를 지정한다.
  • maxlength : 입력할 수 있는 maximum 글자 수를 지정한다.
  • spellcheck : browser가 지원하면 spell check 기능을 사용할 수 있다.
<input type="text" id="comment" name="comment" value="I'm a text field" />

Password

password type을 지정해도 text 입력 시에 특별한 제약사항은 없지만, 입력된 text는 dot이나 별표와 같은 문자로 감춰진다.

주의할 점은 password type으로 지정하는 것은 단순히 UI 상의 기능이므로 데이터 전송 시 보안까지는 책임지지 않는다. user form data를 보호하기 위해서는 https를 사용해서 data 전송 전에 암호화를 해야 한다.

<input type="password" id="pwd" name="pwd" />

Hidden

또 다른 text control의 종류로는 hidden type이 있다. hidden type은 사용자에게 보이지 않는 폼 컨트롤을 생성할 때 사용한다.(사용자에게는 보이지 않지만 서버로는 전송된다.)

예를 들어, timestamp 등의 data를 사용자에게는 노출하지 않고 전송하기 위해 사용할 수 있다.

<input type="hidden" id="timestamp" name="timestamp" value="1286705410" />

Check box

input type에 checkbox를 지정하면 Check box control을 생성할 수 있다.

<input type="checkbox" id="carrots" name="carrots" value="carrots" checked />

checked 속성을 가지고 있으면 페이지가 로드될 때 확인란이 체크된다. 그리고 확인란 또는 연결된 label을 클릭하면 확인란이 토글 된다.

Checkable items : checkboxes and radio buttons check box와 radio button 컨트롤은 checked 속성을 사용한다. 이 속성은 위젯이 check되었는지를 나타낸다.

  • Checkable item들은 일반적인 다른 폼 위젯과 같이 동작하지 않는다.
  • 다른 위젯은 name 속성을 가지고 있는 경우 값이 채워지지 않았더라도 전송한다.
  • Checkable item들은 오직 checked 상태일 때만 데이터를 전송한다.(name이 있더라도)
  • 만약 check 되었지만 value가 없다면 on으로 전송된다.
  • 일반적으로 lebel, fieldset/legend 엘리먼트와 함께 사용된다.

Radio button

input type에 radio를 지정하면 radio button control을 생성할 수 있다.

<input type="radio" id="soup" name="meal" checked />

라디오 버튼은 여러개를 묶어서 사용할 수 있다. name 속성에 동일한 값을 주면 같은 버튼 그룹으로 엮을 수 있다. 그리고 한 번에 오직 하나의 버튼만 선택할 수 있다. 즉, 버튼중에 하나가 선택되면 다른 버튼들은 자동으로 선택 해제되게 된다.

form이 전송될 때, 선택된 하나의 radio button만 전송된다. 만약 선택된 버튼이 없다면 전달되지 않는다. 그리고 그룹내에 라디오 버튼 중 하나가 선택되면 form을 reset하기 전에는 모든 버튼의 선택을 취소할 수 없다.

image

input type에 image를 지정하면 image button control을 생성할 수 있다. image button은 클릭하면 summit 버튼과 같이 동작한다는 점만 제외하고는 <img> 엘리먼트와 똑같이 렌더링된다.

그리고 img 엘리먼트의 모든 속성과 다른 form button들이 같은 속성을 지원한다.

<input type="image" alt="Click me!" src="my-img.png" width="80" height="30" />

image 엘리먼트로 submit 하면 value 대신에 이미지 기준의 x, y 좌표를 전송한다.

fname=song&lname=jewoo&x=36&y=44

file

input type에 file을 지정하면 file picker control을 생성할 수 있다. File을 서버로 전송하기 위해 사용하고, file picker 위젯을 사용하여 하나 이상의 파일을 선택할 수 있다.

선택 가능한 파일은 accept 속성을 사용해서 지정할 수 있다. 그리고 하나 이상의 파일을 선택하게 하려면 multiple 속성을 추가하면 된다.

<input type="file" name="file" id="file" accept="image/*" multiple />

1-3. label

label은 HTML forms의 label을 정의하는 공식적인 방법이다.

  • 접근 가능한 form을 만들 때 중요한 엘리먼트다.
  • for attribute는 <label> 엘리먼트와 폼 위젯을 연결하기 위해 사용되고, 폼 위젯의 id와 for의 값이 매핑되어야 한다.
  • for attribute로 연결되면 사용자가 라벨을 눌렀을 때 그거에 맞는 위젯을 활성화시킨다.
<form action="/my-handling-form-page" method="post">
  <div>
    <label for="name">Name:</label>
    <input type="text" id="name" />
  </div>
  <div>
    <label for="mail">E-mail:</label>
    <input type="email" id="mail" />
  </div>
</form>

1-4. select

여러 개의 리스트에서 여러 개의 아이템을 선택할 때 사용한다.

서버에 전송되는 데이터는 select 엘리먼트의 name 어트리뷰트를 키로 하고 option 엘리먼트의 value 어트리뷰트를 값으로 하여 key=value 형태로 전송된다.

optgroup 엘리먼트는 option 엘리먼트를 그룹화하는 역할을 한다.

<label for="cars">Choose a car:</label>
<select name="cars" id="cars" form="carform">
  <optgroup label="Swedish Cars">
    <option value="volvo">Volvo</option>
    <option value="saab">Saab</option>
  </optgroup>
  <optgroup label="German Cars">
    <option value="bmw">BMW</option>
    <option value="audi">Audi</option>
  </optgroup>
</select>

1-5. fieldset/legend

<fieldset> 엘리먼트를 사용하여 동일한 목적을 공유하는 위젯 그룹을 만들 수 있다.

opening fieldset 태그 바로 아래에 <legend> 엘리먼트를 포함시켜서 fieldset에 label을 지정할 수 있다.

legend 엘리먼트의 fieldset 태그 내에서 사용되어야 하고 그룹화된 fieldset의 제목을 정의한다.

<form>
  <fieldset>
    <legend>Fruit juice size</legend>
    <p>
      <input type="radio" name="size" id="size_1" value="small" />
      <label for="size_1">Small</label>
    </p>
    <p>
      <input type="radio" name="size" id="size_2" value="medium" />
      <label for="size_2">Medium</label>
    </p>
    <p>
      <input type="radio" name="size" id="size_3" value="large" />
      <label for="size_3">Large</label>
    </p>
  </fieldset>
</form>

1-6. textarea

textarea 엘리먼트는 여러 줄의 text를 입력할 때 사용한다.

<textarea name="editor" rows="15" cols="20"></textarea>

1-7. button

라디오 버튼은 이름은 버튼이지만 실제로 버튼은 아니다. <button> 엘리먼트는 세 가지 타입이 존재한다.

<button> 엘리먼트를 사용하던 <input>의 type 속성에 button을 지정하던 동일하게 동작한다.

그러나 button 엘리먼트를 사용하면 opening 태그와 closing 태그 사이에 컨텐츠를 입력할 수 있다. input 엘리먼트는 빈 엘리먼트로만 사용 가능하다.(value는 속성을 통해서 입력받으므로 오직 일반 텍스트만 가능하다.)

submit

form data를 서버로 전송하는 역할을 한다. (default)

<button type="submit">This is a <strong>submit button</strong></button>

<input type="submit" value="This is a submit button" />

reset

모든 form 위젯을 default value로 초기화한다.

<button type="reset">This is a <strong>reset button</strong></button>

<input type="reset" value="This is a reset button" />

button

버튼 type은 자동으로 적용되는 동작은 없고, Javascript code를 사용하여 사용자 정의 동작을 수행한다.

<button type="button">This is an <strong>anonymous button</strong></button>

<input type="button" value="This is an anonymous button" />

2. Client-side form validation

서버로 데이터를 전송하기 전에 전송에 필요한 모든 데이터를 올바른 포맷으로 form control 들에 채워넣는 것이 중요하다.

이렇게 올바른 포맷으로 form control에 데이터를 작성할 수 있도록 검증하는 과정을 client-side form validation이라 한다.

client-side form validation은 높은 사용자 경험을 위해 중요한 기능이다.

client-side form validation이 보안을 책임지는 것은 좋지 않다. 앱은 항상 클라이언트 측의 데이터에 대해 서버에서 추가로 validation check를 해야 한다.

Validation type 별 특징

  • Built-in form validation
    • HTML5에 form validation 기능으로 많은 Javascript 코드를 요구하지 않는다.
    • Javascript validation 보다 좋은 성능을 가지만 Custom에 용이하진 않다.
  • Javascript validation
    • 완전히 custom이 가능하지만 모두 직접 만들거나 library를 사용해야 한다.

2-1. Build-in From Validation

HTML5 form control의 가장 중요한 기능 중 하나는 validation이다.

Javascript에 의존하지 않고 대부분의 사용자 데이터의 유효성을 검증할 수 있다.

  • required : 필수로 form field를 채워야 하는지
  • minlength/maxlength : 최소/최대 길이
  • min/max : 최소/최대 정수
  • type : number, email등 input type 지정
  • pattern : 입력 가능 패턴 정의(정규표현식)
<form>
  <input name="email" required />
  <button type="submit">제출</button>
</form>

CSS 가상 클래스

valid 상태일 때 엘리먼트가 :valid CSS 가상 클래스에 매치된다.

input:valid {
  border-color: blue;
}

invalid 상태일 때 엘리먼트가 :invalid CSS 가상 클래스에 매치된다.

input:invalid {
  border-color: red;
}

HTML5에서는 폼 입력을 아래와 같이 처리한다.

  1. 사용자 입력

    • validation 결과에 따라 :valid/:invalid 가상 클래스 적용
  2. 폼 데이터 전송 시도
  3. validation 규칙에 따라 입력 값 검증
    • 통과하면 전송
    • 통과하지 못하면 전송 차단 후 오류 메세지 노출

오류 메세지

브라우저 기본 동작을 활용하여 오류 메세지를 나타낼 수 있다.

기본 오류 메세지는 다음과 같고 각 message는 locale에 따라 번역되어 표시된다.

  • required : 이 입력란을 작성하세요.
  • minlength/maxlength : 이 텍스트를 {maxlength}자 이상으로 늘리세요.
  • min/max : 값은 {min}이상이어야 합니다.
  • type : 이메일 주소에 ‘@’를 포함해주세요. ‘‘에는 ‘@’가 없습니다.
  • pattern : 요청한 형식과 일치시키세요.

2-3. Javascript를 사용한 form validation

만약 기본 오류 메세지를 수정하거나 레거시 브라우저를 처리하려면 반드시 Javascript를 사용해야 한다.

이때 Constraint Validation API를 사용할 수 있다.

Constraint Validation API

대부분의 브라우저는 Constraint Validation API를 지원한다.

다음과 같은 form 엘리먼트 DOM interface에서 Constraint Validation API를 사용할 수 있다.

  • HTMLButtonElement (<button>)
  • HTMLFieldSetElement (<fieldset>)
  • HTMLInputElement (<input>)
  • HTMLOutputElement (<output>)
  • HTMLSelectElement (<select>)
  • HTMLTextAreaElement (<textarea>)

Constraint Validation API은 다음과 같은 property들과 메서드로 구성된다.

  • property
    • validity : 다음과 같은 property를 가지고 있는 ValidityState Object를 반환한다.
      • patternMismatch : pattern에 맞는 value가 입력되었는지
      • tooLong : maxLength 속성 만족 여부
      • tooShort : minLength 속성 만족 여부
      • rangeOverflow : max 속성 만족 여부
      • rangeUnderflow : min 속성 만족 여부
      • typeMismatch : 지정된 타입에 알맞은 value가 입력되었는지(email)
      • valid : valid 인지 invalid 인지
      • valueMissing : required 속성 만족 여부
    • validationMessage : locale이 반영된 오류 메세지를 반환한다.
    • willValidate : form이 summit 될 때 valid 한 상태라면 true, 아니라면 false를 반환한다.
  • method
    • checkValidity() : 엘리먼트에 유효성 문제가 없는 경우 true를 반환한다.
    • setCustomValidity(emssage) : 엘리먼트에 사용자 정의 오류 메세지를 추가한다. 오류 발생 시 지정된 오류가 표시된다.

아래 코드에서 이메일 입력의 validity.typeMismatch 속성을 확인하여 만약 typeMismatch가 true라면 setCustomValidity() 메서드를 사용하여 에러 메세지를 지정한다.

<form>
  <label for="mail"
    >I would like you to provide me with an e-mail address:</label
  >
  <input type="email" id="mail" name="mail" />
  <button>Submit</button>
</form>
const email = document.getElementById("mail");

email.addEventListener("input", function (event) {
  if (email.validity.typeMismatch) {
    email.setCustomValidity("I am expecting an e-mail address!");
  } else {
    email.setCustomValidity("");
  }
});

커스텀 메세지 형식

만약 메세지 내용뿐 아니라 나타내는 모습까지 제어하고 싶다면 기본 동작을 preventDefault로 막고 원하는 위치에 나타낼 수도 있다.

input.addEventListener("invalid", (e) => {
  e.preventDefault();

  //특정 메세지를 원하는 위치에 넣는 코드
});

참고

  • https://developer.mozilla.org/ko/docs/Learn/HTML/Forms/
    • https://developer.mozilla.org/ko/docs/Learn/HTML/Forms/Your_first_HTML_form
    • https://developer.mozilla.org/en-US/docs/Learn/Forms/How_to_structure_a_web_form
    • https://developer.mozilla.org/en-US/docs/Learn/Forms/Basic_native_form_controls
    • https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation
  • https://poiemaweb.com/html5-tag-forms
  • HTML5 폼 검증에 대해 정리해 보자