이 글에서 다루는 내용과 확인 포인트
SAPUI5 애플리케이션에서 사용자에게 정보를 전달하는 두 가지 대표 위젯인 sap.m.MessageBox와 sap.m.MessageToast는 얼핏 비슷해 보이지만 UX 측면에서 완전히 다른 역할을 담당합니다. 이 글은 두 컴포넌트의 동작 원리와 상황별 선택 기준 3가지를 실무 관점에서 정리합니다.
- MessageBox와 MessageToast의 렌더링 방식과 인터랙션 차이 이해
- "확인이 필요한 알림" vs "일시적 피드백"의 판단 기준 3가지 습득
- 주문 저장, 삭제 확인, 자동 저장 등 실무 시나리오별 적용 방법
- i18n 다국어 처리, 콜백 체이닝, 접근성 고려 포인트 정리
알아두면 좋은 배경 지식
이 글의 코드는 SAPUI5 View-Controller(MVC) 패턴을 이해하고 있다는 전제에서 진행합니다. Controller의 onPress, onInit 같은 이벤트 핸들러 작성 경험, sap.ui.define을 통한 모듈 로딩 개념, 그리고 i18n.properties 리소스 번들의 기본 사용법을 알고 있으면 흐름을 따라오는 데 무리가 없습니다.
실습 환경과 준비물
이 글의 코드는 아래 환경에서 검증된 형태로 작성되었습니다. 버전에 따라 API 시그니처가 달라질 수 있으므로 프로젝트의 manifest.json에서 minUI5Version을 먼저 확인하시길 권장합니다.
- SAPUI5 런타임: 1.120.x LTS (SAP Build Work Zone 및 BTP 배포 기준)
- 개발 도구: SAP Business Application Studio 또는 VS Code + Easy-UI5 Generator
- 필수 모듈:
sap/m/MessageBox,sap/m/MessageToast - 테스트 환경: Chrome DevTools, OPA5 통합 테스트 프레임워크
- 브라우저: Chromium 계열 최신 버전 (Safari의 경우 애니메이션 렌더링이 미묘하게 다를 수 있음)
SAP Fiori launchpad 안에 배포하는 경우 sap.ushell 컨테이너가 z-index를 관리하므로, MessageBox가 Shell Bar 아래로 가려지는 이슈가 있는지 사전 점검이 필요합니다.
동작 원리와 선택 기준의 세 가지 축
MessageBox와 MessageToast는 내부적으로 완전히 다른 DOM 구조를 사용합니다. MessageBox는 sap.m.Dialog를 확장한 모달 컴포넌트로, 표시되는 순간 뒤쪽에 반투명 오버레이(sapMDialogBg)를 깔고 사용자 입력을 차단합니다. 반면 MessageToast는 sap.m.Popup 기반의 비모달 요소로, 화면 하단에 약 3초간 나타났다가 자동으로 사라집니다.
이 구조적 차이가 실무 선택 기준의 핵심입니다. 아래 세 가지 축으로 판단하면 대부분의 경우 명확한 답이 나옵니다.
- 사용자 응답이 필요한가? — 삭제 확인, 저장 여부, 이탈 경고처럼 "예/아니오" 답변이 반드시 필요하면 MessageBox. 단순히 "저장되었습니다"를 알리는 정도면 MessageToast.
- 후속 작업의 분기가 결과에 의존하는가? — 사용자가 "취소"를 눌렀을 때 다음 로직이 달라져야 한다면 MessageBox의 콜백을 활용해야 합니다. Toast는 콜백을 지원하지 않습니다.
- 알림의 심각도(severity)가 어느 정도인가? — Error, Warning, Confirmation 같이 사용자가 반드시 인지해야 하는 상황은 MessageBox. Success, Info처럼 흐름을 끊지 않고 알려주는 정도면 MessageToast.
비유하자면 MessageBox는 회의실에서 "결재 서명"을 요청하는 상황이고, MessageToast는 복도에서 지나가며 "수고하셨습니다"라고 말하는 상황입니다. 서명 요청을 지나가면서 하면 안 되고, 인사말에 결재 서명을 요구해서도 안 됩니다.
또한 접근성(Accessibility) 측면에서도 차이가 있습니다. MessageBox는 role="alertdialog"가 부여되어 스크린 리더가 즉시 낭독하고 포커스를 강제로 이동시키지만, MessageToast는 aria-live="polite"로 처리되어 현재 낭독 중인 문장이 끝난 뒤에 전달됩니다.
1단계 — 기본형: 주문 저장 완료 알림
가장 단순한 사례부터 시작합니다. 판매 주문(SalesOrder)이 성공적으로 저장되었을 때 사용자에게 알리는 코드입니다. 응답이 필요 없고, 흐름을 끊지 않아야 하므로 MessageToast가 적합합니다.
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast",
"sap/m/MessageBox"
], function (Controller, MessageToast, MessageBox) {
"use strict";
return Controller.extend("com.acme.sales.controller.OrderDetail", {
onSaveOrder: function () {
var oOrderModel = this.getView().getModel("order");
var sOrderId = oOrderModel.getProperty("/orderId");
MessageToast.show("주문 " + sOrderId + " 이(가) 저장되었습니다.", {
duration: 3000,
width: "20em",
my: "center bottom",
at: "center bottom",
offset: "0 -50"
});
}
});
});
duration은 밀리초 단위이며 최소 1000ms, 최대 10000ms까지 설정 가능합니다. my와 at은 sap.ui.core.Popup.Dock 상수를 문자열로 표현한 것으로, 화면 어느 위치에 어떤 기준점으로 붙일지 지정합니다.
2단계 — 실무 시나리오: 삭제 확인과 콜백 분기
이번에는 사용자가 배송 주문(DeliveryOrder)을 삭제하려 할 때 되돌릴 수 없는 작업임을 인지시키고, 명시적 확인을 받아야 하는 상황입니다. 여기서는 반드시 MessageBox를 사용해야 합니다.
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageBox",
"sap/m/MessageToast",
"sap/base/Log"
], function (Controller, MessageBox, MessageToast, Log) {
"use strict";
return Controller.extend("com.acme.delivery.controller.DeliveryList", {
onDeleteDelivery: function (oEvent) {
var oItem = oEvent.getSource().getBindingContext("delivery").getObject();
var oResourceBundle = this.getView().getModel("i18n").getResourceBundle();
var sMsg = oResourceBundle.getText("msg.confirmDelete", [oItem.deliveryId]);
MessageBox.confirm(sMsg, {
icon: MessageBox.Icon.WARNING,
title: oResourceBundle.getText("title.deleteDelivery"),
actions: [MessageBox.Action.DELETE, MessageBox.Action.CANCEL],
emphasizedAction: MessageBox.Action.DELETE,
onClose: function (sAction) {
if (sAction === MessageBox.Action.DELETE) {
this._performDelete(oItem.deliveryId);
} else {
Log.info("삭제 취소됨", oItem.deliveryId, "DeliveryList");
}
}.bind(this)
});
},
_performDelete: function (sDeliveryId) {
var oModel = this.getView().getModel();
oModel.remove("/DeliverySet('" + sDeliveryId + "')", {
success: function () {
MessageToast.show("배송 " + sDeliveryId + " 삭제 완료");
},
error: function (oError) {
Log.error("삭제 실패", oError.message, "DeliveryList");
MessageBox.error("삭제 처리 중 오류가 발생했습니다.
" + oError.message);
}
});
}
});
});
주목할 부분은 세 가지입니다. 첫째, emphasizedAction으로 기본 버튼을 강조 표시해 위험한 작업을 명시적으로 인지시킵니다. 둘째, onClose 콜백에서 사용자의 선택에 따라 분기 처리합니다. 셋째, 삭제 자체는 파괴적 액션이므로 MessageBox로 확인받고, 삭제 완료 알림은 사용자의 흐름을 방해하지 않도록 MessageToast로 처리했습니다. 이 조합이 실무에서 가장 흔한 UX 패턴입니다.
3단계 — 프로덕션: 재사용 가능한 알림 유틸리티
규모 있는 프로젝트에서는 컨트롤러마다 MessageBox 호출을 반복하면 유지보수가 어려워집니다. 알림 로직을 유틸리티로 캡슐화하고, 자동 로깅과 i18n 통합, 그리고 QUnit 테스트까지 고려한 프로덕션 수준 코드를 살펴봅니다.
sap.ui.define([
"sap/ui/base/Object",
"sap/m/MessageBox",
"sap/m/MessageToast",
"sap/base/Log"
], function (BaseObject, MessageBox, MessageToast, Log) {
"use strict";
return BaseObject.extend("com.acme.common.NotificationService", {
constructor: function (oResourceBundle) {
BaseObject.call(this);
this._oBundle = oResourceBundle;
},
toast: function (sKey, aArgs) {
var sText = this._oBundle.getText(sKey, aArgs);
MessageToast.show(sText, { duration: 2500 });
Log.debug("Toast: " + sText, null, "NotificationService");
},
confirm: function (sMsgKey, sTitleKey, aArgs) {
var sMsg = this._oBundle.getText(sMsgKey, aArgs);
var sTitle = this._oBundle.getText(sTitleKey);
return new Promise(function (resolve) {
MessageBox.confirm(sMsg, {
title: sTitle,
actions: [MessageBox.Action.OK, MessageBox.Action.CANCEL],
emphasizedAction: MessageBox.Action.OK,
onClose: function (sAction) {
resolve(sAction === MessageBox.Action.OK);
}
});
});
},
error: function (oError, sContext) {
var sMsg = oError.message || this._oBundle.getText("msg.genericError");
Log.error(sMsg, oError.stack, sContext || "NotificationService");
MessageBox.error(sMsg, {
details: oError.responseText || oError.stack,
actions: [MessageBox.Action.CLOSE]
});
}
});
});
onSubmitPurchaseOrder: async function () {
var oNotif = this.getOwnerComponent().oNotification;
var bConfirmed = await oNotif.confirm("msg.confirmSubmit", "title.submit", [this._sPOId]);
if (!bConfirmed) return;
try {
await this._submitToBackend();
oNotif.toast("msg.submitSuccess", [this._sPOId]);
} catch (oError) {
oNotif.error(oError, "PurchaseOrderController");
}
}
OPA5 통합 테스트에서는 MessageBox의 버튼을 waitFor로 잡아 press하는 방식으로 흐름을 검증할 수 있으며, MessageToast는 DOM에 잠깐만 존재하므로 show를 sinon으로 spy 처리해 호출 여부만 검증하는 것이 일반적으로 안정적입니다.
자주 마주치는 실수와 해결 방법
- Q1. MessageToast가 배포 환경에서만 보이지 않아요.
대개 CSP(Content Security Policy) 또는 z-index 이슈입니다. Fiori launchpad 안에서 커스텀 CSS가.sapMMessageToast의z-index를 덮어쓰고 있는지 확인하고, Shell 컨테이너보다 높은 값(예: 9999)이 필요할 수 있습니다. - Q2. MessageBox.confirm의 콜백에서 this가 undefined입니다.
화살표 함수를 사용하거나.bind(this)를 명시적으로 붙여야 합니다. SAPUI5는 기본적으로 콜백 컨텍스트를 자동 바인딩하지 않습니다. - Q3. 에러 상황에 Toast를 쓰면 안 되나요?
기술적으로는 가능하지만 권장되지 않습니다. Toast는 3초 후 사라지므로 사용자가 놓칠 수 있고, 스크린 리더에서 aria-live 속성으로 처리되어 즉시 안내되지 않습니다. 에러는 최소한 MessageBox.error 혹은 MessageStrip으로 남겨두는 것이 접근성 관점에서 안전합니다. - Q4. 연달아 여러 Toast를 띄우면 겹치나요?
기본 동작은 큐잉이 아닌 즉시 대체입니다. 여러 개를 순차적으로 보여주려면 onClose 옵션에서 다음 Toast를 호출하거나, MessageManager와 결합해 MessageView로 통합 표시하는 방식이 일반적으로 권장됩니다.
확장해서 살펴볼 만한 주제
이 글에서 다룬 두 컴포넌트는 SAPUI5 알림 체계의 기본입니다. 여기서 한 걸음 더 나아가려면 sap.ui.core.message.MessageManager를 통한 백엔드 메시지 자동 파싱, sap.m.MessageStrip과 sap.m.MessagePopover를 이용한 폼 검증 UI, 그리고 Fiori Elements의 표준 메시지 처리 흐름을 함께 살펴보길 권장합니다. 특히 OData V4 모델을 사용하는 경우 백엔드 메시지가 자동으로 MessageManager에 등록되므로, 이 체계와 MessageBox/Toast를 어떻게 조합할지 설계하는 것이 UX 완성도의 핵심 포인트가 됩니다.
댓글 0
아직 댓글이 없습니다.