[Javascript] 서명 입력폼 - 정규표현식을 통한 유효성 체크 & html 텍스트/스타일 복사

hyejinJo·2023년 10월 24일
0

Javascript / jQuery

목록 보기
3/8

서명 복사 기능

이메일 서비스의 서명을 등록할 때, 이미 만들어진 html 을 그대로 서명에 복사 붙여넣기 할 수 있는 기능을 만들고자 했고 이를 자바스크립트로 구현하려한다.

  • 왼쪽 form 에 이름, 번호 등을 입력하면 오른쪽 화면에 그대로 정보가 스타일이 입혀진 채 반영됨
  • 완성된 화면의 html 을 그대로 이메일 서명 양식에 복사

입력폼 만들기 - 정규표현식을 통해 유효성 체크

form 양식을 작업하는데, 국문칸엔 한글만, 영문칸엔 영어만 입력할 수 있도록 정규표현식을 사용하려했다. 이때 정규표현식을 통해 텍스트를 대체할 수 있는 메서드인 replace() 에 대해 또 한 번 알게되었다.

  • 입력폼에 영문, 한글 이름 그리고 번호를 적으면 아래에 입력한 값들이 실시간으로 나타나도록 구현
  • 정규표현식 중 한글만 입력되게 하는 부분이 작동되지 않아 로그를 찍어보니, 내가 삽입한 표현식은 완벽한 한글자가 되어야 하는 표현식이었다. (예: ‘안’ ⇒ o ‘ㅇ’ ⇒ x) 영문과 달리 한글 특성상 자음과 모음이 합쳐져 한 글자로 인식되는데, 입력 시작부터 모음이나 자음으로만 인식되어 한글이 입력되지 않은 것이었다. 자음과 모음도 같이 인식되도록 정규표현식을 다음과 같이 바꿨다.
    • 이전 정규표현식 (한 글자로 인식) : /[^가-힣]/g
    • 수정 정규표현식 (자음, 모음 포함하여 인식) : /[^가-힣ㄱ-ㅎㅏ-ㅣ]/g
  • 중간에 띄어쓰기가 되지 않아 찾아보니 \s 도 같이 넣어줘야 스페이스바도 인식할 수 있다고 한다.
<body>
  <div>
		<form action="" style="border: 2px solid #ccc; padding: 15px 0">
	    <fieldset class="input-wrap" style="border: none">
	      <label for="kr-name" style="display: inline-block; width: 65px">한글명: </label>
	      <input id="kr-name" type="text" oninput="inputFn('kr-name')">
	    </fieldset>
	    <fieldset class="input-wrap" style="border: none">
	      <label for="en-name" style="display: inline-block; width: 65px">영문명: </label>
	      <input id="en-name" type="text" oninput="inputFn('en-name')">
	    </fieldset>
	    <fieldset class="input-wrap" style="border: none">
	      <label for="phone1" style="display: inline-block; width: 65px">전화번호: </label>
	      <input id="phone1" type="text" maxlength="4" oninput="inputFn('phone1')" style="width: 60px"> -
	      <input id="phone2" type="text" maxlength="4" oninput="inputFn('phone2')" style="width: 60px">
	    </fieldset>
	  </form>
		<!-- 복사 버튼 -->
		<button onclick="copyElementToClipboard('x_Signature')"  style="width: 100px; margin-top: 20px; margin-left: 15px; padding: 5px 15px; cursor: pointer;">서명 복사</button>
	</div>

	<!-- 복사될 화면 -->
	<div id="x_Signature" style="border: 2px solid #ccc; padding: 15px">
		<div class="table-wrap table-wrap1"  style="width: 100%;">
        <table style="color: black;  font-size: 12px;  font-family: Arial, '맑은 고딕', 'Malgun Gothic', '굴림', 'Gulim';">
          <tbody>
	          <tr>
	            <td style="font-size:17px; font-family:'맑은 고딕','Malgun Gothic'; font-weight:bold; vertical-align:bottom; width:480px; height:27px; padding-bottom:3px; letter-spacing:-0.5px; line-height:17px; ">
	              <!-- 국문 이름 -->
	              <span class="kr-name" style="font-family: Arial sans-serif "></span>&nbsp; &nbsp;
	              <!-- 영문 이름 -->
	              <span class="en-name" style="color:#333333; font-size:15px; font-family: Arial sans-serif"></span>
	            </td>
	          </tr>
	          <tr>
	            <td style="color:#888888; font-weight:normal; width:480px; height:18px; line-height:18px; ">
	              <span style="font-family: Arial"> <span class="position"></span> | <span class="organization"></span></span>
	            </td>
	          </tr>
	          <tr>
	            <td style="color:#888888; height:15px; padding:2px 0 0 0; line-height:15px; "></td>
	          </tr>
          </tbody>
        </table>
      </div>
      <div class="table-wrap table-wrap2" style="width: 100%; ">
        <table style="color: black;  font-size: 12px;  font-family: Arial, '맑은 고딕', 'Malgun Gothic', '굴림', 'Gulim';>
	        <colgroup>
	          <col width="20">
	          <col>
	        </colgroup>
	        <tbody>
		        <tr>
		          <td style="color:#888888; font-weight:bold; text-align:left; vertical-align:middle; width:20px; height:19px; float:left; line-height:18px; font-family: Arial;">M</td>
		          <td style="font-weight:normal; height:19px; float:left; line-height:18px; "><span class="phone">+82 10 <span class="phone1"></span> <span class="phone2"></span></span></td>
		        </tr>
		        <tr>
		          <td style="color:#888888; font-weight:bold; text-align:left; vertical-align:middle; width:20px; height:19px; float:left; line-height:18px; ">
		            T</td>
		          <td style="font-weight:normal; height:19px; float:left; line-height:18px; ">+82 2 542 1987</td>
		        </tr>
		        <tr>
		          <td style="color:#888888; font-weight:bold; text-align:left; vertical-align:middle; width:20px; height:19px; float:left; line-height:18px; ">
		            F</td>
		          <td style="font-weight:normal; height:19px; float:left; line-height:18px; ">+82 2 542 1988</td>
		        </tr>
		        <tr>
		          <td colspan="2" style="font-weight:normal; height:19px; float:left; line-height:18px; ">&nbsp; </td>
		        </tr>
	        </tbody>
        </table>
      </div>
	</div>
