UI5

아직도 단일 선택? MultiComboBox 3단계 #shorts #SAP #UI5

▶ YouTube에서 보기

개요 및 이 글에서 다루는 범위

SAPUI5/OpenUI5의 sap.m.MultiComboBox는 사용자가 드롭다운에서 여러 항목을 동시에 선택할 수 있게 해주는 입력 컨트롤입니다. 단일 선택만 가능한 ComboBox와 달리, 토큰(Token) 형태로 선택된 값이 입력 필드 안에 누적 표시되어 발주 카테고리 필터, 권한 그룹 부여, 다중 부서 검색 같은 실무 시나리오에서 자주 사용됩니다. 이 글은 XML View 선언부터 selectionChange 이벤트 처리, getSelectedKeys로 선택값을 추출해 OData 필터로 넘기는 흐름까지 3단계로 정리합니다.

핵심 개념과 동작 원리

MultiComboBox는 내부적으로 sap.m.ComboBoxBase를 상속하며, 선택된 항목을 selectedKeys(문자열 배열) 프로퍼티로 관리합니다. ComboBox와의 가장 큰 차이는 리스트 항목 좌측의 체크박스 유무와 선택값이 단일 문자열이냐 배열이냐입니다. ComboBox는 selectedKey(단수), MultiComboBox는 selectedKeys(복수)를 사용합니다.

이벤트 측면에서 selectionChange는 항목이 추가/제거될 때마다 발생하는 반면, selectionFinish는 드롭다운이 닫힐 때 한 번만 발생합니다. 실시간 필터링이 필요하면 전자, 서버 호출 비용을 줄이려면 후자가 적합합니다.

선택값 추출 API는 두 가지입니다. getSelectedKeys()["CAT01", "CAT02"]처럼 키 배열만 반환하므로 OData 필터 구성에 바로 활용하기 좋습니다. getSelectedItems()sap.ui.core.Item 객체 배열을 돌려주어 getText(), getKey(), getBindingContext() 같은 메서드를 통해 부가 정보를 가져올 수 있습니다.

1단계: XML 선언과 JSON 모델 바인딩

e-commerce 어드민 화면에서 상품 카테고리를 다중 선택하는 시나리오입니다. View XML에 컨트롤을 선언합니다.

<mvc:View
    controllerName="com.acme.shop.controller.CategoryFilter"
    xmlns="sap.m"
    xmlns:core="sap.ui.core"
    xmlns:mvc="sap.ui.core.mvc">
    <Panel headerText="상품 카테고리 필터">
        <MultiComboBox
            id="mcbCategory"
            width="320px"
            placeholder="카테고리를 선택하세요"
            items="{categoryModel>/categories}"
            selectionChange="onCategoryChange"
            selectionFinish="onCategoryFinish">
            <core:Item
                key="{categoryModel>code}"
                text="{categoryModel>label}" />
        </MultiComboBox>
    </Panel>
</mvc:View>

컨트롤러에서 JSON 모델을 만들어 카테고리 목록을 주입합니다.

sap.ui.define([
    "sap/ui/core/mvc/Controller",
    "sap/ui/model/json/JSONModel"
], function (Controller, JSONModel) {
    "use strict";

    return Controller.extend("com.acme.shop.controller.CategoryFilter", {
        onInit: function () {
            var oCategoryModel = new JSONModel({
                categories: [
                    { code: "FASHION",  label: "패션/의류" },
                    { code: "BEAUTY",   label: "뷰티" },
                    { code: "DIGITAL",  label: "디지털가전" },
                    { code: "LIVING",   label: "리빙/주방" },
                    { code: "SPORTS",   label: "스포츠/레저" },
                    { code: "GROCERY",  label: "식품" }
                ]
            });
            this.getView().setModel(oCategoryModel, "categoryModel");
        },

        onCategoryChange: function (oEvent) {
            var aKeys = oEvent.getSource().getSelectedKeys();
            console.log("현재 선택된 키:", aKeys);
        },

        onCategoryFinish: function (oEvent) {
            var aKeys = oEvent.getSource().getSelectedKeys();
            sap.m.MessageToast.show("선택 완료: " + aKeys.join(", "));
        }
    });
});

2단계: selectionChange 이벤트로 실시간 필터 처리

실무에서는 선택 즉시 필터 영역을 갱신하고 잘못된 입력에 대해 사용자에게 피드백을 줘야 합니다. changedItemselected 파라미터로 추가/제거를 구분합니다.

onCategoryChange: function (oEvent) {
    var oMcb = oEvent.getSource();
    var oChangedItem = oEvent.getParameter("changedItem");
    var bSelected = oEvent.getParameter("selected");
    var sReason = bSelected ? "추가됨" : "해제됨";

    if (!oChangedItem) {
        sap.m.MessageToast.show("등록되지 않은 카테고리입니다.");
        return;
    }

    console.log("[" + sReason + "] " + oChangedItem.getKey() + " / " + oChangedItem.getText());

    var aKeys = oMcb.getSelectedKeys();
    this._applyClientFilter(aKeys);
},

