코딩/Javascript

[TIL] 22.12.22 - Vue3 약관동의 체크박스

AMD만세 2022. 12. 22. 17:59

 Vue3 체크박스

 

1) 배운 내용

<template>
  <div class="row items-center wrapper">
    <div class="row signinBox">
      <form action="#" id="form__wrap">
        <div class="signinBox__form">
          <div class="row items-center signinBox__checkAll" id="checkAll">
            <input type="checkbox" class="check0" id="check0" @click="changeColor(); checkBox();">
            <label for="check0"></label>
            <span class="q-mr-sm text-bold" style="font-size: 18px;">전체 동의</span>
            <span class="checkbox__label" style="text-decoration: none; font-size: 1.3rem;">(선택항목 포함)</span>
          </div>

          <div v-for="list in lists" :key="list" class="signinBox__wrapper" @click="openDrawer(list);">
            <div class="row items-center signinBox__check" @click="checkBoxCheck()">
              <input type="checkbox" :id="'input' + list.id" name="agree">
              <label :for="'input' + list.id"></label>
              <span class="checkbox__label" style="font-size: 15px;">{{ list.label }}</span>
              <div :id="'img' + list.id" class="img__arrowUp"></div>
            </div>
            <div :id="'check' + list.id" class="row item__content">
              {{ list.content }}
            </div>
          </div>
        </div>
        <div class="row justify-center signinBox__buttonWrapper">
          <button type="submit" class="item__button" disabled><a style="color: white;"
              href="/#/membership2">다음</a></button>
        </div>
      </form>
    </div>
  </div>
</template>

<script>
import { defineComponent, ref } from 'vue'


