[모던JS: 브라우저] 폼과 폼 조작

KG·2021년 6월 21일
0

모던JS

목록 보기
35/47
post-thumbnail

Intro

본 포스팅은 여기에 올라온 게시글을 바탕으로 작성되었습니다.
파트와 카테고리 동일한 순서로 모든 내용을 소개하는 것이 아닌, 몰랐거나 새로운 내용 위주로 다시 정리하여 개인공부 목적으로 작성합니다.
중간중간 개인 판단 하에 필요하다고 생각될 시, 기존 내용에 추가로 보충되는 내용이 있을 수 있습니다.

폼 프로퍼티와 메서드

폼(form)을 조작하는데 사용되는 요소에는 특별한 프로퍼티와 이벤트가 많다. 아무래도 서버와의 통신에 자주 사용되는 기본적인 요소이다 보니 그에 관련된 처리가 지원되어야 하기 때문이다.

1) 폼과 요소 탐색하기

폼은 특수한 컬렉션인 document.forms의 구성원이다. document.forms는 이름과 순서가 있는 기명 컬렉션(named collection)으로, 개발자는 이 이름이나 순서를 사용해 문서 내 폼에 접근할 수 있다.

document.forms.my	// name='my'인 폼
document.forms[0]	// 문서 내 첫 번째 폼

이렇게 원하는 폼을 가져온 다음에는, 또 다른 기명 컬렉션인 form.elements를 사용해 폼 내부의 요소에 다시 접근할 수 있다.

<form name="my">
  <input name="one" value="1">
  <input name="two" value="2">
</form>

<script>
  // <form name="my"> 인 요소
  let form = document.forms.my;
  
  // 해당 폼 내부에 <input name="one"> 인 요소
  let elem = form.elements.one;
  
  alert(elem.value);
</script>

HTML 마크업 구조에서 <form> 요소 내부에는 다른 기본 HTML 요소도 위치할 수는 있지만, 이들은 폼의 elements로는 인식되지 않는다. form.elements의 구성요소는 다음과 같은 요소가 있다. 자세한 요소는 다른 챕터에서 조금 더 상세히 살펴보자.

  • <input>
  • <textarea>
  • label
  • <form> : 폼 내부에 또 다른 폼이 위치하는 경우
  • ...

이때 라디오 버튼과 같은 input 타입을 다루다보면 동일한 이름을 가진 여러 개의 요소를 다뤄야 하는 경우가 생길 수 있다. 이 경우 form.elements[name]과 같이 이름으로 접근하는 경우에 그 대상은 컬렉션이 된다.

<form>
  <input type="radio" name="age" value="10" />
  <input type="radio" name="age" value="20" />
</form>

<script>
  // 문서 내 첫 번째 <form>에 접근
  let form = document.forms[0];
  
  // 해당 폼에서 age란 이름을 갖고 있는 요소에 접근
  let ageElems = form.elements.age;
  
  // 해당 요소가 여러 개이므로 해당 값은 컬렉션이 됨 
  alert(ageElems[0]);
</script>

이처럼 폼과 그 요소를 탐색할 때 사용하는 프로퍼티는 폼을 포함한 요소의 name 속성에 해당하는 값으로 접근하는 방식인 것을 알 수 있다.

폼 내부에서는 추가적으로 <fieldset> 이라는 폼 요소를 하나로 묶을 수 있는 태그를 사용할 수 있는데, 해당 요소는 폼과 마찬가지로 자신의 내부에 있는 폼 조작 요소에 접근할 수 있도록 elements 프로퍼티를 동일하게 지원한다.

<body>
  <form id='form'>
    <fieldset name='userFields'>
      <legend>info</legend>
      <input name='login' type='text' />
    </fieldset>
  </form>
</body>

<script>
  // <input name='login'>인 요소에 접근
  alert(form.elements.login);
  
  // name='userFields'인 <fieldset> 요소에 접근
  let fieldset = form.elements.userFields;
  alert(fieldset);
  
  // 두 군데에서 접근하는 요소는 서로 동일함
  alert(fieldset.elements.login == form.elements.login);
</script>

form.elemnets[name] 방식보다 더 짧게 접근하는 방법도 있다. elements 프로퍼티를 생략하고 form[name/index] 방식으로 접근하더라도 동일한 요소에 접근할 수 있다. 다만 해당 방식은 어떤 요소의 name 속성이 변경되고 난 후에도, 계속 변경되기 전의 이름으로 접근이 가능하다는 문제가 있다. 물론 새롭게 변경한 이름으로도 접근이 가능한데, 이전의 이름이 제거되지 않음으로 인해 예기치 않은 충돌이 발생할 수 있다.

