들어가며
Input Box에 입력된 값을 코드를 통해 대소문자 변경하는 것은 매우매우 쉽다. 다만, 무엇이든 ‘편의성을 올리는 것’은 조금 더 고민이 필요하다. ‘이러면 좀 더 편할 것 같은데..’ 라는 생각이 들만한 부분을 사용자 관점에서 주관적으로 생각해보아야하고, 다양한 측면에서 따져보아야하기 때문이다.
최근에 만들었던 기능 중에도 위와 같이 별거 아니지만 사용자 관점에서 문제를 생각해봐야하는 순간이 있었다. Javascript를 다루는 한 결국 언젠가 이러한 고민의 순간은 또 다시 찾아올 것이기에, 그 때 내가 고민한 것은 어떤 것들이었는지 간단하게 정리해보고 그렇게 구현한 코드를 기록하고자 한다.
상황 및 목표
상황 : “select box”와 “text input box”가 1개 씩 존재
목표 :
select box 특정 요소 A 선택시 : input box에 기존에 입력된 값과 앞으로 입력 될 값은 모두 소문자
select box 특정 요소 B 선택시 : input box에 기존에 입력된 값과 앞으로 입력 될 값은 모두 대문자
input box에 글이 길게 입력되어있는 상태에서, 글의 중간으로 커서를 옮긴 후 값을 입력하는 경우 기존의 값 영향 없이, 입력되는 값의 타입 대소문자 설정
이슈
개발을 하다보니 위의 세번째 목표에서 의외로 문제가 있었다.
화면에 보여지는 값은 대소문자 변환이 정상적으로 이루어진듯 보이지만 실제로 내부적으로는 값이 변경되지 않는 현상
단순하게 대소문자 변경하는 방식은 SELECT 변환시 혹은 타이핑시 커서가 맨끝으로 강제로 이동되는 현상
첫번째 문제는 확인해보니 vue.js를 사용하는 경우 겪을 수 있을 문제였다. 단순히 Input Box의 값 뿐만 아니라 지정된 v-model 프로퍼티 값도 명시적 변경이 필요했다.
두번째 문제는 대소문자 변경 후 입력된 값이 전체 단위로 replace되어 기존 커서의 위치를 잃어버리기 때문이었다. 이것은 “setSelectionRange“라는 함수를 통해 해결할 수 있었다. 원래 setSelectionRange는 특정 영역의 글자를 마치 마우스로 드래그한 듯 선택하기 위해 사용되는 함수다. 그래서 나는 이것을 활용하여 대소문자 변경 직전 keyup 이벤트가 발생된 위치를 저장해두고 문자열 치환 직후 setSelectionRange를 통해 커서의 위치를 되돌려주는 방법으로 문제를 해결하였다.
최종 코드
위 모든 목표와 문제를 해결할 수 있는, 최종적으로 구현된 코드는 다음과 같다.
- common.js
function handleInput(selectBox, inputBox, data, fieldPath) {
selectBox = document.querySelector(selectBox);
inputBox = document.querySelector(inputBox);
if (!isEmpty(fieldPath)) {
data = fieldPath.reduce((target, key) => target[key], data);
}
function convertByChange() {
if (selectBox.value == 'gkId') {
data.method_info = data.method_info.trim().toLowerCase();
} else if (selectBox.value == 'parentTicketId' || selectBox.value == 'ticketId') {
data.method_info = data.method_info.trim().toUpperCase();
}
}
function convertByKeyup(event) {
if( !['gkId', 'parentTicketId', 'ticketId'].includes(selectBox.value) ) return;
let input = event.target;
let start = input.selectionStart;
let end = input.selectionEnd;
let inputValue = input.value;
if (selectBox.value == 'gkId') {
inputValue = inputValue.toLowerCase();
} else if (selectBox.value == 'parentTicketId' || selectBox.value == 'ticketId') {
inputValue = inputValue.toUpperCase();
}
input.value = inputValue;
data.method_info = inputValue;
input.setSelectionRange(start, end);
}
function convertInputString(event) {
let input = event.target;
let inputValue = input.value;
inputValue = inputValue.replace(/(\s+)/g, ',').replace(/,+/g, ',').replace(/(^,)|(,$)/g, '').split(',');
if(inputValue.length > 30) {
alert("한 번에 여러 개 조회 시 최대 30개까지 검색할 수 있습니다.");
inputValue = inputValue.slice(0,30);
}
inputValue = inputValue.join(', ');
input.value = inputValue;
data.method_info = inputValue;
}
function convertByEnter(event){
if(event.keyCode == 13){
convertInputString(event);
}
}
selectBox.addEventListener('change', convertByChange);
inputBox.addEventListener('keyup', convertByKeyup);
inputBox.addEventListener('blur', convertInputString);
inputBox.addEventListener('keydown', convertByEnter);
}
- Input핸들링대상화면.js
화면초기화함수 : function() {
handleInput('#method_id','#method_info', this);
}
- input핸들링대상화면.jsp
<div class="input_select col6">
<select name="" v-model="method_id" id="method_id">
<option v-for="method in methodIdList" :value="method.comnCd">
{{ method.comnCdNm }}
</option>
</select>
</div>
<input type="text" name="" @keyup.enter="searchVocRequestedList(1)" v-model="method_info" id="method_info" class="col4" value="">
결과
- SELECT → 선택된 option에 따른 입력값 대소문자 변경 & option 변경시 대소문자 변경

- 중간부터 입력시 대소문자 제어 & 커서 위치 보존
