컴포넌트/필드 컴포넌트

RadioGroupField (라디오그룹 필드)

VanillaFront 2026. 9. 12. 19:59

Va.RadioGroupField — 라벨 + 배타 선택 라디오 그룹

Va.RadioGroup이 순수 배타 선택 그룹이라면, Va.RadioGroupField는 그 위에 폼 라벨·필수 표시·검증 메시지를 얹은 완성 폼 필드입니다. Field 계열 아키텍처 그대로, 내부에 Va.RadioGroup을 소유하는 Composition 구조. 실무에서 라디오를 쓸 때 가장 자주 만나게 되는 컴포넌트.

  • 클래스: Va.RadioGroupField  va_component.js:11343
  • short name: radioGroupField
  • 상속: Va.Field (다른 Field 형제들과 같음)
  • 내부 컴포넌트: Va.RadioGroup 인스턴스 (fieldComponent)
  • isContainer: true
  • 베이스 CSS: va-radio-group-field


1. 기본 사용

{
    tagName: 'radioGroupField',
    label: '결제 방식',
    key: 'code',
    display: 'name',
    data: [
        { code: 'CARD', name: '신용카드', checked: true },
        { code: 'BANK', name: '계좌이체' },
        { code: 'PHONE', name: '휴대폰 결제' }
    ],
    required: true,
    onChange: 'onPaymentChange'
}

라벨 + 검증 + 배타 선택 라디오들이 한 번에 세팅. 값은 선택된 하나의 key로 다룹니다.


2. Field 계열에서의 위치

Va.Field
   ├─ Va.InputField           ← Va.Input
   ├─ Va.CheckboxField        ← Va.Checkbox            (단일)
   ├─ Va.CheckboxGroupField   ← Va.CheckboxGroup       (다중)
   ├─ Va.RadioField           ← Va.Radio               (단일)
   ├─ Va.RadioGroupField      ← Va.RadioGroup          (배타)         ← 이 문서
   └─ ...

RadioGroupField의 정체: Field 베이스 + 내부에 Va.RadioGroup 인스턴스.


3. RadioGroup / 다른 Field와의 차이

항목Va.RadioGroupVa.RadioGroupFieldVa.CheckboxGroupField

폼 라벨
검증 메시지
info 툴팁
필수 표시(별표)
선택 개수 1개 (배타) 1개 (배타) N개 (다중)
값 형태 단일 key 단일 key key 배열
자체 배타 로직 ✓ (내부 위임)
상속 Component Field Field

한 줄 요약: "폼 안 라벨 붙은 배타 선택 라디오 그룹."


4. 주요 속성

데이터 관련 (RadioGroup 계승)

속성기본값설명

data 옵션 배열. 각 항목 { [key], [display], checked }
key 'key' 값 필드명
display 'display' 표시 필드명

배치·상태

속성기본값설명

direction 'horizontal' 'horizontal'(가로) / 'vertical'(세로)
radioLabelClick true 라디오 옆 라벨 클릭으로 선택 가능
readonly / disabled 상태 (자식 전체에 전파)
size 크기

라벨 관련 (Field 상속)

속성설명

label 폼 라벨
labelPosition top / bottom / left / right
labelWidth 라벨 폭
noLabel 폼 라벨 숨김
infoButton info 아이콘
required 필수 표시

검증

속성설명

validation {state, size, message}
validationState success / warning / error
validationMessage 메시지

세부 커스터마이즈 (radioGroup 옵션 키)

{
    tagName: 'radioGroupField',
    label: '결제 방식',
    radioGroup: {                    // ← 내부 RadioGroup에 직접 전달
        stopPropagation: false
    }
}

각 Field 계열 옵션 키:

  • InputField → input
  • CheckboxField → checkbox
  • CheckboxGroupField → checkboxGroup
  • RadioField → radio
  • RadioGroupField → radioGroup

5. 이벤트

이벤트시그니처발생 시점

change (component, element, value, evt) 라디오 선택 시. value는 선택된 항목의 key
click (component, element, value, evt) 클릭 시. value는 클릭된 항목의 key

change 콜백 예시

onPaymentChange(comp, el, value, evt) {
    console.log('선택됨:', value);   // 'CARD' / 'BANK' / 'PHONE'
    if (value === 'CARD') {
        this.getRef('cardInfo').setDisplay('flex');
    }
}

RadioGroup과 동일하게 change/click의 세 번째 인자가 선택된 key. 배타 선택이라 그것이 곧 "현재 값".

이벤트 목록이 매우 짧음 — 소스에 this.events = ['change']만 명시. focus/blur 등 표준 Field 이벤트가 재발화 목록에 없어, 필요하면 component.fieldComponent에 직접 리스너 붙여야 합니다.