<form id='form'>
  <input name='login' />
</form>

<script>
  // 두 방식은 서로 동일한 요소에 접근
  alert(form.elements.login == form.login);
  
  // 기존 <input> 요소의 name 속성값을 변경
  form.login.name = 'username';
  
  // form.elements엔 변경된 name 속성값이 반영
  form.elements.login;		// undefined
  form.elements.username;	// input
  
  // 단축된 방식은 새로운 이름과 이전 이름 모두 인식
  alert(form.login === form.username);	// true
</script>

다만 폼 요소의 이름(name)은 한 번 지정된 후 수정되는 경우는 흔치 않기 때문에 이러한 특징이 큰 문제로 이어지는 경우는 드물다.

2) element.form 역참조

폼 내부의 요소는 다시 element.form으로 상위의 폼에 접근할 수 있다. 이는 폼이 내부 요소에 대해 참조가 가능하듯, 각 내부 요소 또한 역으로 폼을 참조할 수 있음을 보여준다. 이를 그림으로 나타내면 아래와 같다.

<form id='form'>
  <input type='text' name='login' />
</form>

<script>
  // form 요소 내부의 <input name='login'>
  let login = form.login;
  
  // <input name='login'> 요소 상위의 form
  alert(login.form);
</script>

3) 폼 요소(elements)

앞서 언급한 폼 요소 외에도 다양한 폼 요소가 있다. 폼 조작에 사용되는 요소들에 대해 간단하게 살펴보자.

1. input과 textarea

텍스트를 다룰 때 가장 많이 사용하는 폼 요소 중에 하나이다. input의 경우엔 따로 타입을 지정하여 텍스트 외의 자료도 다룰 수 있지만, textarea의 경우엔 그 이름에서 알 수 있듯이 텍스트 위주의 자료를 다룬다. 그 외에 차이점으로 textarea는 입력하는 칸의 영역을 input 요소보다 유연하게 조절이 가능하다.

inputtextarea 요소의 값은 input.value (string) 또는 input.checked (boolean)을 사용해서 얻을 수 있다.

input.value = 'New value';
textarea.value = 'New text';

input.checked = true;	// 체크박스 또는 라디오 버튼 등

textarea 요소 내부에 다른 HTML 요소가 있더라도 브라우저는 이를 렌더링한다. 그러나 기본적으로 textarea는 사용자 입력값을 받는데 사용되는 요소임을 잊지말자. 만약 내부의 값이 HTML이라고 할 지라도 값을 얻을 때 textarea.innerHTML을 사용해서는 안 된다. textarea.innerHTML에는 페이지를 처음 열 당시의 HTML만 저장되어 최신 값을 구할 수 없기 때문이다.

2. select와 option

<select> 요소에는 세 가지 중요 프로퍼티가 있다.

  1. select.options : <option> 하위 요소를 담고 있는 컬렉션
  2. select.value : 현재 선택된 <option>
  3. select.selectedIndex : 현재 선택된 <option>의 번호(인덱스)

이 세가지 프로퍼티를 사용하면 아래와 같은 세 가지 방법으로 <select>의 값 설정이 가능하다.

  1. 조건에 맞는 <option> 하위 요소를 찾아 option.selected 속성을 true로 설정
  2. select.value를 원하는 값으로 수정
  3. select.selectedIndex를 원하는 option 번호/인덱스로 수정

세 방법 중 첫 번째 방법이 가장 확실하지만, 두 번째나 세 번째 방법이 대체로 더 편리할 때가 많다.

<select id='select'>
  <option value="apple">Apple</option>
  <option value="pear">Pear</option>
  <option value="banana">Banana</option>
</select>

<script>
  // 세 가지 코드의 실행 결과는 모두 동일하다
  select.options[2].selected = true;
  select.value = 'banana';
  select.seletedIndex = 2;
</script>

대부분의 다른 폼 조작 요소와는 달리 <select> 요소는 multiple 속성을 통해 여러 개의 option을 선택할 수 있다. multiple 속성을 사용하는 경우는 드물지만, 쓰게 된다면 첫 번째 방법을 사용해 <option> 하위 요소에 있는 selected 프로퍼티를 추가/제거해야 정확한 처리가 가능하다. 이때 선택된 여러 개의 option이 담긴 컬렉션은 다음 예시처럼 select.options를 사용해 얻을 수 있다.