</body>
<script>
  function inputFn(input_name){
    const result = document.querySelector(`.${input_name}`) 
		// 입력폼에 따라 정규표현식 다르게 적용
    const regExp = input_name === "kr-name" ? /[^-힣ㄱ-ㅎㅏ-ㅣ\s]/g :
      input_name === "en-name" ? /[^A-Za-z\s]/g : /\D/g;

    const input = document.getElementById(input_name)
    let value = input.value;

    value = value.replace(regExp, '');
    input.value = value; // 수정된 값으로 다시 input value 값 갱신
    result.innerHTML = input.value; // 갱신된 값 화면에 노출
  }
</script>

그 외에도 입력폼에 있어 많이 쓰이는 정규표현식들을 찾아보았다.

특수문자 제한하기

https://kimsg.tistory.com/294

한글, 영어, 숫자만 받기

https://codechacha.com/ko/javascript-input-alphabet-number-hangul/

이메일 정규표현식

이메일 형식이 복잡한 만큼 정규식 또한 복잡했다;

/^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/

도메인 url 정규표현식

/^http[s]?:\/\/([\S]{3,})/i

이메일에 복사/붙여넣기 기능 - html 텍스트/스타일 복사

작성된 서명은 그대로 아웃룩이라는 이메일 사이트에 서명으로 등록하려 하는데, 드래그 할 필요없이 버튼을 누르면 텍스트 뿐만 아닌 적용된 style 까지 그대로 먹혀 들어간 채로 복사를 하는 기능이 필요했다.

아웃룩 접속

오른쪽 상단 설정 > 메일 > 작성 및 회신

붙여넣기 기능

<div id="x_Signature">
	...
</div>
<button onclick="copyElementToClipboard('x_Signature')">태그 복사</button>

// execCommand 사용

function copyElementToClipboard(elementId) {
    const element = document.getElementById(elementId);
    const elementClone = element.cloneNode(true); // 요소와 하위 내용 복사
    const styles = window.getComputedStyle(element); // 스타일 정보 가져오기
    for (let prop of styles) {
      elementClone.style.setProperty(prop, styles.getPropertyValue(prop), styles.getPropertyPriority(prop));
    }

    const tempContainer = document.createElement('div');
    tempContainer.appendChild(elementClone);
    document.body.appendChild(tempContainer);

    const selection = window.getSelection();
    const range = document.createRange();
    range.selectNodeContents(tempContainer);
    selection.removeAllRanges();
    selection.addRange(range);

    try {
      document.execCommand('copy'); // 클립보드에 복사
			alert('서명이 복사되었습니다.')
      console.log('스타일을 포함한 요소가 성공적으로 복사되었습니다.');
    } catch (err) {
			alert('서명 복사에 실패했습니다.')
      console.error('요소 복사에 실패했습니다: ' + err);
    } finally {
      document.body.removeChild(tempContainer);
    }
  }

복사버튼을 클릭 후 해당 칸에 붙여넣기 했더니 스타일이 그대로 잘 적용되었다.

+ execCommand 사용 지양

하지만 execCommand 는 이제 지원을 하지 않는 기능이라 사용이 지양되고 있었고, 그 대체제로 Clipboard API 가 사용된다는 것을 알게되어 해당 기능으로 쓰고자 했다. 하지만..

// Clipboard API 사용