_applyClientFilter: function (aKeys) {
    var oTable = this.byId("productTable");
    if (!oTable || aKeys.length === 0) {
        if (oTable) { oTable.getBinding("items").filter([]); }
        return;
    }
    var aFilters = aKeys.map(function (sKey) {
        return new sap.ui.model.Filter("CategoryCode", "EQ", sKey);
    });
    var oCombined = new sap.ui.model.Filter({ filters: aFilters, and: false });
    oTable.getBinding("items").filter([oCombined]);
}

changedItem이 null로 들어오는 경우는 사용자가 드롭다운에 없는 텍스트를 직접 입력하고 Enter를 친 상황입니다. 명시적으로 토스트를 띄워주면 UX가 자연스러워집니다.

3단계: getSelectedKeys로 OData 필터 구성

SAP S/4HANA나 BTP 환경에서는 선택된 키들을 OData 서비스 조회 시 필터로 전달하는 흐름이 일반적입니다. selectionFinish와 디바운스를 조합해 불필요한 서버 호출을 막습니다.

<MultiComboBox
    id="mcbCategory"
    width="20rem"
    placeholder="검색할 카테고리를 선택"
    items="{
        path: /ProductCategorySet,
        sorter: { path: CategoryLabel },
        parameters: { select: CategoryCode,CategoryLabel }
    }"
    selectionFinish=".onCategoryFinish">
    <core:ListItem
        key="{CategoryCode}"
        text="{CategoryLabel}"
        additionalText="{CategoryCode}" />
</MultiComboBox>
onCategoryFinish: function (oEvent) {
    var oMcb = oEvent.getSource();
    var aKeys = oMcb.getSelectedKeys();

    if (aKeys.length > 5) {
        oMcb.setValueState("Error");
        oMcb.setValueStateText("최대 5개까지만 선택할 수 있습니다.");
        return;
    }
    oMcb.setValueState("None");

    if (this._iSearchDebounce) { clearTimeout(this._iSearchDebounce); }
    this._iSearchDebounce = setTimeout(function () {
        this._refreshProductTable(aKeys);
    }.bind(this), 200);
},

_refreshProductTable: function (aKeys) {
    var oBinding = this.byId("productTable").getBinding("items");

    if (!aKeys.length) {
        oBinding.filter([]);
        return;
    }

    var aFilters = aKeys.map(function (sCode) {
        return new sap.ui.model.Filter("CategoryCode", "EQ", sCode);
    });
    oBinding.filter([new sap.ui.model.Filter({ filters: aFilters, and: false })]);
}

초기값 설정과 유효성 검사

초기 진입 시 자주 사용되는 항목을 미리 선택하려면 items 바인딩이 완료된 후 setSelectedKeys를 호출해야 합니다. 비동기 모델에서는 attachRequestCompleted 또는 attachDataReceived 이후에 호출해야 토큰이 정상 표시됩니다.

// JSONModel 사용 시 — 데이터 세팅 직후 호출 가능
this.getView().setModel(oCategoryModel, "categoryModel");
this.getView().byId("mcbCategory").setSelectedKeys(["FASHION", "BEAUTY"]);

// OData 바인딩 사용 시 — 데이터 수신 후 호출
var oBinding = this.getView().byId("mcbCategory").getBinding("items");
oBinding.attachEventOnce("dataReceived", function () {
    this.getView().byId("mcbCategory").setSelectedKeys(["FASHION"]);
}.bind(this));

자주 만나는 함정과 FAQ

  • getSelectedKeys()가 빈 배열 반환: core:Item key="{code}"에서 key가 누락되거나 모델 prefix 오타인 경우가 가장 흔합니다. 모델 이름을 지정했다면 categoryModel>code처럼 prefix를 key 바인딩에도 적용해야 합니다.
  • setSelectedKeys 후 토큰 미표시: items 바인딩이 비동기로 도착하기 전에 호출하면 매칭 항목이 없어 무시됩니다. dataReceived 이후 호출로 해결합니다.
  • selectionChange 이중 발화: 컨트롤러 라이프사이클에서 이벤트 핸들러를 중복 attach한 경우 발생합니다. onAfterRendering에서 attach하면 매 렌더링마다 추가되므로 주의가 필요합니다.

응용 패턴 — FilterBar 통합

sap.ui.comp.filterbar.FilterBar와 함께 사용할 때는 FilterGroupItem으로 감싸고 name을 지정해야 Variant 저장/복원이 동작합니다. Variant 복원 시에도 setSelectedKeys가 dataReceived 이후 타이밍에 호출되도록 FilterBar의 afterVariantLoad 이벤트와 연동하는 것이 안정적입니다.

댓글 0

아직 댓글이 없습니다.