6. 메서드 (RadioGroup 위임)

값 조회·세팅

메서드설명

getValue() 선택된 항목의 단일 key 반환. 아무것도 선택 안 됐으면 null
setValue(key) 해당 key를 가진 항목 선택 (배타 로직 자동)
getDisplay() 선택된 항목의 display 배열 (실제로는 원소 하나)
getDisplayAsText() display를 한 줄 문자열로

데이터 관리

메서드설명

setData(data) 데이터 전체 교체
getData() 현재 데이터 반환
addData(item) 항목 추가 (기본 체크됨 — 배타 주의)
insertData(item, index) 특정 위치에 삽입
append(component) 프로그램적으로 라디오 자식 추가

상태

메서드설명

setDisabled(bool) 자식 전체 비활성화
setReadOnly(bool) 자식 전체 읽기 전용

검증

메서드설명

setValidation(state, message) 검증 표시 + aria
clearValidation() 검증 해제

주의: focus() 편의 메서드 없음. 첫 라디오에 포커스 주려면 component.fieldComponent.getChildComponents()[0].focus() 우회.


7. 내부 구조

<div elname="element" class="va-field va-radio-group-field [vertical|horizontal]"
     field="true">
  <div elname="inner" class="field-inner">
    <div elname="labelDiv" class="label-div">
      <label cpname="label">결제 방식 <span class="required">*</span></label>
    </div>
    <div elname="comment" class="field-comment"></div>
    <div elname="fieldDiv" class="field-div">
      <div cpname="field" class="va-radio-group [horizontal|vertical]">
        ← 내부 Va.RadioGroup
        <div class="radio-group-inner">
          <div class="va-radio">…</div>
          <div class="va-radio">…</div>
          <!-- 자식 라디오들 -->
        </div>
      </div>
    </div>
  </div>
  <div elname="validationDiv" style="display:none">
    <div class="va-validation">...</div>
  </div>
</div>

두 개의 방향 축 각각 존재:

  • labelPosition — 폼 라벨의 위치 (Field 담당)
  • direction — 그룹 안 라디오 배치 방향 (RadioGroup 담당)

두 옵션을 자유롭게 조합할 수 있습니다.


8. update()의 200ms 지연

CheckboxGroupField와 동일한 특성. update()에서 상태 반영이 _setTimer('update', ..., 200)으로 감싸져 200ms 후 실행 (va_component.js:11401-11407).

this._setTimer('update', ()=>{
    this.fieldComponent.setReadOnly(this.readonly);
    this.fieldComponent.setDisabled(this.disabled);
    ...
}, 200)

실무적 함의:

  • setReadOnly() / setDisabled() 호출 후 즉시 UI에 반영되지 않을 수 있음
  • 연속 호출 시 마지막 것만 반영됨 (_setTimer 특성)

프로그램적으로 상태 바꾸고 바로 다른 로직 진행할 때 이 지연을 인지하세요.


9. 언제 쓰나

RadioGroupField가 맞을 때

  • 폼 안 배타 선택 필드 (성별, 결제 방식, 등급, 배송 방식)
  • 항목 2~5개 정도
  • 라벨·필수·검증이 필요할 때
  • 서버에서 받은 동적 옵션 목록

다른 걸 쓸 때

  • 라벨 없이 인라인 → Va.RadioGroup
  • 여러 선택 → Va.CheckboxGroupField
  • 항목 6개 이상 → Va.ComboboxField (드롭다운)
  • 세그먼트 UI → Va.SegmentedControl
  • 단일 on/off → Va.CheckboxField / Va.SwitchField

10. 흔한 조합 예시

// 표준
{
    tagName: 'radioGroupField',
    label: '결제 방식',
    key: 'code',
    display: 'name',
    data: [
        { code: 'CARD', name: '신용카드', checked: true },
        { code: 'BANK', name: '계좌이체' }
    ],
    required: true,
    onChange: 'onPaymentChange'
}

// 세로 배치
{
    tagName: 'radioGroupField',
    label: '요금제',
    key: 'code',
    display: 'name',
    direction: 'vertical',
    data: [
        { code: 'BASIC',   name: '기본 - 월 9,900원' },
        { code: 'PREMIUM', name: '프리미엄 - 월 19,900원' }
    ]
}

// 좌측 라벨 (폼 정렬)
{
    tagName: 'radioGroupField',
    label: '성별',
    labelPosition: 'left',
    labelWidth: 100,
    key: 'code',
    display: 'name',
    data: [
        { code: 'M', name: '남성' },
        { code: 'F', name: '여성' }
    ]
}

