Tempo Di Valse

[Vue.js] 커스텀 RadioButton 만들어 보기 본문

개발/Web

[Vue.js] 커스텀 RadioButton 만들어 보기

TempoDiValse 2022. 2. 23. 17:22
반응형

지난 포스팅에서는,

 

[Vue.js] 커스텀 Checkbox 만들어보기

UI 를 만들다 보면, 디자이너가 그려주는 UI 에서는 시스템에서 제공해주는 UI 와는 많이 다른 신기하게 생긴 컴포넌트들이 많이 보이곤 한다. 게다가 기능이 이미 코어에도 존재하는 것들, 계열

tempodivalse.tistory.com

을 해보았다. 이번에는 체크박스와 비슷한 <input> 의 radio 타입을 커스터마이즈 하는 작업도 하며, 나아가서는 Android 에서는 RadioGroup 이라고 부르는데 라디오 버튼들의 묶음을 만드는 것을 해보도록 하겠다.


1. Vue 컴포넌트 생성

 

먼저, RadioGroup 이라고 .vue 파일을 하나 만들었고 기본 프레임도 작성을 했다.

 

<template>
    <label>
        <input type="radio">
        // 여긴 텍스트가 들어갈 곳
    </label>
</template>

<script>

export default {
    name: 'RadioGroup',
}

</script>

체크박스 때와 같이 <label> 태그를 <input> 밖에 둘러 쌓아 만들고, 라디오 버튼 UI 뿐만 아니라 글씨 영역까지도 클릭하면 반영되도록 할 것이며, 라디오 버튼이기 때문에 type 값은 radio 로 설정을 해놓은 상태이다.

 

2. Style 적용

 

단일 라디오 버튼 UI 수정을 위해 체크박스와 똑같이 기존 UI 를 삭제하고 체크 ON/OFF 이미지가 들어가기 까지의 스타일만 준비를 한다.

<template>
    <label class="radio-item">
        <input type="radio">
        <span class="icon-image check"></span>
        // 여긴 텍스트가 들어갈 곳
    </label>
</template>

<script>

export default {
    name: 'RadioGroup',
}

</script>

<style>

label.radio-item {  }
label.radio-item input[type=radio] { display: none; }

label.radio-item span.icon-image { display: inline-block; width: 22px; height: 22px; }
label.radio-item span.icon-image.check { background-image: url(${check_off_image}); background-size: 100%; }
label.radio-item span.icon-image.check.on { background-image: url(${check_on_image}); background-size: 100%; }


</style>

 

여기에서, 체크박스를 커스터마이즈 할 때와는 다르게 <label> 의 for 속성 지정이 되어있지 않은 것과 <input> 의 이벤트를 받지 않은 것을 확인 할 수 있다. 그 이유는 단일 라디오 버튼이 아닌 라디오 그룹형식으로 컴포넌트를 만들 예정이기 때문에 다음의 과정에서 진행을 해보도록 하자.

 

3. RadioGroup 으로 발전

 

맨 처음에도 이야기 했듯이, 안드로이드 개발자 입장에서 얘기하자면 RadioButton 이 여러개 있어서 RadioGroup 을 이루게 되고, 이 그룹 내에서 값이 바뀌면 그 값을 외부에서 감지할 수 있도록 해야 한다. 하지만 지금의 상태는 단일 라디오버튼이기 때문에 같은 여러 개를 더 만들어야 한다. 그렇다고 복,붙으로 코드를 이어가는 것이 아닌 'v-for' 를 활용하여 여러 개를 만들고, 각각이 하나의 컴포넌트처럼 돌아가도록 만들어보자.

 

먼저, 그룹 처럼 거듭나기 위해 <div> 클래스로 겉을 감싸보았다.

<template>
    <div class="radio-group">
        <label class="radio-item">
            <input type="radio">
            <span class="icon-image check"></span>
            // 여긴 텍스트가 들어갈 곳
        </label>
    </div>
</template>

클래스명은 radio-group 으로 명했고, CSS 스타일도 해당 클래스명으로 조작할 것이다. 다음에는 라디오 버튼이 여러개 나오게 해야 하기 때문에 단일 라디오버튼이 있는 <label> 에 v-for 를 적용할 것이다. 하지만 v-for 는 아이템이 있어야 루프를 돌 수 있기 때문에 외부에서 아이템들을 정의하여 라디오버튼에 적용할 수 있도록 만들어본다.

 