<select id="select" multiple>
  <option value="blues" selected>Blues</option>
  <option value="rock" selected>Rock</option>
  <option value="classic">Classic</option>
</select>

<script>
  // 선택된 값 전체를 출력
  let selected = Array.from(select.options)
  	.filter(option => option.selected)
  	.map(option => option.value);
  
  alert(selected);	// blues, rock
</script>

3. Option 생성자

Option 생성자는 잘 사용되지 않으나 흥미로운 점이 있다. <option> 요소를 생성자를 통해 만드는 문법은 아래와 같다.

option = new Option(text, value, defaultSelected, selected);

각 매개변수는 다음과 같다.

  • text : option 내부 텍스트
  • value : option의 값
  • defaultSelected : true이면 HTML 속성 selected 생성
  • selected : true 이면 해당 option이 선택됨

이때 defaultSelectedselected의 차이는 다음과 같다. defaultSelected의 경우엔 option.getAttribute('selected')를 사용해서 얻을 수 있는 HTML 속성을 설정해준다. 반면 selectedoption의 선택 여부 자체를 결정한다. 보통 Option 생성자를 사용할 때엔 대개 두 매개변수를 모두 true/false로 지정하여 함께 사용한다.

// 선택되지 않은 상태의 option 생성
// <option value='value'>Text</option>
let option = new Option('Text', 'value');

// 선택된 상태의 option 생성
// <option value='value' selected>Text</option>
let option = new Option('Text', 'value', true, true); 

Option 생성자를 통해 만든 요소에는 다음과 같은 프로퍼티가 지원된다.

  • option.selected : option의 선택 여부
  • option.index : option 중 몇 번째인지를 나타내는 번호/인덱스
  • option.text : 사용자가 보게 될 텍스트

focus와 blur

사용자가 폼 요소를 클릭하거나 탭(Tab) 키를 눌러 요소를 이동하면 해당 요소가 포커스(focus)된다. HTML 속성 중에는 autofocus라는 속성이 있는데, 해당 속성을 사용하면 페이지가 로드된 후 자동으로 포커싱이 일어난다. 이 외에도 자바스크립트를 이용하는 등 다양한 포커싱 방법이 있다.

요소를 포커싱한다는 것은 암묵적으로 '이 곳에 데이터를 입력할 준비가 되었다'는 것을 의미하기에, 포커싱이 이뤄지는 순간에 요구사항을 충족시키는 초기화 코드를 실행시킬 판단 근거가 되기도 한다.

반면 요소가 포커싱을 잃는 순간(blur)는 때때로 포커싱보다 더 중요한 의미를 가질 수 있다. 사용자가 다른 곳을 클릭하거나 탭키를 눌러 다음 폼 필드로 이동하면 포커싱을 잃게 된다. 마찬가지로 이 외에도 다양한 방법으로 포커싱을 잃게 할 수 있다.

요소가 포커싱을 잃었다는 것은 '데이터 입력이 완료되었다'는 것을 의미하기 때문에, 포커싱이 해제되는 순간엔 데이터를 검증하거나, 입력된 데이터를 임시저장 또는 서버에 요청을 보내는 등의 코드를 실행할 수 있다.

1) focus, blur 이벤트

focus 이벤트는 요소가 포커싱을 받을 때, blur 이벤트는 포커싱을 잃을 때 발생한다. 두 이벤트를 사용해서 입력 필드 값 검증을 수행할 수 있다.

  • blur 이벤트 핸들러에서는 필드에 이메일이 정상적으로 입력되었는지 확인 후 비정상 입력이 있다면 에러를 출력
  • focus 이벤트 핸들러에선 에러 메시지를 숨김
<style>
  .invalid { border-color: red; }
  #error { color: red }
</style>

이메일: <input type="email" id="input">

<div id="error"></div>

<script>
  input.onblur = function() {
    if (!input.value.includes('@')) {
      input.classList.add('invalid');
      error.innerHTML = '올바른 메일 주소 기입 필요';
    }
  };
  
  input.onfocus = function() {
    if (this.classList.contains('invalid')) {
      this.classList.remove('invalid');
      error.innerHTML = "";
    }
  };