export default defineComponent({
  setup() {
    return {
      right: ref(false)
    }
  },
  data() {
    return {
      lists: [
        { id: '1', label: '서비스 이용약관 (필수)', content: '1.개인정보처리방침 회원 가입 의사의 확인, 연령 확인 및 법정대리인 동의 진행, 이용자 및 법정대리인의 본인 확인, 이용자 식 별, 회원탈퇴 의사의 확인 등 회원관리를 위하여 개인정보를 이용합니다. 콘텐츠 등 기존 서비스 제공(광고 포함)에 더하여, 인구통계학적 분석, 서비스 방문 및 이용기록의 분석, 개인정보 및 관심에 기반한 이용자 간 관계의 형성, 지인 및 관심사 등에 기반한 맞춤형 서비스 제공 등 신규 서비스 요소의 발굴 및 기존 서비 스 개선 등을 위하여 개인정보를 이용합니다. 법령 및 회사 이용약관을 위반하는 회원에 대한 이용 제한 조치, 부정 이용 행위를 포함하여 서비스의 원활 한 운영에 지장을 주는 행위에 대한 방지 및 제재, 계정도용 및 부정거래 방지, 약관 개정 등의 고지사항 전 달, 분쟁조정' },
        { id: '2', label: '개인정보 수집 및 이용 동의 (필수)', content: '1.개인정보처리방침 회원 가입 의사의 확인, 연령 확인 및 법정대리인 동의 진행, 이용자 및 법정대리인의 본인 확인, 이용자 식 별, 회원탈퇴 의사의 확인 등 회원관리를 위하여 개인정보를 이용합니다. 콘텐츠 등 기존 서비스 제공(광고 포함)에 더하여, 인구통계학적 분석, 서비스 방문 및 이용기록의 분석, 개인정보 및 관심에 기반한 이용자 간 관계의 형성, 지인 및 관심사 등에 기반한 맞춤형 서비스 제공 등 신규 서비스 요소의 발굴 및 기존 서비 스 개선 등을 위하여 개인정보를 이용합니다. 법령 및 회사 이용약관을 위반하는 회원에 대한 이용 제한 조치, 부정 이용 행위를 포함하여 서비스의 원활 한 운영에 지장을 주는 행위에 대한 방지 및 제재, 계정도용 및 부정거래 방지, 약관 개정 등의 고지사항 전 달, 분쟁조정' },
        { id: '3', label: '앱 알림, 마케팅 정보 수신 동의 (선택)', content: '1.개인정보처리방침 회원 가입 의사의 확인, 연령 확인 및 법정대리인 동의 진행, 이용자 및 법정대리인의 본인 확인, 이용자 식 별, 회원탈퇴 의사의 확인 등 회원관리를 위하여 개인정보를 이용합니다. 콘텐츠 등 기존 서비스 제공(광고 포함)에 더하여, 인구통계학적 분석, 서비스 방문 및 이용기록의 분석, 개인정보 및 관심에 기반한 이용자 간 관계의 형성, 지인 및 관심사 등에 기반한 맞춤형 서비스 제공 등 신규 서비스 요소의 발굴 및 기존 서비 스 개선 등을 위하여 개인정보를 이용합니다. 법령 및 회사 이용약관을 위반하는 회원에 대한 이용 제한 조치, 부정 이용 행위를 포함하여 서비스의 원활 한 운영에 지장을 주는 행위에 대한 방지 및 제재, 계정도용 및 부정거래 방지, 약관 개정 등의 고지사항 전 달, 분쟁조정' },
      ],
      checkOthers: [],
      statusAll: {},
      checkToggles: [],
      statusCheck: {}
    }
  },
  methods: {
    // 공지사항 세부
    openDrawer(list) {

      const x = document.getElementById('check' + list.id);
      const y = x.classList;
      const a = document.getElementById('img' + list.id);
      const b = a.classList;
      if (y.contains('item__content')) {
        y.replace('item__content', 'active');
        b.replace('img__arrowUp', 'img__arrowDown');
      } else {
        y.replace('active', 'item__content');
        b.replace('img__arrowDown', 'img__arrowUp');
      }
    },
    // 전체 동의
    checkBox() {
      const agreeChkAll = document.querySelector('input[id=check0]');
      const agreeChk = document.querySelectorAll('input[name=agree]');
      const submitButton = document.querySelector('button');
      const checkOthers = []
      const statusAll = agreeChkAll.checked

      agreeChkAll.addEventListener('change', (e) => {

        for (let i = 0; i < agreeChk.length; i++) {
          agreeChk[i].checked = e.target.checked;
          checkOthers.push({
            value: e.target.checked
          })
        }

      });
      // 전체 체크 시 다음버튼 활성화, 해제 시 다음버튼 비활성화
      submitButton.disabled = !submitButton.disabled;
    },
    // 전체동의 체크 후 필수 체크박스 해제 시 전체동의도 해제
    checkBoxCheck() {
      const agreeCheck = document.querySelectorAll('.signinBox__check input');

      console.log('배열로 받기', agreeCheck)
      console.log(agreeCheck[0].checked)
      console.log(agreeCheck[1].checked)
      console.log(agreeCheck[2].checked)

      const checkToggles = [];
      for (let i = 0; i < agreeCheck.length; i++) {
        checkToggles.push(agreeCheck[i])
      }
      console.log(checkToggles)

      const x = Number(checkToggles[0].checked)
      const y = Number(checkToggles[1].checked)
      console.log(x, y)


      checkToggles.splice(0, 2, x, y)

      const sum = checkToggles.reduce((acc, cur, list) => {
        if (acc + cur === 2) {
          document.getElementById('check0').checked = true;
          document.querySelector('button').disabled = false;
        } else {
          document.getElementById('check0').checked = false;
          document.querySelector('button').disabled = true;
        }
      }, 0)
      console.log(sum)

      // 필수 해제 시 전체동의도 해제
      if (checkToggles[0] && checkToggles[1]) {
        document.getElementById('check0').checked = true;
        document.querySelector('button').disabled = false;
      } else {
        document.getElementById('check0').checked = false;
        document.querySelector('button').disabled = true;
      }

    },
    changeColor() {
      const change = document.getElementById('checkAll')
      const color = change.classList;
      if (color.contains('signinBox__checkAll')) {
        color.replace('signinBox__checkAll', 'check__active');
      } else {
        color.replace('check__active', 'signinBox__checkAll');
      }
    }
  },
})

</script>

<style lang="scss" scoped>
@import "src/css/app.scss";

* {
  overflow-x: hidden;
}

.wrapper {
  min-width: 36rem;
  min-height: 100vh;
  margin: 0 auto 0;
}

.signinBox {
  width: 69.3rem;
  margin: 0 auto;
}

.signinBox__label {
  font-size: 4rem;
  color: #980ED8;
  line-height: 5rem;
}

.signinBox__form {
  width: 70rem;
  margin: 0 auto 0;

  .signinBox__formTitle {
    margin: 0 0 4.4rem;
    font-size: 3.5rem;
    line-height: 4.2rem;
    font-weight: $font-weight-semi-bold;
    text-align: center;
  }
}

