티스토리 뷰

나중에 쓸지도 몰라서 해시태그를 추가하는 컴포넌트를 만들었다.

기본적인 기능은 네이버 블로그의 해시태그 기능을 보며 참고했다.

아래는 내가 만든 해시태그 첫 진입시 UI

이걸 클릭하게되면..

이런식으로 focus가 input에 들어가게된다.

enterspace를 누를때마다 태그가 추가되고, 특수문자중복은 안된다.

위의 이미지처럼 중복태그나, 특수문자 입력 후 태그 추가시(enter, space) 경고창이 뜬다.

아래는 만든 해시태그를 불러올 부모 컴포넌트이다.

// 부모 컴포넌트
<hashtags :placeholder="#해시태그를작성하세요"></hashtags>

바인딩된 placeholder로 기본 설명을 넣을 수 있다. 기본값이 들어가 있으니 굳이 쓰고 싶지 않다면 제외해도 된다.

// 부모 js
import Hashtags from '@/components/Hashtags';

components : {
 Hashtags : Hashtags
}

그리고 본 소스. 아래는 구현해놓은 vue code sandbox 를 첨부해놓았다.

<div class="comp_hashtag" @click="setHashtags" ref="group">
  <p class="help" v-if="helpVisible">{{ defaultPlaceholder }}</p>

  <!-- Hashtags -->
  <div class="tags" v-if="!helpVisible">
    <input
      type="text"
      class="fake"
      ref="fake"
      @keydown.backspace.prevent="deleteTag(focusIndex)"
      @keydown.delete.prevent="deleteTag(focusIndex)"
    />
    <span
      class="tag"
      v-for="(row, index) in tags"
      :key="index"
      :class="{active: row.select}"
      @click="selectTag(index)"
      >{{ row.value }}</span
    >
  </div>
  <!--// Hashtags -->

  <div class="inp" v-show="!helpVisible">
    <input
      type="text"
      ref="input"
      v-model.trim="value"
      @focus="initSelect"
      @keydown.space.prevent="addHashTags"
      @keydown.enter.prevent="addHashTags"
      @keydown.backspace="initErrorMsg"
      @keydown.delete="initErrorMsg"
      placeholder="태그입력"
    />
  </div>

  <transition
    enter-active-class="animate__animated animate__fadeInDown animate__faster"
    leave-active-class="animate__animated animate__fadeOut"
  >
    <p class="noti" v-if="this.errorMsg">{{ errorMsg }}</p>
  </transition>
</div>
export default {
  name: 'Hashtags',
  props: ['placeholder'],
  data() {
    return {
      defaultPlaceholder: this.placeholder ? this.placeholder : '#추천태그 #특수문자제외',
      errorMsg: null,
      focusIndex: null,
      helpVisible: true,
      tags: [],
      value: '',
    };
  },
  methods: {
    setVisible() {
      return (this.helpVisible = false);
    },
    async setHashtags() {
      if (this.tags.length > 0) {
        return;
      }

      const result = await this.setVisible();

      if (!result) this.$refs.input.focus();
    },

    addTag() {
      this.tags.push({value: this.value, select: false});
      return true;
    },
    unselectTag() {
      this.tags.forEach(tag => (tag.select = false));
    },
    selectTag(idx) {
      if (this.tags.some(tag => tag.select)) {
        this.unselectTag();
      }

      this.tags[idx].select = !this.tags[idx].select;

      if (!this.tags[idx].select) {
        this.initSelectIndex();
        return;
      }

      this.$refs.fake.focus();
      this.focusIndex = idx;
    },
    deleteTag(idx) {
      if (idx === null) {
        return;
      }

      this.initSelectIndex();
      this.tags.splice(idx, 1);
    },

    initSelect() {
      if (!this.tags.some(tag => tag.select)) {
        return;
      }

      this.unselectTag();
      this.initSelectIndex();
    },
    initSelectIndex() {
      this.focusIndex = null;
    },
    initErrorMsg() {
      this.errorMsg = null;
    },
    validate() {
      if (this.tags.some(tag => tag.value === this.value)) {
        return '중복된 단어를 입력하셨습니다.';
      }

      const regex = /[~!@#$%^&*()+|<>?:{},.="':;/-]/;
      if (regex.test(this.value)) {
        return '특수문자는 태그로 등록할 수 없습니다.';
      }

      return false;
    },
    async addHashTags(event) {
      // CASE 공백
      if (event.target.value === '') {
        this.initErrorMsg();
        event.target.focus();
        return;
      }
      // CASE 유효성(중복,특문)
      const resultMsg = await this.validate();
      if (resultMsg) {
        this.errorMsg = resultMsg;
        this.$refs.input.focus();
        return;
      }

      await this.addTag();

      this.errorMsg = null;
      this.value = null;
      this.$refs.input.focus();
    },

    documentClick(e) {
      if (this.$refs.group !== e.target && !this.$refs.group.contains(e.target)) {
        this.unselectTag();
        this.errorMsg = null;
      }
    },
  },
  mounted() {
    document.addEventListener('mousedown', this.documentClick);
  },
};

구현 :

https://codesandbox.io/s/friendly-morning-6uhud?from-embed=&file=/src/components/Hashtags.vue

 

 

해당 컴포넌트는 모바일은 고려가 되어있지 않다.

왜냐하면 추가된 해시태그는 backspacedelete로 지우게 되어있기 때문이다.
추후에 좀더 추가해서 선택에 따라 모바일 적용도 해놔야겠다.

 

 

 

 

*댓글과 좋아요는 힘이됩니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함