</script>

모던 HTML에서는 그 외에도 required, pattern 등의 속성을 지원하기 때문에 HTML 내부에서도 어느 정도 입력값 검증을 수행할 수 있다. 그럼에도 불구하고 추가적으로 자바스크립트를 사용해 검증을 하는 이유는 자바스크립트가 보다 더 유연한 처리가 가능하기 때문이다. 또한 자바스크립트를 통해 제대로 된 값이 입력되었다면, 자동으로 이를 서버에 보낼 수 있는 처리를 원활하게 할 수 있다.

2) focus, blur 메서드

elem.focus()elem.blur() 메서드를 사용하면 요소에 포커스를 줄 수도 있고 제거할 수도 있다. 사이트 방문자가 유효하지 않은 값을 입력하면 사이트를 떠나지 못하도록 하는 코드를 작성해보자.

<style>
  .error {
    background: red;
  }
</style>

이메일: <input type="email" id="input">
<input type="text" style="width:220px" placeholder="이메일 형식이 아닌 값을 입력하고 여기에 포커스를 주세요.">

<script>
  input.onblur = function() {
    // 이메일이 아닌 경우 => 에러 출력
    if (!this.value.includes('@')) {
      this.classList.add('error');
      input.focus();	// 포커스 이벤트 발생
    }
    else {
      this.classList.remove('error');
    }
  }
</script>

해당 코드는 파이어폭스의 고질적인 버그를 제외한다면 다른 브라우저에서 모두 잘 동작한다.

이메일이 아닌 값을 입력한 후에 <input> 요소를 벗어나려는 경우엔 onblur 메서드가 이를 캐치해 포커스를 다시 해당 요소로 되돌려 놓는다. 이때 onblur는 요소가 포커스를 잃고 난 후에 발생하는 이벤트를 처리하기 때문에 해당 메서드 내부에서 event.preventDefault()를 통해 포커스를 잃게 하는 것을 방지할 수 없다.

이 외에도 포커스를 잃는 경우를 자바스크립트적으로 조절할 수 있다.

  • alert 출력창이 뜨게 되면 자동적으로 blur 이벤트가 발생한다. 그러나 해당 출력창이 해제되면 자동으로 다시 focus 이벤트가 발생해 해당 요소로 돌아간다

  • 만약 어떤 요소가 DOM에서 제거된다면 자동으로 focus가 해제된다. 이때 해당 요소가 다시 삽입되더라도 잃어버린 focus는 복구되지 않는다.

이 외에도 다른 방법이 존재한다. 그러나 보통 자바스크립트 내부적으로 포커싱을 잃는 방식은 오동작의 우려가 많다. 사용자가 의도적으로 원하는 때에 포커스를 잃는 것이 보장되지 않기 때문이다. 따라서 가장 최선의 방식은 사용자가 직접 스스로 포커스를 잃는 순간에 관여하도록 하는 것이다.

3) tabindex를 사용해 모든 요소 포커싱 처리

대다수의 요소는 기본적으로 포커싱을 지원하지 않는다. 이는 브라우저 별로 약간의 차이가 있을 수는 있지만, 보통 <button>, <input>, <select>, <a>와 같이 사용자가 웹 페이지와 상호작용 할 수 있도록 도와주는 요소는 focus/blur 이벤트를 지원하는 경우가 많다.

반면 <div>, <span>, <table>과 같이 무언가를 표시하는 용도로 사용되는 요소는 포커싱이 지원되지 않는다. 그럼에도 불구하고 해당 요소에도 포커싱을 지원하고자 한다면 HTML 속성 중 tabindex를 사용하는 방법이 있다.

tabindex 속성이 있는 요소는 종류와 상관없이 포커스가 가능하다. 속성값은 숫자인데, 이 숫자는 탭 키를 눌러 요소 사이를 이동할 때 기준이 되는 순서를 의미한다. 예를 들어 두 개의 요소에 tabindex를 지정하고 각각 1번과 2번의 값을 할당했다면, 첫 요소가 포커싱 되어있는 상태에서 탭을 누르면 2번 요소로 이동하게 된다.

tabindex의 순서는 기본적으로 오름차순 순서로 적용된다. 즉 1부터 시작해 점점 큰 숫자가 매겨진 순서로 이동하며, 그 이후엔 tabindex가 없이도 포커싱이 작용하는 요소 순서로 이동한다. tabindex가 없는 요소는 문서 내 순서에 따라 포커스가 이동한다.

