#해시 태그 기능은 게시물을 구현할 때 거의 빠지지 않고 사용되는 기능이라고 할 수 있다. 태그를 구현하는 방법에는 여러가지가 있지만, 위의 GIF와 같이 동적으로 태그를 생성하는 디자인이 가장 많이 사용된다고 할 수 있다. 티스토리에서도, Velog에서도 이와 같은 방식으로 해시 태그 기능을 제공하고 있다.
내가 진행하고 있는 프로젝트에서도 아래의 요구 사항을 충족하는 태그 기능을 구현해야했다.
- #을 입력하고 스페이스바를 누르면 태그를 생성 (최대 5개까지만 입력 가능)
- 생성된 태그를 눌렀을 때 & 입력이 없는 상태에서 Backspace를 눌렀을 때 태그 삭제
- 드래그 앤 드롭으로 순서 변경 가능하도록
1. #을 입력하고 스페이스바를 누르면 태그 생성
태그의 목록들을 보여주기 위한 template 코드는 다음과 같이 작성했다.
<div class="input-wrapper">
<label class="input-label">태그</label>
<div class="hash-wrapper">
<div
class="hash-item"
v-for="(tag, index) in hashState.hashArr"
:key="index"
>
<p>{{ tag }}</p>
<p class="hash-item-delete">x</p>
</div>
<input
class="input-tag"
type="text"
v-model="hashState.hashtag"
@keyup.space="onKeyUpSpace"
:placeholder="hashState.hashArr.length < 5 ? '#태그 입력 (최대 5개)' : '최대 5개까지 입력'"
:disabled="hashState.hashArr.length >= 5"
/>
</div>
</div>
hashArr은 태그들의 목록, hashtag는 현재 input에 입력된 tag 문자열의 상태에 해당한다.
v-model로 hashtag 입력값을 바인딩하였고, vue에서 제공하는 @keyup.space 메소드로 스페이스바를 눌렀을 때 태그를 만드는 로직이 포함된 함수를 실행시켰다.
#을 입력하고 난 그 뒤의 문자열의 값으로 태그를 생성하고, 해당 문자열을 hashArr 배열에 push하는 간단한 로직이다.
const hashState = reactive({
hashArr: [],
hashtag: '',
});
const onKeyUpSpace = (e) => {
if (hashState.hashtag[0] === '#' && hashState.hashtag.substring(1).trim() !== '') {
hashState.hashArr.push(hashState.hashtag);
hashState.hashtag = '';
}
};
2. 생성된 태그를 눌렀을 때 & 입력이 없는 상태에서 Backspace를 눌렀을 때 태그 삭제
2.1 생성된 태그를 눌렀을 때 태그를 삭제
태그 하나를 구성하는 div에 태그를 삭제하는 함수를 정의하고 연결했다.
<div class="hash-item" v-for="(tag, index) in hashState.hashArr" @click="removeHashTag(index)" :key="index">
선택된 태그의 index를 받아 배열에서 제거한다.
const removeHashTag = (index) => {
if (index >= 0 && index < hashState.hashArr.length) {
hashState.hashArr.splice(index, 1);
}
}
2.2 입력창이 없는 상태에서 Backspace를 누르면 마지막 태그를 삭제
input에 @keyup.delete 이벤트에 삭제 함수를 정의하고 연결했다. vue에서는 backspace 이벤트 핸들러를 단독으로 제공하지는 않지만, backspace와 delete를 눌렀을 때의 처리를 위한 @keyup.delete 이벤트를 제공한다.
<input class="input-tag" type="text"
v-model="hashState.hashtag"
@keyup.space="onKeyUpSpace"
@keyup.delete="onKeyUpBackspace"
:placeholder="hashState.hashArr.length < 5 ? '#태그 입력 (최대 5개)' : '최대 5개까지 입력'" :disabled="hashState.hashArr.length >= 5"
/>
e.code가 backspace인지 확인하고, 현재의 입력된 태그가 없는 상태에서 backspace를 눌렀을 때 마지막 태그를 제거한다.
const onKeyUpBackspace = (e) => {
if (e.code === 'Backspace' && hashState.hashtag === '') {
if (hashState.hashArr.length > 0) {
hashState.hashArr.pop();
}
}
}
3. 드래그 앤 드롭으로 순서 변경 가능하도록
해당 기능은 추가로 요청이 들어왔던 부분인데, 드래그 앤 드롭으로 인덱스 값의 위치를 감지하는 것이 쉽지 않을 것이라고 느껴졌다. 하지만 Javascript에서 제공하는 드래그 앤 드롭(drag and drop) API를 활용하면 매우 간단하게 구현이 가능한 부분이었다. 드래그 앤 드롭(drag and drop) API에 관한 내용은 아래 문서들을 참조했다.
https://developer.mozilla.org/ko/docs/Web/API/DataTransfer
https://www.tcpschool.com/html/html5_api_dragAndDrop
Vue에서 제공하는 Drag Event Handler + Javascript의 drag and drop API로 작성한 코드이다.
<div class="hash-item" v-for="(tag, index) in hashState.hashArr" :key="index" @click="removeHashTag(index)"
draggable="true"
@dragstart="onDragStart(index, $event)"
@dragover="onDragOver(index, $event)"
@drop="onDrop(index, $event)"
>
event.dataTransfer.setData 메소드로 드래그가 시작된 index의 값을 저장하고,
event.dataTransfer.getData 메소드로 받은 해당 시작 index의 값 + onDrop이 된 index의 값을 가지고 배열의 순서를 변경하는 로직을 적용했다.
const onDragStart = (index, event) => {
event.dataTransfer.setData('tagIndex', index);
};
const onDragOver = (index, event) => {
event.preventDefault();
};
const onDrop = (index, event) => {
event.preventDefault();
const draggedIndex = event.dataTransfer.getData('tagIndex');
const draggedTag = hashState.hashArr[draggedIndex];
hashState.hashArr.splice(draggedIndex, 1);
hashState.hashArr.splice(index, 0, draggedTag);
};
처음에는 @dragstart, @drop 에 대한 부분만 작성했는데 작동하지 않았다.
그 이유는 @dragover 이벤트에 대한 처리를 따로 해주지 않아서 발생했던 문제이다.
dragover event는 드래그 중인 아이템이 지나가는 동안 계속해서 발생한다. 해당 이벤트를 preventDefault() 처리해주지 않으면 브라우저의 기본 동작이 실행되어 드래그 앤 드롭 동작이 원할하게 이루어지지 않는다. 따라서 해당 관련 함수를 작성해서 드래그 앤 드롭 기능을 성공적으로 구현할 수 있었다.
'Vue.js' 카테고리의 다른 글
Vue Transition: UI에 생명 불어넣기 (1) | 2024.10.29 |
---|---|
[Vue.js] v-model을 활용한 컴포넌트 간 데이터 바인딩 (2) | 2024.01.27 |