컴포넌트/필드 컴포넌트

MonthPicker (월선택)

VanillaFront 2026. 9. 12. 18:52

Va.MonthPicker — 년-월을 선택하는 캘린더 팝업

년도만 다루는 Va.YearPicker와 년-월-일을 다루는 Va.DatePicker의 중간 자리를 차지하는 컴포넌트입니다. 팝업엔 특정 년도의 12개월 격자가 표시되고, 좌우 화살표로 년도를 이동합니다. 월별 매출·월별 통계 필드에 정확히 어울려요.

  • 클래스: Va.MonthPicker  va_component.js:5947
  • short name: monthPicker
  • 상속: Va.PureField (YearPicker·DatePicker와 형제)
  • isContainer: true
  • 베이스 CSS: va-monthpicker (팝업 va-menu-pop, calendar-inner)


1. 기본 사용

{
    tagName: 'monthPicker',
    value: '202412',              // YYYYMM 6자리
    onSelect: 'onMonthChange'
}
  • 화면 표시: 2024-12
  • 저장 값(getValue()): 202412
  • 팝업: 12개월 격자 + 위쪽 화살표로 년도 이동

2. Va.YearPicker / Va.DatePicker와의 차이

항목Va.YearPickerVa.MonthPickerVa.DatePicker

입력 대상 년 (4자리) 년-월 (6자리) 년-월-일 (8자리)
저장 값 '2024' '202412' '20241225'
팝업 UI 10년 격자 12개월 격자 일 단위 캘린더
마스킹 없음 ____-__ ____-__-__
화살표 이동 10년씩 1년씩 1개월씩
오늘 버튼 ✓ (이번 달로 이동) ✓ (오늘로)
popWidth 기본 240 240 260

한 줄 요약: "년-월 단위로 필요할 때의 정답. 월간 리포트·분기 통계·월별 예산."


3. 주요 속성

날짜 포맷

속성기본값설명

dateFormat 'ym' 화면 순서 — ym (년-월) / my (월-년)
dateSeperator '-' 화면 구분자
valueDateFormat 'ym' 저장 값 순서
valueDateSeperator '' 저장 값 구분자 (기본 없음)
masking 자동 (____-__ 또는 __-____) dateFormat에 따라 자동

핵심 개념 — DatePicker와 동일한 화면/저장 값 분리:

{
    tagName: 'monthPicker',
    dateFormat: 'my',            // 화면: 12-2024
    dateSeperator: '-',
    valueDateFormat: 'ym',       // 저장: 202412 (서버가 원하는 형태)
    valueDateSeperator: ''
}

팝업

속성기본값설명

popWidth 240 팝업 폭
expanded false 초기 상태

PureField 상속

value, placeholder, readonly, disabled, size, appearance, stopPropagation 등 표준.


4. 날짜 포맷 조합 예시

한국 표준

{ tagName: 'monthPicker', value: '202412' }
// 화면: 2024-12
// 저장: 202412

미국식 표시

{
    tagName: 'monthPicker',
    dateFormat: 'my',
    dateSeperator: '/',
    valueDateFormat: 'ym'
}
// 화면: 12/2024
// 저장: 202412

서버가 하이픈 포함 원할 때

{
    tagName: 'monthPicker',
    valueDateSeperator: '-',
    value: '2024-12'
}
// 화면: 2024-12
// 저장: 2024-12

5. 이벤트

이벤트시그니처발생 시점

select (component, element, value, evt) 팝업에서 월 클릭 시
beforePop / afterPop / hidePop 팝업 표시/숨김  
expand / collapse 팝업 확장/축소  
focus / blur 표준  

6. 메서드 — 두 벌의 setter/getter

DatePicker·DateTimePicker와 같은 패턴:

메서드설명

setValue(value) valueDateFormat 규약 값 세팅 (서버에서 받은 값을 세팅할 때)
getValue() valueDateFormat 규약으로 반환 (서버 전송용)
setRawValue(value) 항상 YYYYMM 원시 값 세팅 (내부용)
getRawValue() 항상 YYYYMM 원시 값 반환 (내부용)