이때 tabindex에는 다음과 같은 미묘한 순서값이 존재한다.

  • tabindex0인 요소 : 이 요소는 tabindex 속성이 없는 것처럼 동작한다. 따라서 포커싱은 받을 수 있지만, tabindex가 1보다 크거나 같은 요소보다는 나중에 포커스를 받는다. 즉 이 값은 주로 포커스는 가능하게 만들지만 포커스 순서는 문서 레벨에서 기본 순서를 그대로 유지하고 싶은 경우 사용한다.

  • tabindex-1인 요소 : 이는 오직 스크립트만 사용해서 포커스 하고자 할 때 사용한다. 탭 키를 통해서는 해당 요소에 포커싱을 적용할 수 없지만, 자바스크립트를 사용해 elem.focus() 메서드 등을 사용하면 제대로 포커싱이 가능하다.

<ul>
  <li tabindex="1"></li>
  <li tabindex="0"></li>
  <li tabindex="2"></li>
  <li tabindex="-1">음수 일</li>
</ul>

<style>
  li { cursor: pointer; }
  :focus { outline: 1px dashed green; }
</style>

위 코드를 실행하면 포커스는 tabindex1, 2, 0인 순서로 이동한다. <li>는 기본적으로 포커스 할 수 없는 요소이나 tabindex를 사용해 실제 포커스를 적용하는 것을 볼 수 있다. tabindex는 HTML 속성 외에도 DOM 객체의 프로퍼티로 지원되는데 elem.tabIndex를 사용해서 동일한 효과를 볼 수 있다.

4) focusin과 focusout

이벤트 버블링을 다루는 챕터에서 기본적으로 버블링이 발생하지 않는 몇몇 이벤트가 있음을 살펴보았다. 여기서 다루는 focusblur 이벤트가 바로 버블링이 발생하지 않는 이벤트이다. 따라서 다음의 예시는 제대로 동작하지 않는다.

<!-- 폼 안에서 포커스가 된 경우 클래스를 추가함 -->
<form onfocus="this.className='focused'">
  <input type="text" name="surname" value="">
  <input type="text" name="name" value="이름">
</form>

<style> .focused { outline: 1px solid red; } </style>

<input> 요소의 공동 조상 요소인 <form>에서 포커싱 이벤트를 처리하고 있으나 버블링 되지 않는 관계로 이를 제대로 처리하지 못하고 있다. 이런 기본 동작을 피해 이벤트 위임 방식으로 포커싱을 처리하는 방법은 크게 두 가지가 있다.

  1. 캡처링(Capturing) 이용

    focusblur 이벤트는 버블링은 되지 않지만 캡처링은 가능하다. 따라서 캡처링 단계에서 핸들러가 트리거 되도록 한다면 상위 요소에서 이벤트 위임 패턴으로 처리할 수 있다.

<form id="form">
  <input type="text" name="surname" value="">
  <input type="text" name="name" value="이름">
</form>

<style> .focused { outline: 1px solid red; } </style>

<script>
  form.addEventListener('focus', () => form.classList.add('focused'), true);
  form.addEventListener('blur', () => form.classList.remove('focused'), true);
</script>
  1. focusinfocusout 이벤트 사용

    두 번째 방법으로는 focusinfocusout 이벤트를 사용하는 것이다. 두 이벤트는 focus, blur와 동일하지만 버블링이 된다는 특징을 가지고 있다. 때문에 이벤트 위임 패턴으로 포커싱을 처리할 수 있다.

    다만 두 이벤트는 on<event> 방식으로 핸들러를 추가하는 방식이 아닌, elem.addEventListener 방식으로만 핸들러를 등록해야 정상적으로 처리되는 이벤트라는 점에 주의하자.

<form id="form">
  <input type="text" name="surname" value="">
  <input type="text" name="name" value="이름">
</form>

<style> .focused { outline: 1px solid red; } </style>

<script>
  form.addEventListener('focusin', () => form.classList.add('focused'));
  form.addEventListener('focusout', () => form.classList.remove('focused'));
</script>

그 외 현재 포커스 된 요소를 파악하기 위해서는 document.activeElement 프로퍼티를 사용해 접근할 수 있다.

이벤트: change, input, copy, paste