function copyElementToClipboard(elementId) {
    const element = document.getElementById(elementId);
    const elementClone = element.cloneNode(true); // 요소와 하위 내용 복사
    const styles = window.getComputedStyle(element); // 스타일 정보 가져오기
    for (let prop of styles) {
      elementClone.style.setProperty(prop, styles.getPropertyValue(prop), styles.getPropertyPriority(prop));
    }

    const tempContainer = document.createElement('div');
    tempContainer.appendChild(elementClone);
    document.body.appendChild(tempContainer);

    const range = document.createRange();
    range.selectNodeContents(tempContainer);

    navigator.clipboard.write([new ClipboardItem({ 'text/html': new Blob([range.toString()], { type: 'text/html' }) })])
      .then(() => {
        console.log('스타일을 포함한 요소가 성공적으로 클립보드에 복사되었습니다.');
      })
      .catch(err => {
        console.error('요소 복사에 실패했습니다: ' + err);
      })
      .finally(() => {
        document.body.removeChild(tempContainer);
      });
  }

결과: style 적용 없이 텍스트만 복사됨

Clipboard API 를 사용하면 저렇게 텍스트만 복사되어 찾아보니, HTML 요소의 CSS 스타일까지 복사하는 것은 Clipboard API 로 수행하기 어렵다고 한다..

할 수 없이 execCommand 를 적용한 상태로 작업을 완료한 상황인데, 시간이 될 때 더 나은 방법을 찾아야할 것 같다

+ 추가

사용 후 피드백을 받아 다음과 같이 수정해보았다.

  • 유효성검사 때문에 처음부터 입력이 안되면 혼란을 주어 입력폼 아래 경고성 문구가 표시되는 것으로 수정
  • 직급, 본부를 수정하는 것은 select 박스 형식으로 선택하게끔 수정
  • 전화번호 input 은 type 을 number 로 바꾼 후 onChange 콜백함수에 maxLength 를 따로 인수로 넣어주는 형식으로 수정 (type 이 number 인 상태에서는 왜인지 maxLength 속성을 쓸 수가 없었다…)
// form

<form action="">
  <fieldset class="input-wrap" style="border: none; height: 27px">
    <label for="kr-name" style="display: inline-block; width: 65px">한글명: </label>
    <input id="kr-name" type="text" placeholder="ex) 김모션" oninput="inputFn('kr-name')" style="width: 185px">
    <div class="error" style="display: none; margin-top: 3px; margin-left: 70px; color: crimson; font-size: 14px">(한글로 입력)</div>
  </fieldset>
  <fieldset class="input-wrap" style="border: none; height: 27px">
    <label for="en-name" style="display: inline-block; width: 65px">영문명: </label>
    <input id="en-name" type="text" placeholder="ex) Motion Kim" oninput="inputFn('en-name')" style="width: 185px">
    <div class="error" style="display: none; margin-top: 3px; margin-left: 70px; color: crimson; font-size: 14px">(영문으로 입력)</div>
  </fieldset>
  <fieldset class="input-wrap" style="border: none; height: 27px">
    <label for="position" style="display: inline-block; width: 65px">직급: </label>
    <select id="position" onchange="inputFn('position')" style="width: 185px">
      <option value="">---선택하기---</option>
      <option value="CEO">CEO</option>
      <option value="Deputy Leader">Deputy Leader</option>
      <option value="Leader">Leader</option>
      <option value="Manager">Manager</option>
      <option value="Developer">Developer</option>
      <option value="Designer">Designer</option>
      <option value="Planner">Planner</option>
    </select>
  </fieldset>
  <fieldset class="input-wrap" style="border: none; height: 27px">
    <label for="organization" style="display: inline-block; width: 65px">본부: </label>
    <select id="organization" onchange="inputFn('organization')" style="width: 185px">
      <option value="">---선택하기---</option>
      <option value="대표이사">대표이사</option>
      <option value="Planning 1">Planning</option>
      <option value="Technology 1">Technology</option>
      <option value="Design 1">Design</option>
      <option value="Management">Management</option>
    </select>
  </fieldset>
  <fieldset class="input-wrap" style="border: none">
    <label for="phone1">전화번호: </label>
    <span style="display: inline-block; margin-left: 5px;">010 - </span>
    <input id="phone1" type="number" oninput="inputFn('phone1', 4)" style="width: 60px"> -
    <input id="phone2" type="number" oninput="inputFn('phone2', 4)" style="width: 60px">
  </fieldset>
</form>
// script

function inputFn(input_name, maxLength){
    const result = document.querySelector(`.${input_name}`)
    const regExp = input_name === "kr-name" ? /[^-힣ㄱ-ㅎㅏ-ㅣ\s]/g :
      input_name === "en-name" ? /[^A-Za-z\s]/g : /\D/g;

    const input = document.getElementById(input_name)
    let value = input.value;

    // 입력 값이 최대 길이를 초과하면 잘라냄
    if (maxLength && value.length > maxLength) {
      value = value.slice(0, maxLength);
    }

    // 유효성 검사
    if (input_name === "kr-name" || input_name === "en-name") {
      const errorElement = document.querySelector(`#${input_name} + .error`);
      if(regExp.test(value)) {
        errorElement.style.display = "block";
      } else {
        errorElement.style.display = "none";
      }
    }

    input.value = value;
    result.innerHTML = input.value;
  }



참고:

정규 표현식에 다중 공백과 한글이 있는 문자열 처리
정규표현식을 사용한 유효성검사

profile
FE Developer 💡

0개의 댓글