.signinBox__logo {
  margin-bottom: 20px;
}

.signinBox__label {
  margin-bottom: 70px;
}

.signinBox__checkAll {
  width: 69.3rem;
  height: 7rem;
  padding: 0;
  background-color: #F4F4F4;
  border-radius: 5px;
  margin-bottom: 19px;
}

.check__active {
  width: 69.3rem;
  height: 7rem;
  padding: 0;
  background-color: #F4E7FB;
  border-radius: 5px;
  margin-bottom: 19px;
}

input[type="checkbox"] {
  display: none;
}

input[type="checkbox"]+label {
  display: inline-block;
  width: 30px;
  height: 30px;
  overflow-y: hidden;
  border: 1px solid #A8ABAF;
  border-radius: 3px;
  position: relative;
  margin: 0 11px 0 24px;
}

input[id="check0"]:checked+label::after {
  content: '✔︎';
  font-size: 25px;
  color: white;
  background-color: #980ED8;
  width: 30px;
  height: 30px;
  text-align: center;
  position: absolute;
  left: 0;
  top: 0;
}

input[id="input1"]:checked+label::after {
  content: '✔︎';
  font-size: 25px;
  color: white;
  background-color: #980ED8;
  width: 30px;
  height: 30px;
  text-align: center;
  position: absolute;
  left: 0;
  top: 0;
}

input[id="input2"]:checked+label::after {
  content: '✔︎';
  font-size: 25px;
  color: white;
  background-color: #980ED8;
  width: 30px;
  height: 30px;
  text-align: center;
  position: absolute;
  left: 0;
  top: 0;
}

input[id="input3"]:checked+label::after {
  content: '✔︎';
  font-size: 25px;
  color: white;
  background-color: #980ED8;
  width: 30px;
  height: 30px;
  text-align: center;
  position: absolute;
  left: 0;
  top: 0;
}

.checkbox__label {
  color: #666C70;
  text-decoration: underline;
}

.signinBox__check {
  width: 69.3rem;
  height: 7rem;
  padding: 0;
  border: 1px solid rgba(43.9%, 43.9%, 43.9%, 0.37);
  border-radius: 5px;
  margin-bottom: 6px;
  position: relative;
}

.img__arrowUp {
  width: 30px;
  height: 30px;
  position: absolute;
  background-image: url("../assets/images/img_arrow_up.svg");
  background-size: contain;
  background-repeat: no-repeat;
  right: 18px;
}

.img__arrowDown {
  width: 30px;
  height: 30px;
  position: absolute;
  background-image: url("../assets/images/img_arrow_down.svg");
  background-size: contain;
  background-repeat: no-repeat;
  right: 18px;
}

.item__content {
  width: 100%;
  display: none;
  background-color: #F9F9F9;
}

.active {
  width: 100%;
  display: block;
  background-color: #F9F9F9;
}

.signinBox__buttonWrapper {
  width: 100%;
  margin-top: 5rem;
}

.item__button {
  width: 8.9rem;
  height: 5.2rem;
  border-radius: 4px;
  font-size: 1.8rem;
  background-color: #373737;
  color: white;
  text-decoration: none;
}
</style>

별거 아닌데 이틀 반을 태웠다.

 

<구현된 부분>

1) 체크박스 전체 클릭 시 나머지 체크박스 전체 체크됨.(반대로도 가능)

2) 필수 체크박스 2개 체크 시, 체크박스 전체 체크됨.

3) 전체 체크박스 클릭된 상태에서 필수 체크박스 2개 중 1개 해제했을 때 전체 체크박스 해제됨.

4) 선택 체크박스는 제외.

5) 위 조건대로 '다음' 버튼 활성화 & 비활성화 구현.

 

2) Trouble Shooting

-> document.querySelectorAll로 가져올 경우, 배열이 아닌 NodeList를 가져온다.(배열을 새로 생성한 후 작업해야함)

-> 조건식 if, elseif 보다는 혹여 향후에 약관이 추가될 가능성을 생각해서 reduce 메서드를 활용했다.

-> 바닐라 자바스크립트로 돔을 조작해서 해보려 했는데 Vue에서는 돔을 직접조작하는 걸 지양하라고 함.

 

❇︎리팩토링 필요!