데이터가 변경될 때 실행되는 다양한 이벤트가 존재한다. 이번 챕터에서는 해당 이벤트들을 짚고 넘어가보자.

1) change 이벤트

change 이벤트는 요소 변경이 끝나면 발생한다. 텍스트 입력 요소의 경우엔 요소 변경이 끝날 때가 아니라, 요소 입력 후 포커스를 잃을 때 해당 이벤트가 발생한다. 아래 예시에서는 입력 필드에 글자를 입력하는 동안엔 change 이벤트가 발생하지 않음을 볼 수 있다. 하지만 입력을 끝내고 다른 곳으로 이동하는 등의 동작을 통해 포커스를 잃게 되는 경우 그 순간 change 이벤트가 발생한다.

<input type='text' onchange='alert(this.value)' />
<input type='button' value='버튼' />

그러나 텍스트 입력 요소가 아닌 경우라면 선택 값이 변경된 직후에 해당 이벤트가 발생한다. 예를 들어 <select> 요소 또는 <input> 요소 타입이 checkbox/radio 인 경우가 이에 해당한다.

<select onchange="alert(this.value)">
  <option value="">선택하세요.</option>
  <option value="1">옵션 1</option>
  <option value="2">옵션 2</option>
  <option value="3">옵션 3</option>
</select>

React에서는 텍스트 입력 필드에서 onChange 이벤트를 사용해서 값을 입력/수정/제거 등의 과정을 처리한다. 폼 요소는 리액트에서 제어 컴포넌트(Controlled Component)로 분류되는데 각 요소의 value를 리액트 컴포넌트의 state로 관리한다. 리액트는 state가 변경될 때마다 리-렌더링 되기 때문에 onChange 이벤트를 통해 텍스트 입력값을 받더라도, 매번 철자가 입력되는 순간 렌더링이 재차 발생하고 그로 인해 순간순간 새로이 입력되는 값을 설정할 수 있다.

2) input 이벤트

input 이벤트는 사용자가 값을 수정할 때마다 발생한다. 키보드 이벤트와 달리 input 이벤트는 어떤 방법으로든 값을 변경할 때 발생한다. 즉 마우스를 사용해서 글자를 붙여 넣는다거나, 음성인식 기능을 통해 글자를 입력하는 경우처럼 키보드 이외의 수단으로 값을 변경할 때도 input 이벤트가 발생한다.

<input type="text" id="input"> oninput: <span id="result"></span>
<script>
  input.oninput = function() {
    result.innerHTML = input.value;
  };
</script>

때문에 만약 수정이 발생할 때마다 이벤트를 실행하고 싶다면 <input> 이벤트가 가정 적절하다. 다만 방향키와 같이 값을 변경시키지 않는 조작에는 반응하지 않는다.

그 무엇도 oninput을 막을 수는 없다. input 이벤트는 값이 수정되자 마자 발생하기 때문이다. 따라서 event.preventDefault()를 사용해 기본 동작을 막더라도, 값이 수정되면 그 즉시 input 이벤트가 새로 발생하기에 효과가 없다.

3) cut, copy, paste 이벤트

cut, copy, paste 이벤트는 각각 값을 잘라내기·복사·붙여넣기 할 때 발생한다. 이 이벤트는 ClipboradEvent 클래스의 하위 클래스로 분류되며, 복사 및 붙여넣기 한 데이터에 접근할 수 있다. 가령 어떤 블로그에서 내용을 복사해서 다른 곳에 붙여넣을때 자동으로 출처가 같이 기입되는 경우가 있다. 이러한 동작을 클립보드(Clipboard) API 차원에서 다룰 수 있다.

input 이벤트와는 달리 세 이벤트는 event.preventDefault()를 사용해 기본 동작을 막을 수 있다. 이 경우엔 아무것도 복사 및 붙여넣기 작업을 할 수 없는 상태가 된다. 예를 들어 아래의 코드는 잘라내기·복사·붙여넣기 동작을 시도하면 모든 동작이 중단되고 alert 창을 통해 중단된 이벤트 이름을 보여준다.

<input type="text" id="input">
<script>
  input.oncut = input.oncopy = input.onpaste = function(event) {
    alert(event.type + ' - ' + event.clipboardData.getData('text/plain'));
    return false;
  };
</script>

해당 이벤트는 단순히 텍스트 뿐만 아니라 모든 것을 복사/붙여넣기 할 수 있다. 예를 들어 OS 파일 매니저에서 파일을 복사해 붙여넣는 등의 작업도 가능하다.

