개요 및 이 글에서 다룰 것
SAP UI5 애플리케이션에서 화면에 데이터를 뿌릴 때, 백엔드에서 내려온 원본 값을 그대로 노출하면 사용자 경험이 어색해집니다. 예를 들어 20260714라는 문자열, 1500000.00이라는 숫자, P라는 코드 값은 각각 2026-07-14, 1,500,000원, 결제완료처럼 보기 좋게 바뀌어야 합니다. 이 글에서는 UI5의 Formatter 함수를 활용해 데이터 표시 로직을 View 바깥으로 분리하고, 실무에서 흔히 마주치는 판매주문(SalesOrder) 시나리오에 바로 적용하는 방법을 정리합니다.
- Formatter 함수의 동작 원리와 바인딩 파이프라인 이해
- 단일/복합 바인딩에서 Formatter를 적용하는 문법 습득
- 날짜·금액·상태 코드 변환 3종 실전 예제 구현
- 에러 처리, 로깅, 테스트 관점의 프로덕션 패턴 익히기
미리 알고 있으면 좋은 것
SAPUI5의 기본적인 MVC 구조(Model, View, Controller)와 XML View 문법을 알고 있어야 합니다. 특히 { } 표현식을 사용한 Property Binding, 그리고 JSON Model 또는 OData Model에서 데이터가 어떤 흐름으로 View에 도달하는지 감이 있어야 합니다. JavaScript ES5 이상의 함수 정의 방식과 sap.ui.define 모듈 정의 문법도 함께 알아두면 좋습니다.
환경 및 버전 준비물
이 글의 코드는 다음 환경 기준으로 작성되었습니다.
- SAPUI5 1.120 LTS (또는 OpenUI5 1.120 이상)
- SAP Business Application Studio 또는 VS Code + Fiori Tools 확장
- Node.js 18 LTS 이상 (UI5 Tooling 실행용)
- UI5 CLI(
@ui5/cli) 3.x - 브라우저: Chrome 최신 버전 (개발자 도구 활용)
SAP Fiori Launchpad와 연동할 계획이라면 S/4HANA 2022 이상 또는 SAP BTP ABAP Environment 등 배포 대상 시스템을 미리 확인해두는 것이 일반적으로 권장됩니다. 로컬 실습만 진행할 경우 ui5 serve로 정적 서버를 띄우고 목(mock) 데이터로 확인해도 충분합니다.
핵심 개념 파헤치기
UI5에서 Formatter는 바인딩된 값이 View 컨트롤 속성에 도달하기 직전에 개입하는 변환 함수입니다. 은행 창구에 비유하자면, 고객(Model)이 건넨 원화 지폐(원본 값)를 창구 직원(Formatter)이 봉투에 담아 잘 정리한 뒤 뒷줄 담당자(Control)에게 넘겨주는 역할과 같습니다. 봉투에 담긴 형태만 바뀔 뿐 원본은 훼손되지 않습니다.
Formatter의 방향은 기본적으로 Model → View 단방향입니다. 반대 방향(View → Model)이 필요하다면 Formatter 대신
sap.ui.model.type.*계열의 Type 클래스를 사용해야 합니다. Formatter는 표시만, Type은 표시와 입력 검증까지 처리한다고 이해하면 쉽습니다.
Formatter 함수의 시그니처는 매우 단순합니다.
function formatterName(oValue1, oValue2, ...) {
// 변환 로직
return sConvertedValue;
}
단일 프로퍼티 바인딩이면 인자가 하나, 복합(Composite) 바인딩이면 parts 배열 순서대로 인자가 전달됩니다. 함수 실행 컨텍스트(this)는 기본적으로 바인딩이 걸린 컨트롤이며, 필요하다면 .bind()를 활용해 명시적으로 바꿀 수도 있습니다.
주의할 점은 Formatter는 매 렌더링 사이클마다 호출될 수 있다는 것입니다. 무거운 연산(정규식 반복, 네트워크 호출 등)을 넣으면 리스트 스크롤 성능이 눈에 띄게 저하됩니다. 순수 함수(pure function)로 유지하고, 부수 효과가 없어야 한다는 원칙을 지키는 것이 일반적으로 권장됩니다.
실전 코드 3단계
1단계 - 기본 예제: 날짜 문자열을 사람이 읽기 좋은 포맷으로
OData나 REST API에서 20260714처럼 구분자 없는 8자리 문자열로 날짜가 내려오는 경우를 자주 만납니다. 이를 2026-07-14 형태로 바꾸는 간단한 Formatter입니다.
// webapp/model/formatter.js
sap.ui.define([], function () {
"use strict";
return {
formatOrderDate: function (sRawDate) {
if (!sRawDate || sRawDate.length !== 8) {
return "";
}
var sYear = sRawDate.substring(0, 4);
var sMonth = sRawDate.substring(4, 6);
var sDay = sRawDate.substring(6, 8);
return sYear + "-" + sMonth + "-" + sDay;
}
};
});
Controller에서는 이 모듈을 formatter라는 이름으로 노출시켜 View가 접근할 수 있도록 합니다.
// webapp/controller/SalesOrderList.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"myapp/model/formatter"
], function (Controller, formatter) {
"use strict";
return Controller.extend("myapp.controller.SalesOrderList", {
formatter: formatter,
onInit: function () {
// 모델 초기화 로직
}
});
});
XML View에서는 다음처럼 호출합니다.
<Text text="{
path: 'salesOrder>/OrderDate',
formatter: '.formatter.formatOrderDate'
}" />
2단계 - 실무 시나리오: 통화 기호와 로케일을 반영한 금액 포맷
실제 판매주문 화면에서는 사용자 로케일(ko-KR, en-US 등)에 따라 금액 표기법이 달라져야 합니다. UI5가 제공하는 NumberFormat과 Locale을 활용해 좀 더 견고하게 만들어봅니다. 예상치 못한 값에 대비한 에러 처리와 콘솔 로깅도 함께 추가합니다.
sap.ui.define([
"sap/ui/core/format/NumberFormat",
"sap/base/Log"
], function (NumberFormat, Log) {
"use strict";
return {
formatAmountWithCurrency: function (fAmount, sCurrencyCode) {
if (fAmount === null || fAmount === undefined) {
return "";
}
try {
var oCurrencyFormat = NumberFormat.getCurrencyInstance({
showMeasure: true,
currencyCode: false
});
return oCurrencyFormat.format(fAmount, sCurrencyCode || "KRW");
} catch (oError) {
Log.error(
"금액 포맷 변환 실패",
oError.message,
"myapp.model.formatter"
);
return String(fAmount);
}
}
};
});
이 Formatter는 두 개의 값(금액, 통화코드)이 필요하므로 XML에서 복합 바인딩(parts)으로 연결합니다.
<ObjectNumber
number="{
parts: [
{path: 'salesOrder>/NetAmount'},
{path: 'salesOrder>/Currency'}
],
formatter: '.formatter.formatAmountWithCurrency'
}" />
3단계 - 프로덕션: 상태 코드 변환 + i18n + 단위 테스트
주문 상태 코드(N=신규, P=결제완료, S=배송중, C=취소)를 사용자 언어에 맞게 번역하고, ObjectStatus의 state 색상까지 함께 반환하는 두 개의 Formatter를 만듭니다. i18n 리소스 번들을 컨트롤에서 꺼내는 방식이 핵심입니다.
sap.ui.define([
"sap/ui/core/library",
"sap/base/Log"
], function (coreLibrary, Log) {
"use strict";
var ValueState = coreLibrary.ValueState;
var STATE_MAP = {
"N": ValueState.Information,
"P": ValueState.Success,
"S": ValueState.Warning,
"C": ValueState.Error
};
return {
formatOrderStatusText: function (sStatusCode) {
if (!sStatusCode) {
return "";
}
var oBundle = this.getModel("i18n").getResourceBundle();
var sKey = "orderStatus." + sStatusCode;
var sText = oBundle.getText(sKey);
if (sText === sKey) {
Log.warning("i18n 키 누락: " + sKey);
return sStatusCode;
}
return sText;
},
formatOrderStatusState: function (sStatusCode) {
return STATE_MAP[sStatusCode] || ValueState.None;
}
};
});
<ObjectStatus
text="{
path: 'salesOrder>/StatusCode',
formatter: '.formatter.formatOrderStatusText'
}"
state="{
path: 'salesOrder>/StatusCode',
formatter: '.formatter.formatOrderStatusState'
}" />
Formatter는 순수 함수라 QUnit 단위 테스트로 검증하기 쉽습니다. webapp/test/unit/model/formatter.js에 다음처럼 작성합니다.
sap.ui.define([
"myapp/model/formatter"
], function (formatter) {
"use strict";
QUnit.module("formatOrderDate");
QUnit.test("정상 8자리 문자열을 변환한다", function (assert) {
assert.strictEqual(
formatter.formatOrderDate("20260714"),
"2026-07-14"
);
});
QUnit.test("빈 값이면 빈 문자열을 반환한다", function (assert) {
assert.strictEqual(formatter.formatOrderDate(""), "");
assert.strictEqual(formatter.formatOrderDate(null), "");
});
});
흔한 실수와 트러블슈팅
Formatter 도입 초기에 자주 겪는 문제들을 FAQ 형태로 정리합니다.
Q1. View에 값이 표시되지 않고 [object Object]가 나옵니다.
A. Formatter 경로를 잘못 지정한 경우가 대부분입니다. XML에서 formatter: '.formatter.formatOrderDate'처럼 반드시 앞에 점(.)을 붙여 컨트롤러 컨텍스트를 명시해야 합니다. 또한 컨트롤러에 formatter: formatter 프로퍼티를 노출했는지 확인하세요.
Q2. 초기 렌더링 시 Formatter가 undefined를 받아 에러가 납니다.
A. OData 모델은 비동기로 데이터를 불러오기 때문에 첫 호출 시점에는 값이 없습니다. Formatter 최상단에서 항상 if (!value) return ""; 형태의 가드 절을 두는 것이 일반적으로 권장됩니다. 그렇지 않으면 substring is not a function 같은 오류가 콘솔에 반복 출력됩니다.
Q3. 복합 바인딩(parts)에서 Formatter가 아예 호출되지 않습니다.
A. parts를 쓸 때 최상위에 path를 같이 두면 UI5가 문법을 오인합니다. parts: [ ... ]와 formatter만 두거나, 각 파트 안에 path와 model을 명시해야 합니다. 또한 파트 개수와 Formatter 함수의 파라미터 개수가 반드시 일치해야 합니다.
그 외에도 Formatter 내부에서 console.log를 남발하면 프로덕션 성능에 영향이 있으므로 sap/base/Log를 사용하고, 리스트 아이템이 수천 건 이상이라면 Formatter 내부 로직을 최소화하거나 서버 측에서 이미 변환된 필드를 별도로 내려받는 편이 낫습니다.
이어서 살펴보면 좋은 주제
Formatter로 표시 변환의 기본을 익혔다면, 다음 단계로 sap.ui.model.type.Date, sap.ui.model.type.Currency와 같은 Type 클래스를 학습해 양방향 바인딩과 입력 검증까지 처리하는 방법을 살펴볼 수 있습니다. 또한 Fiori Elements 앱을 사용하는 경우, Annotation의 @UI.TextArrangement나 Value Help를 통해 Formatter 없이도 표시 변환이 가능한 케이스가 많으므로 CDS Annotation 학습을 병행하면 시너지가 큽니다. 성능 튜닝 관점에서는 UI5 Flexible Column Layout에서의 재렌더링 최적화, Expression Binding으로 간단한 조건 분기를 인라인 처리하는 기법도 함께 익혀두면 실무 생산성이 올라갑니다.
더 읽어볼 링크
댓글 0
아직 댓글이 없습니다.