동작 흐름:

  • setValue('202412') → 내부 저장은 YYYYMM → 화면은 dateFormat/dateSeperator로 변환 표시
  • getValue() → 화면 값 파싱 → valueDateFormat/valueDateSeperator에 맞춰 반환

팝업 제어

메서드설명

showMonthPickerPop(evt) / hideMonthPickerPop() 팝업 강제 열기/닫기
showPop() / expand() drawCalendar() 호출 (레거시 별칭)
hidePop() / collapse() 팝업 숨김
drawCalendar(value) 팝업 내부 렌더링

상태

메서드설명

setDisabled(bool) / setReadOnly(bool) PureField 상속
focus() / blur() 포커스

7. 팝업 UI 구조

drawCalendar()가 렌더링:

┌─────────────────────────────┐
│  ←    2024    →             │  ← 헤더 (년도 표시, ←→로 년도 이동)
├─────────────────────────────┤
│  1월    2월    3월           │
│  4월    5월    6월           │
│  7월    8월    9월           │  ← 3×4 격자 (12개월)
│  10월   11월   12월          │
├─────────────────────────────┤
│         오늘                 │  ← "오늘" 버튼 (이번 달로 이동)
└─────────────────────────────┘
  • 년도 이동 (← /  버튼) — 1년씩
  • 12개월 버튼 격자 — 클릭 시 select 이벤트 발생 + 팝업 닫힘
  • 오늘 버튼 — 현재 년-월로 즉시 이동 (Va.Util.getNowYearMonth())

8. 내부 구조

<div elname="element" class="va-monthpicker [size]..." tag-name="monthPicker" field="true">
  <div elname="fieldWrapper" class="field-wrapper">
    <input elname="field" type="text" placeholder="____-__" style="border:0px">
    <div elname="focusLine" class="focus-line"></div>
    <div elname="calendarIconWrapper">
      <span elname="calendarIcon" class="icon menu ico_calender_ltr">📅</span>
    </div>
  </div>
  <div elname="popDiv" class="menu-button-pop-div">
    <div elname="pop" class="va-menu-pop" style="position:absolute; display:none">
      <div elname="popInner" class="calendar-inner">
        <div class="date-picker-pop-header">← 2024 →</div>
        <!-- 12개월 격자 -->
        <div class="date-picker-pop-bottom">
          <button>오늘</button>
        </div>
      </div>
    </div>
  </div>
</div>

9. 팝업 상태머신 연동

프레임워크 공용 팝업 상태머신 참여:

  • Va.addAutoHide(this.popElement) — 외부 클릭 시 자동 닫힘
  • Va.hideOtherComponents(this) — 다른 팝업 자동 닫음
  • getHiddenAreaElement()로 팝업 이동 → 부모 stacking 회피
  • 뷰포트 넘침 시 위로 자동 뒤집기

10. 언제 쓰나

MonthPicker가 맞을 때

  • 월별 매출·통계 리포트 필터
  • 월별 예산 등록
  • 월간 근태·급여 대상 월 선택
  • 분기 통계 (월을 선택해 소속 분기 파악)
  • 년-월 단위로 데이터가 관리되는 모든 상황

다른 걸 쓸 때

  • 년도만 필요 → Va.YearPicker
  • 년-월-일 필요 → Va.DatePicker
  • 시간까지 → Va.DateTimePicker
  • 라벨 붙은 폼 필드 → Va.MonthField
  • 년-월 콤보 UI (드롭다운 두 개) → Va.ComboboxField 조합
  • 특정 월 목록만 (예: 최근 6개월) → Va.ComboboxField

11. 흔한 조합 예시

// 한국 표준
{ tagName: 'monthPicker', value: '202412' }

// 미국식 표시
{
    tagName: 'monthPicker',
    dateFormat: 'my',
    dateSeperator: '/',
    valueDateFormat: 'ym',
    value: '202412'          // 화면: 12/2024
}

// 서버가 하이픈 포함 원할 때
{
    tagName: 'monthPicker',
    valueDateSeperator: '-',
    value: '2024-12'
}