라디오 버튼에 적용할 것을 생각해보면 다음의 3가지가 있을 것이다.

1. <label> 과 이어줄 id 값
2. 각 라디오 버튼이 가지고 있어야 하는 value 값
3. 각 라디오 버튼이 보여줘야 하는 text 값

그래서 외부에서 받아야 할 아이템의 형태는 다음과 같으면 될 것 같다.

{
    ...Codes,
    radioItems: [
        {
            id: 'a', 
            value: 'apple',
            text: '사과'
        },{
            id: 'b',
            value: 'banana',
            text: '바나나'
        }
    ]
}

이 아이템들이 컴포넌트에 들어오도록 바꿔본다 하면,

<template>
    <div class="radio-group">
        <label class="radio-item" v-for="item in radioItems" :for="item.id">
            <input :id="item.id" type="radio" :value="item.value">
            <span class="icon-image check" :class="{ on: value === item.value }"></span>
			{{ text }}
        </label>
    </div>
</template>


<script>

export default {
    name: 'RadioGroup',
    props: {
    	radioItems: Array,
        value: String|Number
    }
}

</script>

식으로 적용이 될 것이다. radioItems 로 들어온 데이터는 v-for 문을 돌면서 똑같은 형식으로 라디오 버튼을 찍어낼 것이다. <label> 의 'for' 속성도 radioItems 안에서 정의되도록 만들었기 때문에 자연스럽게 주입이 될 것이다.

 

그 다음, RadioGroup 의 공통 이름을 설정해야 한다. 원래 radio 의 경우에는 <input type="radio"> 가 여러 개 있다고 작동되는 것이 아니라 <input> 의 name 속성이 서로 같아야 비로소 라디오 버튼의 기능처럼 작동이 되는 것이기 때문에 name 은 필수로 들어가야하는 항목이다. 그래서 약간의 변경이 필요하다.

<template>
    <div class="radio-group">
        <label class="radio-item" v-for="item in radioItems" :for="item.id">
            <input :name="name" :id="item.id" type="radio" :value="item.value">
            <span class="icon-image check" :class="{ on: value === item.value }"></span>
			{{ text }}
        </label>
    </div>
</template>


<script>

export default {
    name: 'RadioGroup',
    props: {
    	name: String,
    	radioItems: Array,
        value: String|Number
    }
}

</script>

외부에서 이름을 정의할 수 있도록 name 을 props 에 추가하였고, <input> 에도 공통으로 들어갈 수 있도록 연결해 주었다. 여러 라디오버튼을 만든다고 해서 ID 값을 name 항목에 작성하면 의도한 대로 작동이 되지 않을 것이다.

 

이제 마지막으로 컴포넌트 외부에서 값이 바뀌는 것을 감지할 수 있도록 양방향 데이터 바인딩 설정을 해보도록 하겠다.

<template>
    <div class="radio-group">
        <label class="radio-item" v-for="item in radioItems" :for="item.id">
            
            <input type="radio" 
                :name="name"
                :id="item.id" 
                :value="item.value" 
                @input="$emit('change', item.value)">
                
            <span class="icon-image check" :class="{ on: value === item.value }"></span>
			{{ text }}
        </label>
    </div>
</template>


<script>

export default {
    name: 'RadioGroup',
    props: {
        name: String,
    	radioItems: Array,
        value: String|Number
    },
    model {
        prop: 'value',
        event: 'change'
    }
}

</script>

변화된 점은, <input> 에 Input 이벤트가 생기게되면 'change' 를 $emit 하도록 설정했다. 컴포넌트 내에서는 'change' 가 호출과 통시에 props 의 value 는 값이 변화가 된다. 이것은 model 속성을 통해 value 를 change 이벤트에 반응하도록 정의 했기 때문에 가능한 일이다.

 

이제 양방향 데이터 바인딩 정의도 완료했기 때문에 외부에서 값을 받고자 한다면,

<radio-group name="select_item" :radio-items="radioItems" v-model="selected" />

이런 방식으로 사용할 수 있을 것이다!

반응형
Comments