1. 개요 및 이 글에서 얻어갈 것
SAPUI5에서 날짜를 다루는 컨트롤은 여러 가지가 있지만, 화면에 항상 달력을 펼쳐 두고 사용자가 즉시 날짜를 클릭하도록 유도해야 한다면 sap.ui.unified.Calendar가 가장 적합합니다. sap.m.DatePicker가 입력 필드를 누르면 드롭다운으로 떠오르는 팝업 방식이라면, Calendar는 그리드 형태의 인라인 달력을 컨테이너에 그대로 박아 넣는 방식입니다. 예약 시스템, 휴가 신청 화면, 대시보드의 일정 보기 등에서 자주 등장합니다.
본 글에서는 다음 내용을 단계별로 다룹니다.
- DatePicker와 Calendar의 용도 차이
- XML 뷰에서 Calendar 선언과
select이벤트 처리 specialDates어그리게이션으로 특정 일자 강조/비활성화intervalSelection,multipleSelection으로 범위 선택- JSONModel 바인딩으로 서버 데이터 기반 달력 렌더링
- 날짜 포맷, 타임존,
minDate/maxDate등 함정 회피 PlanningCalendar/SinglePlanningCalendar와의 선택 기준
2. 핵심 개념 — DatePicker vs Calendar 용도 구분
SAPUI5의 날짜 컨트롤은 크게 세 가지 계열로 나뉩니다. 어떤 컨트롤을 선택하는지에 따라 사용자 경험이 완전히 달라지므로, 요구사항을 먼저 정리한 뒤 컨트롤을 고르는 습관이 권장됩니다.
sap.m.DatePicker: 폼 필드 안에 작게 들어가며, 클릭 시 작은 달력이 팝업으로 떠오릅니다. 입력 양이 많은 폼, 좁은 공간에 적합합니다.sap.ui.unified.Calendar: 항상 펼쳐진 인라인 달력. 사용자가 즉시 클릭 가능한 큰 그리드를 보여주고, 강조 색상이나 범위 표시 같은 시각적 표현이 풍부합니다.sap.m.PlanningCalendar/SinglePlanningCalendar: 시간축이 가로 또는 세로로 펼쳐지는 일정 보기. "이 사람 9월 1일~10일 사이 회의" 같은 스케줄 시각화에 사용됩니다.
비유하자면 DatePicker는 책상 서랍에 접어둔 탁상 달력이고, Calendar는 벽에 걸어둔 월간 달력입니다. PlanningCalendar는 회의실 벽면의 간트 차트에 가깝다고 볼 수 있습니다. 일반적으로 "빈 공간에 달력이 항상 보였으면 좋겠다"는 요구사항이 등장하는 순간 sap.ui.unified.Calendar를 첫 번째 후보로 두는 편이 좋습니다.
3. 1단계: 기본 Calendar 선언과 select 이벤트
먼저 XML 뷰에서 sap.ui.unified 네임스페이스를 임포트한 뒤 Calendar를 선언합니다. 가장 단순한 형태는 다음과 같습니다.
<mvc:View
controllerName="my.app.controller.Main"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
xmlns:u="sap.ui.unified">
<Page title="Calendar Demo">
<u:Calendar
id="cal1"
select=".onDateSelect"
startDateChange=".onStartDateChange" />
<Text id="txtSelected" text="아직 선택된 날짜가 없습니다." />
</Page>
</mvc:View>
컨트롤러에서는 select 이벤트 핸들러에서 선택된 날짜를 꺼냅니다. 핵심은 getSelectedDates()가 DateRange 객체 배열을 반환한다는 점입니다. 단일 선택 모드에서도 배열로 돌아오므로 [0] 인덱스 접근이 일반적입니다.
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/format/DateFormat"
], function (Controller, DateFormat) {
"use strict";
return Controller.extend("my.app.controller.Main", {
onDateSelect: function (oEvent) {
var oCal = oEvent.getSource();
var aSelected = oCal.getSelectedDates();
if (!aSelected.length) {
return;
}
var oStart = aSelected[0].getStartDate();
var oFmt = DateFormat.getDateInstance({ pattern: "yyyy-MM-dd" });
this.byId("txtSelected").setText("선택: " + oFmt.format(oStart));
},
onStartDateChange: function (oEvent) {
// 사용자가 월을 이동했을 때 호출됨
console.log("표시 기준일이 변경되었습니다.");
}
});
});
getStartDate()는 자바스크립트의 Date 객체를 반환합니다. 표시할 때는 sap/ui/core/format/DateFormat을 사용하는 것이 권장됩니다. 직접 toString()을 호출하면 로캘과 타임존 영향으로 사용자마다 다른 문자열이 출력될 수 있습니다.
4. 2단계: specialDates로 날짜 비활성화/강조
실무에서 자주 등장하는 요구사항이 "주말은 빨갛게", "공휴일은 클릭 불가", "오늘 일정 있는 날에 점 표시" 같은 시각적 마킹입니다. 이때 사용하는 어그리게이션이 specialDates이고, 그 안에 sap.ui.unified.DateTypeRange를 채워 넣습니다.
<u:Calendar id="cal2" select=".onDateSelect">
<u:specialDates>
<u:DateTypeRange
startDate="{path: '/holiday', formatter: '.toDate'}"
type="Type01"
tooltip="신정" />
<u:DateTypeRange
startDate="{/blockStart}"
endDate="{/blockEnd}"
type="NonWorking"
tooltip="예약 마감" />
</u:specialDates>
</u:Calendar>
type은 Type01부터 Type20까지의 색상 슬롯과 NonWorking, Working 등 의미 기반 값을 지원합니다. 클릭 자체를 막고 싶다면 disabledDates 어그리게이션을 별도로 사용합니다.
onInit: function () {
var oCal = this.byId("cal2");
// 과거 30일~오늘까지 비활성화
var oToday = new Date();
var oPast = new Date();
oPast.setDate(oToday.getDate() - 30);
oCal.addDisabledDate(new sap.ui.unified.DateRange({
startDate: oPast,
endDate: oToday
}));
}
specialDates는 단순히 보이는 강조이고, disabledDates가 실제 입력을 차단한다는 점이 자주 혼동되는 부분입니다. 두 어그리게이션은 함께 사용 가능하며, 보통 공휴일은 강조 + 비활성화를 동시에 거는 패턴이 일반적입니다.
5. 3단계: 날짜 범위 선택 (intervalSelection)
휴가 신청처럼 "시작일~종료일"을 한 번에 잡아야 할 때는 intervalSelection 속성을 true로 두고, 첫 번째 클릭과 두 번째 클릭 사이의 구간을 인식합니다.
<u:Calendar
id="calRange"
intervalSelection="true"
singleSelection="true"
select=".onRangeSelect" />
onRangeSelect: function (oEvent) {
var oCal = oEvent.getSource();
var aSelected = oCal.getSelectedDates();
if (!aSelected.length) { return; }
var oRange = aSelected[0];
var oStart = oRange.getStartDate();
var oEnd = oRange.getEndDate();
if (!oEnd) {
// 첫 번째 클릭 직후 - 종료일이 아직 없음
return;
}
var oFmt = sap.ui.core.format.DateFormat.getDateInstance({
pattern: "yyyy-MM-dd"
});
MessageToast.show(
"기간: " + oFmt.format(oStart) + " ~ " + oFmt.format(oEnd)
);
}
여러 개의 비연속 날짜를 선택해야 한다면 singleSelection="false"로 두어 다중 선택 모드를 활성화합니다. 이 경우 getSelectedDates() 배열의 길이가 곧 선택된 일자의 수가 됩니다. 다만 다중 선택과 구간 선택을 동시에 켜는 조합은 UX가 혼란스러워 잘 쓰지 않습니다.
6. 4단계: JSONModel 바인딩으로 동적 달력 표시
실제 화면에서는 백엔드에서 받아온 공휴일/예약 데이터를 바탕으로 specialDates를 그려야 합니다. specialDates 어그리게이션은 모델에 바인딩 가능합니다.
onInit: function () {
var oModel = new sap.ui.model.json.JSONModel({
events: [
{ date: "2026-06-06", label: "현충일", type: "Type01" },
{ date: "2026-06-15", label: "팀 워크숍", type: "Type07" },
{ date: "2026-06-20", label: "릴리스", type: "Type10" }
]
});
this.getView().setModel(oModel);
}
<u:Calendar id="calBind">
<u:specialDates>
<u:DateTypeRange
startDate="{
path: 'date',
formatter: '.formatStringToDate'
}"
type="{type}"
tooltip="{label}" />
</u:specialDates>
</u:Calendar>
XML 표기상 <u:specialDates> 안에 단일 템플릿을 두면 자동으로 모델 경로 /events에 바인딩됩니다. 컨트롤러에는 문자열을 Date로 변환하는 포매터를 둡니다.
formatStringToDate: function (sIso) {
if (!sIso) { return null; }
var oFmt = sap.ui.core.format.DateFormat.getDateInstance({
pattern: "yyyy-MM-dd"
});
return oFmt.parse(sIso);
}
이렇게 두면 백엔드에서 일정 데이터가 갱신될 때마다 oModel.setProperty("/events", ...) 한 줄로 달력이 다시 그려집니다. 다만 specialDates에 수백 건 이상의 항목이 들어가면 렌더 비용이 늘어나므로, 현재 표시 중인 월 기준으로 서버 측에서 잘라 받는 패턴이 권장됩니다. startDateChange 이벤트에서 새로운 기준 월을 받아 API를 다시 호출하면 됩니다.
7. 자주 겪는 함정과 FAQ
Q1. getStartDate()로 받은 날짜가 한국시간 기준으로 하루 어긋납니다.
A. Calendar는 "로컬 0시" 기준의 Date 객체를 돌려줍니다. 이 값을 toISOString()으로 직렬화하면 UTC로 환산되면서 전날로 보일 수 있습니다. 백엔드에 보낼 때는 DateFormat으로 yyyy-MM-dd 문자열을 만든 뒤 전송하거나, OData V4의 Edm.Date 타입을 명시적으로 사용하는 것이 권장됩니다.
Q2. minDate/maxDate를 설정했는데 그 범위 밖이 그냥 회색만 되고 클릭이 됩니다.
A. minDate/maxDate는 표시 가능한 달의 한계를 지정하는 속성으로, 자동으로 모든 클릭을 막아주지는 않는 경우가 있습니다. 입력 자체를 차단하려면 disabledDates에 범위를 직접 넣거나, select 핸들러에서 유효성 검사를 한 번 더 거는 방식이 일반적입니다.
Q3. XML 네임스페이스에서 u:Calendar를 못 찾는다는 에러가 납니다.
A. manifest.json의 sap.ui5.dependencies.libs에 sap.ui.unified를 명시적으로 추가해야 합니다. sap.m과 달리 기본 의존성에 포함되지 않는 경우가 많아 빠뜨리기 쉽습니다.
그 외 흔한 실수
specialDates에 그냥 문자열을 넣음 → 반드시Date객체여야 함intervalSelection만 켜고singleSelection은 꺼둠 → 의도와 달리 다중 + 구간이 섞임- 월 이동 시마다 API를 호출하면서
removeAllSpecialDates()를 빠뜨려 중복 표시 - 로캘에 따라 주 시작 요일이 달라지는 점을 잊고 토/일 위치를 하드코딩
8. 응용 패턴 — PlanningCalendar와의 선택 기준
날짜 하나 또는 범위만 받는 게 목적이라면 sap.ui.unified.Calendar로 충분하지만, "리소스별 일정", "시간대별 회의 슬롯" 같은 2차원 표현이 필요해지는 순간 sap.m.PlanningCalendar 또는 sap.m.SinglePlanningCalendar로 갈아타는 편이 좋습니다. 선택 기준을 정리하면 다음과 같습니다.
- Calendar: 단일/범위 날짜 입력, 공휴일 강조, 휴가 신청, 예약 가능일 표시
- SinglePlanningCalendar: 한 사람의 시간 단위 일정(일/주/월 뷰), Outlook 스타일 개인 캘린더
- PlanningCalendar: 여러 리소스(직원/회의실)를 행으로 두고, 일정 블록을 가로로 늘어놓는 간트 뷰
또한 사용자가 입력만 하면 되는 좁은 폼이라면 굳이 Calendar를 펼치지 말고 DatePicker나 DateRangeSelection을 쓰는 편이 화면 공간을 절약합니다. 일반적으로 "항상 보여야 한다"는 요구사항이 있을 때만 인라인 Calendar를 선택하고, 그렇지 않으면 팝업 컨트롤이 더 가볍습니다.
마지막으로 접근성을 위해 ariaLabelledBy를 연결하고, 모바일에서는 months 속성으로 한 번에 표시할 달 수를 1로 유지하는 것이 권장됩니다. 데스크톱 대시보드처럼 가로 공간이 충분한 경우에 한해 months="2" 또는 "3"으로 늘려 두 달치를 동시에 보여주는 패턴이 자주 쓰입니다. 이런 옵션 조합만 익혀두어도 대부분의 일정 선택 화면을 sap.ui.unified.Calendar 하나로 깔끔하게 마무리할 수 있습니다.
댓글 0
아직 댓글이 없습니다.