// 오늘 년-월로 초기화
mounted() {
    const now = new Date();
    const ym = now.getFullYear() +
               String(now.getMonth() + 1).padStart(2, '0');
    this.getRef('month').setValue(ym);
}

// 넓은 팝업
{
    tagName: 'monthPicker',
    popWidth: 300,
    value: '202412'
}

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

  1. value는 6자리 문자열  '202412' 형태. Date 객체 넘기면 .substring 호출에서 크래시.
  2. setValue()는 value.replaceAll 사용 — null이나 숫자 넘기면 크래시. 문자열 확실히.
  3. getValue()는 문자열 반환 — 빈 값일 땐 ''.
  4. dateFormat과 valueDateFormat 혼동 주의 — 화면 vs 저장.
  5. 마스킹 자동 — dateFormat에 따라 자동. 사용자 지정 시 덮어씀.
  6. 필드에 잘못된 년월 타이핑 가능 — 마스킹은 자릿수만 강제. 9999-99 같은 값도 입력 가능. 별도 검증 필요.
  7. popWidth 기본 240 — DatePicker(260)보다 작음. 12개월 격자엔 충분.
  8. ym / my 외 다른 포맷 없음 — 년-일 조합 등은 미지원.
  9. 팝업 상태머신 참여 — 다른 팝업과 자동 상호 배타.
  10. 초기값 없으면 오늘 년-월  drawCalendar()가 Va.Util.getNowYearMonth()로 fallback.
  11. timezone 처리 있음 — 코드에 KST 오프셋 계산 있어 한국 시간대 기준. UTC 정확도가 중요하면 별도 확인.
  12. 오늘 버튼은 이번 달로 이동만 — 자동 선택은 아님. 클릭해야 확정.

13. 실전 예 — 월별 매출 조회

class MonthlySales extends Va.View {
    mounted() {
        // 지난 달로 초기화
        const now = new Date();
        now.setMonth(now.getMonth() - 1);
        const lastMonth = now.getFullYear() +
                          String(now.getMonth() + 1).padStart(2, '0');
        this.getRef('month').setValue(lastMonth);
        this.loadSales(lastMonth);
    }

    onMonthSelect(picker, el, value, evt) {
        this.loadSales(value);
    }

    loadSales(yyyymm) {
        SalesService.getMonthly(this, { yyyymm }, (view, ok, res) => {
            if (ok) view.getRef('grid').setData(res.data.list);
        });
    }

    config() {
        return {
            tagName: 'page',
            tags: [{
                tagName: 'panel',
                tags: [
                    { tagName: 'h2', innerHTML: '월별 매출' },
                    {
                        tagName: 'div',
                        layout: 'ds-flex fd-row ai-center gap-s',
                        tags: [
                            { tagName: 'label', innerHTML: '대상 월:' },
                            {
                                tagName: 'monthPicker',
                                ref: 'month',
                                onSelect: 'onMonthSelect',
                                style: { width: '150px' }
                            }
                        ]
                    },
                    {
                        tagName: 'grid',
                        ref: 'grid',
                        columns: [
                            { key: 'day',     title: '일',   width: 60 },
                            { key: 'revenue', title: '매출', fillRatio: 1 },
                            { key: 'orders',  title: '건수', width: 100 }
                        ]
                    }
                ]
            }]
        };
    }
}

14. MonthField가 필요하면

라벨·검증까지 필요한 폼 필드로 쓰려면 Va.MonthField (Field 계열 래퍼)를 사용하세요:

{
    tagName: 'monthField',
    label: '대상 월',
    value: '202412',
    required: true
}

DateField·YearField와 같은 Composition 패턴으로, 내부에 Va.MonthPicker를 소유합니다.

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

TimePicker (시각선택)  (0) 2026.09.12
MonthField (년월필드)  (0) 2026.09.12
YearField (연도필드)  (1) 2026.09.11
YearPicker (연도선택)  (0) 2026.09.11
DateTimeField (일시필드)  (0) 2026.09.11