Calendar
이 컨트롤은 무엇인가요?
Calendar는 일정(이벤트)을 달력 형태로 보여주고 편집할 수 있게 해주는 컨트롤입니다.
내부적으로는 FullCalendar 라이브러리를 감싸서 syn.uicontrols.$calendar 싱글턴 객체로 제공합니다. 실제 화면 렌더링(월/주/일 뷰, 드래그&드롭, 리사이즈 등)은 모두 FullCalendar가 담당하고, $calendar는 그 위에서 데이터 매핑(eventMapping)과 변경 이력 추적(Flag)을 얹어주는 얇은 래퍼입니다.
언제 사용하나요?
- 일정 관리 화면(스케줄러, 예약 현황판, 근태/휴가 캘린더 등)처럼 날짜 축을 기준으로 여러 건의 이벤트를 보여줘야 할 때
- 사용자가 이벤트를 클릭/드래그해서 조회·이동·크기 조정을 하고, 그 변경분만 서버에 저장해야 할 때(
Flag기반 변경 추적 +getterValue) - 월간/주간/리스트 뷰를 토글하면서 같은 데이터를 다양한 형태로 봐야 할 때
빠른 시작
중요: Calendar는 다른 대부분의 컨트롤(TreeView, WebGrid 등)과 달리 syn.loader.js가 필요한 라이브러리를 자동으로 찾아 넣어주지 않습니다. syn.loader.js는 <syn_xxx> 태그 이름을 보고 미리 정해진 CSS/JS 목록을 로드하는데, 아직 이 매핑 표에 calendar(→ syn_calendar)에 대한 항목이 등록되어 있지 않기 때문입니다. 그래서 FullCalendar 라이브러리 자체와 Calendar.js/Calendar.css까지 페이지에서 직접 pageLoadFiles 훅으로 등록해 줘야 합니다.
<syn_calendar id="calSample" syn-options="{
height: 500,
initialView: 'dayGridMonth'
}" syn-events="['eventClick']"></syn_calendar>
<script>
function pageLoadFiles(styleFiles, jsFiles, templateFiles) {
window.afterLoadFiles = [];
// 1) FullCalendar 라이브러리(달력 엔진) 자체
afterLoadFiles.push('/lib/fullcalendar/index.global.min.js');
// 2) 한국어 로케일(요일/버튼 텍스트를 한글로)
afterLoadFiles.push('/lib/fullcalendar/core/locales/ko.global.min.js');
// 3) Calendar 컨트롤(syn.uicontrols.$calendar 정의)
afterLoadFiles.push('/uicontrols/Calendar/Calendar.css');
afterLoadFiles.push('/uicontrols/Calendar/Calendar.js');
}
</script>
<script src="/js/syn.loader.js"></script>
'use strict';
let $samplePage = {
hook: {
pageLoad() {
syn.uicontrols.$calendar.setValue('calSample', [
{ ScheduleEventID: 1, ScheduleTitle: '킥오프 미팅', StartDate: '2026-07-06', EndDate: '2026-07-06' }
]);
}
},
event: {
calSample_eventClick(info) {
syn.$l.eventLog('calSample_eventClick', info.event.title);
}
}
}
핵심 포인트:
- 마크업은
<syn_calendar>커스텀 태그로 작성합니다. - FullCalendar 라이브러리, 로케일 파일,
Calendar.js/Calendar.css4가지 전부를pageLoadFiles(정확히는afterLoadFiles배열)로 직접 등록해야 합니다. 하나라도 빠뜨리면FullCalendar is not defined같은 에러가 나거나, 달력은 뜨지만 한글 대신 영어(Today, Month 등)로 표시됩니다. syn-options의eventMapping을 통해 화면에 표시할 이벤트 데이터 컬럼명(예:ScheduleEventID,ScheduleTitle,StartDate,EndDate)을 실제 데이터 컬럼명에 맞게 바꿔 쓸 수 있습니다.- 이벤트를 추가/수정/삭제하면 컨트롤이 내부적으로
Flag(C=생성,U=수정,D=삭제,R=변경없음)를 관리합니다.getterValue를 호출하면 변경된 이벤트만 뽑아낼 수 있어서, 전체 데이터를 다시 저장하지 않고 변경분만 서버로 보내는 패턴을 구현할 수 있습니다.
예제 실행하기
example/ 폴더의 각 HTML 파일을 handstack의 wwwroot 정적 서버(예: rdy 프로젝트) 경로 아래에 두고 브라우저로 열면 바로 동작을 확인할 수 있습니다.
basic.html: 월간 뷰에 샘플 일정을 채워 넣고,getEvents/getterValue/gotoDate/changeView/clear를 버튼으로 확인하는 기본 예제events.html:eventClick/datesSet이벤트를 로그로 확인하고,addEvent/updateEvent/removeEvent후getterValue로 변경된 이벤트만 걸러내는 예제
각 예제는 화면 하단 로그 영역(syn.$l.eventLog 출력)에서 이벤트 발생 순서와 전달값을 확인할 수 있습니다.
더 알아보기
- API 상세는 같은 폴더의
API.md를 참고하세요. - 실제 소스:
wwwroot/uicontrols/Calendar/Calendar.js,Calendar.css - FullCalendar 에셋 위치:
wwwroot/lib/fullcalendar/(index.global.min.js,core/locales/ko.global.min.js등) - 실사용 예: qcn.groupware 매장 스케줄 화면의
syn_calendar사용 패턴(월간 일정을 그리고,datesSet으로 조회 기간이 바뀔 때마다 서버에서 다시 데이터를 받아오는 흐름) —eventMapping으로ShopDailyTaskID/EventName/StartDate/EndDate등 실제 컬럼명을 매핑하고,eventDidMount로 이벤트 DOM을 커스터마이징합니다. - FullCalendar 원본 문서: https://fullcalendar.io/docs
실전 예제 페이지
/uicontrols/Calendar/example/ 경로의 예제를 아래 iframe에서 바로 확인할 수 있습니다.
basic.html
events.html
소스와 로드 파일
| 항목 | 내용 |
|---|---|
| 컨트롤 | Calendar |
| 전역 별칭 | syn.uicontrols.$calendar |
| 버전 | v2025.12.26 |
| 소스 | uicontrols/Calendar/Calendar.js |
| 스타일 | uicontrols/Calendar/Calendar.css |
API 참조
싱글턴 객체: syn.uicontrols.$calendar
소스 파일: wwwroot/uicontrols/Calendar/Calendar.js, wwwroot/uicontrols/Calendar/Calendar.css
내부 라이브러리: FullCalendar (new FullCalendar.Calendar(el, options))
마크업
<syn_calendar id="calSample" syn-options="{
height: 'calc(100vh - 100px)',
initialView: 'dayGridMonth',
headerToolbar: { left: 'prev,next today', center: 'title', right: 'dayGridMonth,timeGridWeek,listWeek' },
eventMapping: {
id: 'ScheduleEventID', title: 'ScheduleTitle', start: 'StartDate', end: 'EndDate',
backgroundColor: 'BackgroundColor', borderColor: 'BackgroundColor', textColor: 'TextColor'
},
selectable: true,
editable: true
}" syn-events="['eventClick', 'datesSet', 'eventDrop', 'eventResize']"></syn_calendar>
중요: syn.loader.js에는 syn_calendar 태그에 대응하는 CSS/JS 자동 로드 항목(case 'calendar')이 없습니다. FullCalendar 라이브러리, 로케일 파일, Calendar.js/Calendar.css를 모두 페이지의 pageLoadFiles 훅(afterLoadFiles 배열)으로 직접 등록해야만 컨트롤이 동작합니다. 자세한 코드는 README.md의 "빠른 시작"과 example/ 폴더를 참고하세요.
id는 페이지 내에서 유일해야 하며,syn.uicontrols.$calendar의 각종 메서드에서 이id(elID)를 사용합니다.controlLoad실행 시new FullCalendar.Calendar(el, calendarSettings)가 만들어지고calendar.render()가 호출됩니다. 이때syn-options에 지정한 값 전체가elID/syn-options속성으로 다시 기록되며(el.setAttribute('syn-options', JSON.stringify(setting))),eventMapping/getter/setter/transactConfig/triggerConfig/elID는 FullCalendar 자체 옵션에서는 제외되고 컨트롤 내부용으로만 쓰입니다.
Options (defaultSetting)
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
elID | string | '' | 내부적으로 controlLoad 시점에 채워지는 요소 id. 직접 지정할 필요 없음 |
height | string | number | 'auto' | 달력 전체 높이(FullCalendar height 옵션 그대로 전달) |
expandRows | boolean | true | 남는 세로 공간을 행에 맞춰 늘릴지 여부 |
locale | string | 'ko' | 표시 언어. 실제로 한글이 나오게 하려면 /lib/fullcalendar/core/locales/ko.global.min.js를 별도로 로드해야 합니다(README 참고) |
initialView | string | 'dayGridMonth' | 초기 화면(뷰) 종류. 그 외 timeGridWeek, timeGridDay, listWeek 등 FullCalendar 표준 뷰 이름 사용 가능 |
headerToolbar | object | 객체 설정 | 상단 툴바 좌/중/우 버튼 구성(FullCalendar headerToolbar 옵션 그대로) |
dayMaxEvents | boolean | true | 하루에 표시할 이벤트 수가 넘칠 때 "+N more" 링크로 접을지 여부 |
displayEventTime | boolean | false | 이벤트 제목 앞에 시간을 표시할지 여부 |
selectable | boolean | false | 날짜/시간대를 드래그로 선택할 수 있게 할지 여부(select 이벤트와 함께 사용) |
editable | boolean | false | 이벤트를 드래그로 이동(eventDrop)하거나 크기 조정(eventResize)할 수 있게 할지 여부 |
eventMapping | object | 아래 "eventMapping" 참고 | setValue/addEvent/updateEvent/getterValue가 FullCalendar 이벤트 필드(id/title/start/end/...)와 실제 데이터 컬럼명을 서로 바꿔치기할 때 쓰는 매핑 테이블. FullCalendar 자체에는 전달되지 않는 컨트롤 전용 옵션입니다 |
getter / setter / transactConfig / triggerConfig | - | false/null | syn.uicontrols 공통 옵션(값 바인딩·트랜잭션 연동용). FullCalendar 옵션에서는 제외됩니다 |
이 외에도 syn-options에 FullCalendar가 지원하는 임의의 옵션(예: slotMinTime, businessHours, eventContent, views 등)을 그대로 추가하면 calendarSettings에 포함되어 new FullCalendar.Calendar(el, calendarSettings)에 전달됩니다. 즉 eventMapping/getter/setter/transactConfig/triggerConfig/elID 6가지만 컨트롤이 가로채고, 나머지는 전부 FullCalendar 옵션으로 그대로 흘러갑니다.
eventMapping
| 속성 | 기본값 | 설명 |
|---|---|---|
id | 'ScheduleEventID' | 이벤트 고유 키 컬럼명 |
title | 'ScheduleTitle' | 이벤트 제목 컬럼명 |
start | 'StartDate' | 시작일시 컬럼명 |
end | 'EndDate' | 종료일시 컬럼명 |
backgroundColor | 'BackgroundColor' | 배경색 컬럼명 |
borderColor | 'BackgroundColor' | 테두리색 컬럼명(기본값이 배경색과 같은 컬럼을 가리킴에 유의) |
textColor | 'TextColor' | 글자색 컬럼명 |
allDay | 'AllDay' | 종일 이벤트 여부 컬럼명 |
color | 'Color' | 배경/테두리를 한 번에 지정하는 색상 컬럼명 |
classNames | 'ClassNames' | 이벤트 DOM에 추가할 CSS 클래스 컬럼명 |
매핑에 없는 나머지 컬럼(예: ShopDailyTaskID, Memo 등 업무 데이터 고유 컬럼)은 자동으로 FullCalendar 이벤트의 extendedProps에 들어갑니다. 즉 event.extendedProps.Memo처럼 접근합니다.
메서드
syn.uicontrols.$calendar.<메서드명>(...) 형태로 호출합니다.
| 메서드 | 설명 |
|---|---|
getControl(elID) | 등록된 컨트롤 정보(객체 설정)를 반환합니다. calendar가 실제 FullCalendar 인스턴스입니다. |
getCalendar(elID) | getControl(elID).calendar만 바로 반환하는 축약 메서드. FullCalendar 인스턴스의 원본 API(updateSize() 등)를 직접 호출할 때 사용합니다. |
getValue(elID, meta) | 항상 null을 반환 합니다. Calendar는 다른 컨트롤과 달리 getValue가 구현되어 있지 않습니다. 이벤트 전체 목록이 필요하면 getEvents를, 변경분만 필요하면 getterValue를 사용하세요. |
setValue(elID, value, meta) | 배열(value)을 받아 eventMapping 기준으로 변환한 뒤, {elID}_eventSource라는 이름의 이벤트 소스로 통째로 교체합니다. 기존에 이 이벤트 소스로 들어간 이벤트는 모두 제거되고 새 데이터로 다시 그려집니다. 각 이벤트에는 Flag: 'R'(변경없음)이 자동으로 붙습니다. addEvent 등으로 별도 추가된 이벤트에는 영향을 주지 않습니다. |
getterValue(elID, meta) | Flag가 C(생성)/U(수정)/D(삭제)인 이벤트만 모아 평평한 객체 배열로 반환합니다(extendedProps 내용도 최상위로 펼쳐짐). 변경된 부분만 서버에 저장하고 싶을 때 사용합니다. |
addEvent(elID, eventData) | eventMapping 기준으로 변환한 새 이벤트를 추가합니다(Flag: 'C'). |
removeEvent(elID, eventID) | 이벤트를 제거합니다. 방금 addEvent로 추가되어 아직 저장 전인 이벤트(Flag === 'C')는 즉시 화면에서 삭제되고, 이미 서버에 저장된 이벤트(Flag가 R/U)는 화면에서 숨기기만 하고(display: 'none') Flag를 D로 바꿔서 남겨둡니다(getterValue로 삭제 대상을 서버에 알리기 위함). |
updateEvent(elID, eventData) | eventMapping의 id 컬럼으로 이벤트를 찾아 필드를 갱신합니다. 기존 Flag가 R(변경없음)이면 U로 바뀌고, 이미 C/U/D면 그대로 유지됩니다. 매핑되지 않은 컬럼은 extendedProps로 갱신됩니다. |
getEvents(elID) | 현재 달력에 있는 모든 이벤트를 event.toPlainObject() 형태의 배열로 반환합니다(변경 여부와 무관하게 전체). |
findEvents(elID, filter) | 객체 설정 형태의 filter 객체와 일치하는 이벤트만 찾아 반환합니다. title/id/start/end는 이벤트 최상위 필드에서, 그 외 키는 extendedProps에서 비교합니다. 문자열 값은 대소문자 무시 부분일치, 그 외 값은 != 비교(느슨한 비교)로 판단합니다. filter를 생략하면 전체 이벤트를 반환합니다. |
clear(elID, isControlLoad) | calendar.removeAllEvents()를 호출해 이벤트 소스와 무관하게 모든 이벤트를 지웁니다(setValue가 지우는 범위보다 넓음). |
gotoDate(elID, date) | 지정한 날짜가 보이도록 달력을 이동합니다(FullCalendar gotoDate 그대로 위임). |
changeView(elID, viewName, dateOrRange) | 뷰를 전환합니다(예: 'dayGridMonth' → 'timeGridWeek'). |
refetchEvents(elID) | 이벤트 소스가 함수/URL 기반일 때 다시 조회하도록 트리거합니다. |
controlLoad(elID, setting)은 컨트롤이 마크업을 실제 FullCalendar 인스턴스로 초기화하는 내부 진입점으로, 프레임워크가 자동으로 호출합니다. 직접 호출할 일은 거의 없 습니다.
addModuleList는 폼 제출 시 이 컨트롤을 모듈 목록에 등록하기 위한 내부용 메서드입니다.
이벤트 (syn-events)
TreeView 같은 컨트롤과 달리, Calendar의 syn-events에 적는 이름은 프레임워크가 별도로 감싼 이름이 아니라 FullCalendar가 원래 지원하는 콜백 옵션 이름 그대로입니다. controlLoad가 setting[hook] = eventHandler처럼 훅 이름을 그대로 FullCalendar 옵션 키에 대입하기 때문입니다. 따라서 아래 표에 없어도 FullCalendar 문서에 있는 콜백 이름이면 대부분 그대로 사용할 수 있습니다.
| 이벤트명 | 발생 시점 |
|---|---|
eventClick | 이벤트를 클릭했을 때 |
dateClick | 빈 날짜/시간 셀을 클릭했을 때 |
select | 날짜/시간대를 드래그로 선택했을 때(selectable: true 필요) |
datesSet | 화면에 보이는 날짜 범위가 바뀌었을 때(뷰 전환, prev/next, gotoDate 등). 조회 기간이 바뀔 때마다 서버에서 새 일정을 받아와 setValue로 다시 채우는 용도로 많이 사용 |
eventDrop | 이벤트를 드래그해서 다른 날짜/시간으로 이동했을 때(editable: true 필요) |
eventResize | 이벤트 크기(기간)를 드래그로 조정했을 때(editable: true 필요) |
eventDidMount | 이벤트의 DOM 요소가 렌더링된 직후(툴팁 부착, 스 타일 커스터마이징 등에 사용) |
eventWillUnmount | 이벤트의 DOM 요소가 제거되기 직전 |
eventMouseEnter / eventMouseLeave | 이벤트 위에 마우스가 들어오거나 나갈 때 |
dayCellContent | 날짜 셀의 기본 콘텐츠(날짜 숫자)를 커스터마이징할 때 |
viewDidMount | 뷰가 렌더링된 직후 |
핸들러 등록 예:
let $samplePage = {
event: {
calSample_eventClick(info) {
// info.event._def.extendedProps 에 매핑되지 않은 원본 컬럼이 들어 있습니다.
syn.$l.eventLog('calSample_eventClick', info.event.title);
},
calSample_datesSet(dateInfo) {
var calendar = syn.uicontrols.$calendar.getControl('calSample');
syn.$l.eventLog('calSample_datesSet', dateInfo.startStr + ' ~ ' + dateInfo.endStr);
},
calSample_eventDrop(info) {
syn.$l.eventLog('calSample_eventDrop', info.event.title + ' -> ' + info.event.startStr);
}
}
}
참고
- 변경 이력 추적(
Flag): 컨트롤은 각 이벤트의extendedProps.Flag에R(변경없음,setValue로 처음 채워질 때),C(생성,addEvent),U(수정,updateEvent),D(삭제,removeEvent) 중 하나를 기록합니다. 화면에서 여러 건을 추가/수정/삭제한 뒤getterValue(elID)를 호출하면C/U/D상태인 이벤트만 뽑아서 반환하므로, 전체 데이터를 다시 저장하지 않고 변경분만 서버 트랜잭션으로 보내는 패턴을 만들 수 있습니다. getValue를 쓰면 안 되는 이유:getValue는 구현되어 있지 않고 항상null을 반환합니다. 전체 이벤트가 필요하면getEvents(elID), 변경분만 필요하면getterValue(elID)를 사용하세요.clearvssetValue의 범위 차이:setValue는 자신이 만든 이벤트 소스({elID}_eventSource)만 교체하지만,clear는removeAllEvents()로 이벤트 소스와 상관없이 전체를 지웁니다.addEvent로 추가한 이벤트까지 포함해 전부 지우고 싶다면clear를 사용하세요.- 내부적으로 FullCalendar 인스턴스를 그대로 감싼 구조이므로, 옵션/콜백의 더 자세한 원본 설명은 FullCalendar 문서를 참고하세요: https://fullcalendar.io/docs