이메일 서비스의 서명을 등록할 때, 이미 만들어진 html 을 그대로 서명에 복사 붙여넣기 할 수 있는 기능을 만들고자 했고 이를 자바스크립트로 구현하려한다.
form 양식을 작업하는데, 국문칸엔 한글만, 영문칸엔 영어만 입력할 수 있도록 정규표현식을 사용하려했다. 이때 정규표현식을 통해 텍스트를 대체할 수 있는 메서드인 replace()
에 대해 또 한 번 알게되었다.
/[^가-힣]/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>
<!-- 영문 이름 -->
<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; "> </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://codechacha.com/ko/javascript-input-alphabet-number-hangul/
이메일 형식이 복잡한 만큼 정규식 또한 복잡했다;
/^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/
/^http[s]?:\/\/([\S]{3,})/i
작성된 서명은 그대로 아웃룩이라는 이메일 사이트에 서명으로 등록하려 하는데, 드래그 할 필요없이 버튼을 누르면 텍스트 뿐만 아닌 적용된 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
는 이제 지원을 하지 않는 기능이라 사용이 지양되고 있었고, 그 대체제로 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
를 적용한 상태로 작업을 완료한 상황인데, 시간이 될 때 더 나은 방법을 찾아야할 것 같다
사용 후 피드백을 받아 다음과 같이 수정해보았다.
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;
}