클립보드에서 읽기·쓰기, 파일 등 다양한 데이터 타입에서 작동하는 메서드 목록 명세를 확인할 수 있다. 다만 클립보드는 전역 OS 레벨이기에 onclick 이벤트 핸들러처럼 대부분의 브라우저는 안전을 위해 특정 사용자 동작 범위에서만 클립보드의 읽기와 쓰기에 대한 접근을 허용한다.

예를 들어 IE에서는 window.clipboardData.getData()와 같이 전역변수 window 레벨에서 바로 클립보드의 내용을 가져올 수 있지만 크롬 브라우저에서는 보안상의 이유로 이 같은 접근이 불가하다. 따라서 세 이벤트가 동작할 때 event 객체를 통해 접근하는 방식으로 처리해야 한다.

// 크롬 등 대부분 브라우저 (IE 제외)
function handlePaste (event) {
  let clipboardData = event.clipboardData || window.clipboardData;
  
  // ...
}

또한 파이어폭스를 제외한 모든 브라우저에서는 dispatchEvent를 사용해 커스텀 클립보드 이벤트를 생성하는 것을 금지하고 있다.

submit 이벤트와 메서드

submit 이벤트는 폼을 제출할 때 발생하는데, 주로 폼을 서버로 전송하기 전에 내용을 검증하거나 전송 취소할 때 사용할 수 있다.

한편 form.submit() 메서드는 자바스크립트만으로 폼을 전송하고자 할 때 사용할 수 있다. submit() 메서드는 주로 동적으로 폼을 생성하고 서버에 보내고자 할 때 사용한다.

1) submit 이벤트

폼을 전송하는 방법엔 크게 두 가지가 있다.

  1. <input type='submit> 또는 <input type='image'> 클릭
  2. input 필드에서 엔터(Enter) 누르기

두 방법 모두 폼의 submit 이벤트를 발생시키는 동작이다. 이벤트 핸들러에서는 데이터를 체크할 수 있는데, 데이터에 에러가 있다면 전송을 취소할 수 있다. 서버에 전송하는 동작은 submit 이벤트의 기본 동작이기 때문에 취소 시점에서 event.preventDefault()를 호출하면 된다.

// return false로 인해 서버에 전송되지는 않는다
// 1번과 2번 방식 모두 submit 이벤트를 발생시킨다
<form onsubmit="alert('submit!');return false">
  1. input 필드에 포커스를 준 다음 Enter 키 누르기: <input type="text" value="text"><br>
  2. '제출' 버튼 누르기: <input type="submit" value="제출">
</form>

이와 관련해서 재미난 연계 동작이 하나 있다. input 필드에서 엔터 키를 눌러 폼을 전송하는 경우엔 <input type='submit'>에 있는 click 이벤트가 같이 트리거 된다. 클릭을 하지 않았는데도 불구하고 click 이벤트가 동작하는 것이 조금 이상해보일 수 있지만, 기본 동작으로 정의되어 있다.

<form onsubmit="return false">
  <input type="text" size="30" value="여기에 포커스를 준 다음에 Enter 키 누르기">
  <input type="submit" value="제출" onclick="alert('클릭 이벤트가 트리거!')">
</form>

2) submit 메서드

자바스크립트를 사용해서 직접 폼을 서버에 전송할 수 있다. form.submit() 메서드를 이용하는 것인데, 해당 메서드가 호출된 다음엔 기본적으로 submit 이벤트는 생성되지 않는다. 이는 개발자가 명시적으로 form.submit() 메서드를 호출했다면 스크립트에서 이미 필요한 모든 조치를 다 했다고 가정하기 때문이다. 따라서 submit() 메서드는 다음과 같이 폼을 직접 만들고 전송하길 원하는 경우 사용할 수 있다.

let form = document.createElement('form');
form.action = 'https://google.com/search';
form.method = 'GET';

form.innerHTML = '<input name="q" value="text" />';

// 폼을 제출하려면 반드시 폼이 존재해야 하므로 문서에 추가
document.body.append(form);

form.submit();

References

  1. https://ko.javascript.info/forms-controls
  2. https://bugzilla.mozilla.org/show_bug.cgi?id=53579
  3. https://www.w3.org/TR/clipboard-apis/#dfn-datatransfer
profile
개발잘하고싶다

0개의 댓글