// info 툴팁
{
    tagName: 'radioGroupField',
    label: '수신 방식',
    infoButton: {
        tooltip: '알림을 받을 방식을 선택하세요'
    },
    key: 'code',
    display: 'name',
    data: [ ... ]
}

// 서버에서 데이터 로드
mounted() {
    OptionService.list(this, {}, (view, ok, res) => {
        if (ok) view.getRef('opt').setData(res.data.list);
    });
}

// 초기값 프로그램적 세팅
this.getRef('opt').setValue('BANK');

// 검증
onSubmit() {
    const method = this.getRef('payment').getValue();
    if (method == null) {
        this.getRef('payment').setValidation('error', '결제 방식을 선택하세요');
        return;
    }
    this.getRef('payment').clearValidation();
    // ...
}

11. 실전 예 — 회원가입 요금제 선택

class Signup extends Va.View {
    async mounted() {
        // 사용 가능한 요금제 로드
        const res = await PlanService.list(this);
        if (res.result) {
            this.getRef('plan').setData(res.data.list);
        }
    }

    onPlanChange(comp, el, value, evt) {
        // 요금제에 따라 상세 정보 표시
        const plan = this.getRef('plan').getData().find(p => p.code === value);
        if (plan) {
            this.getRef('detail').setValue(plan.description);
        }
    }

    onSubmit(btn, el, evt) {
        const plan = this.getRef('plan').getValue();

        if (plan == null) {
            this.getRef('plan').setValidation('error', '요금제를 선택하세요');
            return;
        }
        this.getRef('plan').clearValidation();

        SignupService.register(this, { plan }, (view, ok) => {
            if (ok) new Va.Alert({ title: '완료', message: '가입 완료' }).show(view);
        });
    }

    config() {
        return {
            tagName: 'page',
            tags: [{
                tagName: 'panel',
                tags: [
                    { tagName: 'h2', innerHTML: '요금제 선택' },
                    {
                        tagName: 'radioGroupField',
                        ref: 'plan',
                        label: '요금제',
                        key: 'code',
                        display: 'name',
                        direction: 'vertical',
                        required: true,
                        onChange: 'onPlanChange'
                    },
                    {
                        tagName: 'inputField',
                        ref: 'detail',
                        label: '요금제 설명',
                        readonly: true
                    },
                    {
                        tagName: 'button',
                        text: '가입 완료',
                        appearance: 'primary',
                        onClick: 'onSubmit'
                    }
                ]
            }]
        };
    }
}

12. 알아두면 좋을 주의사항

  1. getValue()는 단일 key — CheckboxGroupField(배열)와 다름. null 반환 가능.
  2. setValue(key)가 자동 배타 처리 — 다른 항목 자동 해제.
  3. change/click의 세 번째 인자는 선택된 key — 그대로 사용 가능.
  4. 이벤트 목록이 ['change']만 명시 — focus/blur 등 표준 Field 이벤트 재발화 안 됨.
  5. update()가 200ms 지연  setReadOnly/setDisabled 즉시 반영 안 됨.
  6. addData는 새 항목이 자동으로 체크됨 — 배타 로직 상 기존 선택이 해제될 수 있음.
  7. getDisplay()는 배열 반환 — 이름과 달리 배열. [0] 접근이나 getDisplayAsText() 사용.
  8. 검증 자동 리셋 없음  change 후 명시적으로 clearValidation() 호출 필요할 수 있음.
  9. 폼 라벨과 그룹 방향 독립  labelPosition: 'left' + direction: 'vertical' 조합 가능.
  10. focus() 편의 메서드 없음 — 자식 첫 번째에 포커스 주려면 우회.
  11. HTML name 자동 그룹핑 — 표준 폼 서브밋에서도 배타.
  12. removeData/modifyData 없음 — 전체 교체는 setData().

13. radioGroupField vs checkboxGroupField vs comboboxField 선택

상황추천

여러 옵션 중 하나만 선택 (2~5개) radioGroupField
여러 옵션 중 여러 개 checkboxGroupField
옵션 6개 이상 comboboxField (드롭다운)
세그먼트 UI (붙어있는 버튼) segmentedControl
단일 on/off checkboxField / switchField

"옵션 2~5개 배타 선택은 radioGroupField, 그 이상은 comboboxField" — 기본 원칙.


참고

'컴포넌트 > 필드 컴포넌트' 카테고리의 다른 글

SliderField (슬라이더 필드)  (0) 2026.09.12
Slider (슬라이더)  (0) 2026.09.12
RadioGroup (라디오 그룹)  (0) 2026.09.12
RadioField (라디오 필드)  (1) 2026.09.12
Radio (라디오 버튼)  (0) 2026.09.12