! 제품 버전을 정확하게 입력해 주세요.
제품 버전이 정확하게 기재되어 있지 않은 경우,
최신 버전을 기준으로 안내 드리므로
더욱 빠르고 명확한 안내를 위해
제품 버전을 정확하게 입력해 주세요!

Linked MultiSelectListBox 구현하기 > 온라인 스터디

본문 바로가기

FlexGrid Linked MultiSelectListBox 구현하기

페이지 정보

작성자 GrapeCity 작성일 2022-01-25 15:29 조회 1,165회 댓글 0건

본문

최근 많은 웹 응용프로그램에서 최종 사용자가 선택한 옵션과 선택 순서에 따라서 사용자에게 보여지는 뷰/항목들을 커스터마이징 할 수 있는 옵션들을 많이 제공해 주고 있습니다.

하지만, 이러한 커스터마이징 기능을 위해 최종 사용자에게 선택할 수 있는 옵션들과 선택된 항목들에 대한 구분과 우선 순위를 직관적으로 이해하고 사용하도록 이를 웹 상에 구현하는 것은 결코 쉬운 일이 아닙니다.

예를 들어, 그리드(Grid) 상에 보여지는 컬럼(열)을 사용자의 옵션 선택에 따라서 조정하고자 한다고 가정해 보겠습니다.

어떤 사용자는 모든 컬럼을 보여주고 싶고, 어떤 사용자는 그중에 몇 개만 보여주고 싶을 것이고, 또 어떤 사용자는 컬럼(열)의 순서를 본인이 원하는 방식으로 변경하고 싶을 것입니다.

이런 옵션 선택 기능을 위해, 그동안 많이 사용해오신 콤보(체크)박스 또는 멀티셀렉트 박스를 고민해 볼 수 있으나, 이러한 컨트롤들은 기본적으로 선택에만 중점을 두고 있을 뿐, 선택된 항목의 순서(Order)를 바꾸거나 또는 우선 순위를 준다는 것은 불가능합니다.

이를 위해, 이번 포스팅에서는 아래와 같이 그동안 많은 고객분들께서 요청해주신

Wijmo의 MultiSelectListBox 컨트롤을 응용하여 동적으로 데이터 항목을 MultiSelectListBox 간에 이동시킬 수 있는, 사용자 친화적인 Linked MultiSelectListBox를 구현해 보도록 하겠습니다.


실제로 구현된 화면은 아래와 같으며, 상단의 HTML / CSS / JS 탭을 클릭하여 소스코드를 확인하고 바로 수정해볼 수 있습니다.

샘플 링크 : Linked MultiSelectBox (codepen.io)


Wijmo Linked MultiSelectListBox 구현하기

아래 과정을 통해서 위의 샘플과 동일한 결과물을 만들 수 있습니다.

* 전체 소스 코드는 상단 라이브 샘플 화면의 HTML / CSS / JS 탭을 누르시면 확인이 가능합니다.


  1. 먼저 Wijmo 컨트롤을 사용하기 위해 HTML의 head 태그 안에 레퍼런스를 추가해줍니다.

    <link rel="stylesheet" href="https://cdn.grapecity.com/wijmo/5.20212.808/styles/wijmo.min.css" />
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
    integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"/>
       
    <script src="https://cdn.grapecity.com/wijmo/5.20212.808/controls/wijmo.min.js"></script>
    <script src="https://cdn.grapecity.com/wijmo/5.20212.808/controls/wijmo.input.min.js"></script>

  1. Wijmo의 MultiSelectListBox 컨트롤을 할당할 'theMultiSelectOne', 'theMultiSelectTwo'라는 id를 가진 div 요소를 HTML 파일에 생성합니다

    <!-- 첫번째 MultiSelectListBox -->
    <div id="theMultiSelectOne"></div>
    ​
    <!-- 두번째 MultiSelectListBox -->
    <div id="theMultiSelectTwo"></div>

     

  1. 각 MultiSelectBox에 추가할 랜덤 데이터 함수를 생성합니다.

    function getData() {
      return [
        { id: 1, country: "Luxembourg", gdpm: 57825, popk: 563, gdpcap: 102708 },
        { id: 2, country: "Switzerland", gdpm: 664005, popk: 8238, gdpcap: 80602 },
        { id: 3, country: "Norway", gdpm: 388315, popk: 5205, gdpcap: 74604 },
        { id: 4, country: "Macao", gdpm: 46178, popk: 647, gdpcap: 71372 },
        { id: 5, country: "Qatar", gdpm: 166908, popk: 2421, gdpcap: 68941 },
        { id: 6, country: "Ireland", gdpm: 283716, popk: 4635, gdpcap: 61211 },
        (...)
      ];
    }
    ​
    function getData2(){
      (...)
    }

  1. MultiSelectListBox 컨트롤의 인스턴스를 생성한 후, itemsSource에 데이터를 할당하고 속성을 설정해 줍니다.

    function init() {
        let theMultiSelectOne = new wijmo.input.MultiSelectListBox("#theMultiSelectOne", {
            displayMemberPath: "country", //display 할 속성의 이름을 가져오거나 설정
            showSelectAllCheckbox: true, //컨트롤이 모든 항목을 선택하거나 선택 취소할 항목 위에 "모두 선택" 체크박스를 표시할지 여부 결정
            itemsSource: getData()
        });
        let theMultiSelectTwo = new wijmo.input.MultiSelectListBox("#theMultiSelectTwo", {
            displayMemberPath: "country",
            showSelectAllCheckbox: true,
            itemsSource: getDataTwo()
        });
    }

  • showSelectAllCheckbox : 항목을 모두 선택하거나 모두 선택 해제할 수 있는 "모두 선택" 체크박스 표시 여부를 결정하는 속성으로 List에 나타나는 모든 항목을 한번에 선택/선택 해제 할 수 있습니다.

  1. enableOnlyInputSelection 사용자 정의 함수를 통해 체크박스가 아닌 요소를 클릭하는 경우, 체크박스가 선택되는 동작을 방지하는 동시에 클릭된 항목에 선택(포커스)이 되도록 currentItem을 설정해줍니다.

  • closestClass을 이용하여 클래스 selector에 가장 가까운 상위 항목을 가져온 뒤, 배열의 지정된 요소를 찾을 수 있는 indexOf를 통해 현재 클릭한 항목의 인덱스를 가져옵니다.

  • 가져온 인덱스를 통해 MultiSelectListBox에서 클릭된 항목 정보를 가져온 뒤, 뷰에 현재 항목을 선택하거나 가져오는 currentItem에 항목을 할당하여 포커스가 클릭된 항목에 가도록 설정해줍니다.

enableOnlyInputSelection(theMultiSelectOne);
enableOnlyInputSelection(theMultiSelectTwo);

(......)

function enableOnlyInputSelection(listbox) {
    listbox.hostElement.addEventListener(
        "click",
        (e) => {
            let target = e.target;
            if (target.tagName !== "INPUT") { // input이 아니면
                e.stopPropagation(); //이벤트가 상위 DOM으로 전파되지 않도록 하는 메서드
                e.preventDefault(); //기본 정의된 이벤트가 작동하지 않도록 하는 메서드
                let lbItem = wijmo.closestClass(target, "wj-listbox-item"); // 상위 항목 가져오기
                let lbIndex = Array.prototype.indexOf.call( //클릭한 항목의 인덱스 가져오기
                    lbItem.parentElement.children,
                    lbItem
                );
                let item = listbox.collectionView.items[lbIndex]; //클릭한 아이템 가져오기
                listbox.collectionView.currentItem = item; //클릭한 아이템을 currentItem으로 설정
            }
        },
        true
    );
}


  1. resetPosition 사용자 정의 함수에서 업데이트된 position 정보를 바탕으로 sourceCollection의 데이터들을 정렬해준 다음, 변경된 내용에 대해서 반영하기 위해 그리드를 refresh 해줍니다.

    • sourceCollection은 필터와 정렬이 적용되지 않은 원본 컬렉션을 의미합니다.

    resetPosition(theMultiSelectTwo, true);
    
    function resetPosition(multiselect, updatePos) {
        if (updatePos) { //_pos 값 업데이트
            multiselect.collectionView.sourceCollection.forEach(
                function (c, idx) {
                    return c._pos = idx
                }
            );
        }
        multiselect.collectionView.sourceCollection.sort(function (a, b) {
            return a._pos - b._pos
        }); //_pos 값에 따라 정렬
        multiselect.collectionView.refresh(); // 반영을 위해 새로 고침
    }

위의 기본적인 세팅 및 CSS 설정을 완료하면, MultiSelectListBox가 아래와 같이 나타납니다!


  1. html 영역에 MultiSelectListBox 컨트롤 간의 항목을 이동시키는 이벤트를 발생시키는 버튼을 추가 해줍니다.

    • glyphicon 의 경우, bootstrap에서 제공하는 기본 아이콘이며 1번 단계에서 boostrap CDN 추가하시면 아이콘을 표시하실 수 있습니다.

    <button class="btn" id="btn1" >
         <span class="glyphicon glyphicon-chevron-right"></span>
    </button>
    ​
    <button class="btn" id="btn2" >
         <span class="glyphicon glyphicon-chevron-left"></span>
    </button>


  1. 클릭 이벤트 시 발생하는 각각의 동작에 대한 moveItem 사용자 정의 함수를 설정해 줍니다.

    • btnOne 버튼 이벤트에서 함수를 호출하여 theMultiSelectOne에서 theMultiSelectTwo로 항목을 이동시켜줍니다.

      • 선택된 항목(이동시킬 항목)을 checkedItems을 통해 저장한 뒤, 현재 선택되지 않은 항목만 filter 함수를 통해 return 해줍니다.

      • 그 다음 theMultiSelectTwo 컨트롤의 데이터 컬렉션(sourceCollection)에 선택한 항목을 push 해주고 변경된 사항이 적용되도록 refresh 해줍니다.

      • resetPosition으로 리스트 항목의 위치를 다시 정렬해줍니다.

    • btnTwo 버튼 이벤트 내에서 moveItem 함수는 theMultiSelectTwo 컨트롤의 항목을 theMultiSelectOne 컨트롤로 이동시켜줍니다.

    document.querySelector("#btn1").addEventListener("click", (e) => { //왼쪽 MultiSelectListBox에서 오른쪽 MultiSelectListBox으로 이동
        moveItem(theMultiSelectOne, theMultiSelectTwo, 0)
    });​
    document.querySelector("#btn2").addEventListener("click", (e) => { //오른쪽 MultiSelectListBox에서 왼쪽 MultiSelectListBox으로 이동
        moveItem(theMultiSelectTwo, theMultiSelectOne, 1)
    });​
    function moveItem(control, opponent, index) {
        let checkedItems = control.checkedItems; //체크된 항목 컬렉션
        control.collectionView.sourceCollection = control.collectionView.sourceCollection.filter(
            (item) => item.$checked !== true
        );
        for (let i = 0; i < checkedItems.length; i++) {
            opponent.collectionView.sourceCollection.push(checkedItems[i]); //선택된 항목(이동시킬 항목)을 오른쪽 컨트롤의 데이터 컬렉션으로 이동
            opponent.collectionView.refresh(); //새로고침
            if (index === 0) { // btn1 이벤트인지 확인
                resetPosition(opponent);
            }
        }​
    }

    위 설정 이후 아래와 같이 MultiSelectListBox 간 아이템을 이동시킬 수 있습니다.


  1. HTML 파일에 theMultiSelectTwo 컨트롤의 항목들을 위/아래/최상단/최하단으로 이동시키는 이벤트를 가진 버튼 요소들을 추가해줍니다.

    <div id='btnDiv'>
      <button class="upDownBtn btn" id="btn3" >
        <span class="glyphicon glyphicon-backward"></span>
      </button>
      <button class="upDownBtn btn" id="btn4" >
        <span class="glyphicon glyphicon-chevron-right"></span></button>
      <button class="upDownBtn btn" id="btn5">
        <span class="glyphicon glyphicon-chevron-left"></span></button>
      </button>
      <button class="upDownBtn btn" id="btn6">
        <span class="glyphicon glyphicon-forward"></span>
      </button>
    </div>

    위의 html의 버튼 요소들이 오른쪽 MultiSelectListBox 위에 나타나게 됩니다.


  1. moveCheckedItems 에 이동을 적용할 컨트롤과 동작 명령을 할당해주고 해당 사용자 함수를 호출합니다.

    document.querySelector("#btn3").addEventListener("click", (e) => {
        moveCheckedItems(theMultiSelectTwo, "first"); //최상단으로 이동
    });​
    document.querySelector("#btn4").addEventListener("click", (e) => {
        moveCheckedItems(theMultiSelectTwo, "next"); // 아래로 이동
    });​
    document.querySelector("#btn5").addEventListener("click", (e) => {
        moveCheckedItems(theMultiSelectTwo, "prev"); //위로 이동
    });​
    document.querySelector("#btn6").addEventListener("click", (e) => {
        moveCheckedItems(theMultiSelectTwo, "last"); //최하단으로 이동
    });

  1. MultiSelectListBox 컨트롤에서 selectedIndex를 통해 데이터 항목(현재 포커스 된 항목)을 가져온 뒤, 그 다음 해당 항목을 sourceCollection에서 삭제합니다.

    function movePosition(list, action) {
     let selctedIndex = list.selectedIndex;// selectedIndex : 현재 선택된 항목의 인덱스를 가져오거나 설정하는 속성
     let item = list.collectionView.items[selctedIndex];
     list.collectionView.sourceCollection.splice(selctedIndex, 1);
    ​
     switch (action) {
       case "first":
        (...)
    }

  1. 그 다음, switch문을 통해 주어진 액션에 따라 sourceCollection에서 삭제된 항목이 다시 추가될 위치가 정해지면 sourceCollection에 데이터 항목을 추가한 후, 추가된 항목에 포커스가 가도록 selectedIndex도 재설정합니다 .

    function movePosition(list, action) {
        (...)
        switch (action) {
            case "first":
                list.collectionView.sourceCollection.unshift(item); //제일 첫 번째 항목에 추가
                list.selectedIndex = 0; // 첫 번째 항목에 포커스 설정
                break;
            case "last":
                list.collectionView.sourceCollection.push(item); //맨 마지막 항목에 추가
                list.selectedIndex = list.collectionView.sourceCollection.length - 1; // 맨 마지막 항목에 포커스 설정
                break;
            case "prev":
                list.collectionView.sourceCollection.splice(selctedIndex - 1, 0, item); //기존 선택된 인덱스 이전에 추가
                list.selectedIndex = Math.max(selctedIndex - 1, 0); //기존 선택된 인덱스 이전에 포커스 설정
                break;
            case "next":
                list.collectionView.sourceCollection.splice(selctedIndex + 1, 0, item); //기존 선택된 인덱스 댜음에 추가
                list.selectedIndex = Math.min(
                    selctedIndex + 1,
                    list.collectionView.sourceCollection.length - 1
                ); //기존 선택된 인덱스 다음에 포커스 설정
                break;
            default:
                break;
        }
    }

  1. 마지막으로 변경된 내용을 적용하기 위해 refresh 후, 현재 항목을 재설정하고 resetPositon 함수를 호출하여 컨트롤 위치 정보를 재설정 해줍니다.

    function movePosition(list, action) {
      (...)
      list.collectionView.refresh();
      list.collectionView.currentItem = item;
      resetPosition(list, true);
    }

    movePosition 설정 이후 오른쪽 MultiSelectListBox 내부의 항목을 버튼을 이용하여 위, 아래로 이동할 수 있게 되었으며 기본적인 Linked MultiSelectListBox 구현이 완료되었습니다! 추가적으로 오른쪽 MultiSelectListBox 컨트롤의 (체크박스가) 선택된 항목을 별도 창으로 가져오는 기능을 구현해 보도록 하겠습니다.

  1. 먼저, 이벤트 발생 시 선택된 항목을 보여주기 위한 button 요소를 HTML에 추가합니다.

    <button id="showItem" class="btn">선택된 항목 보여주기</button>

  1. 그다음, 추가된 button 요소의 id 값을 가져와 click 이벤트 핸들러를 추가하여 버튼 클릭 발생 시, 선택된 항목을 보여주는 이벤트를 작성해줍니다.

    • theMultiSelectTwo의 checkedItems 배열을 forEach 문을 통해 반복해 주면서 각각의 id와 country 값을 가져오고 result 변수에 해당 값을 추가합니다.

    • 만일 result의 길이가 0이면 선택된 항목이 없다는 의미이므로 선택된 값이 없다는 문구를 추가해줍니다.

    • 마지막으로 alert 메서드를 호출하여 결과 값을 보여줍니다.

    document.querySelector('#showItem').addEventListener("click", (e) => {
        let result = '';
        theMultiSelectTwo.checkedItems.forEach((item) => {
            result += `id는 ${item.id}이고 값은 ${item.country}입니다.\n`
        });
        if (result.length === 0) {
            result = '선택된 값이 없습니다.'
        }
        alert(result);
    })

    체크박스를 클릭하여 선택된 항목은 아래와 같이 버튼 클릭 시 alert 창으로 나오게 됩니다!


이상으로 Wijmo MultiSelectListBox를 이용하여 Linked MultiSelectListBox를 구현하는 방법에 대한 튜토리얼을 마치겠습니다. 해당 컨트롤을 이용하여 사용자 친화적이고 강력한 UI를 구현해 보시기 바랍니다. MultiSelectListBox의 기본 데모가 궁금하시다면 여기를 클릭하여 주시기 바랍니다.




지금 바로 Wijmo를 다운로드하여 직접 테스트해보세요!

 
  • 페이스북으로 공유
  • 트위터로  공유
  • 링크 복사
  • 카카오톡으로 보내기

댓글목록

등록된 댓글이 없습니다.

메시어스 홈페이지를 통해 제품에 대해서 더 자세히 알아 보세요!
홈페이지 바로가기

태그1

인기글

더보기
  • 인기 게시물이 없습니다.
메시어스 홈페이지를 통해 제품에 대해서 더 자세히 알아 보세요!
홈페이지 바로가기
이메일 : sales-kor@mescius.com | 전화 : 1670-0583 | 경기도 과천시 과천대로 7길 33, 디테크타워 B동 1107호 메시어스(주) 대표자 : 허경명 | 사업자등록번호 : 123-84-00981 | 통신판매업신고번호 : 2013-경기안양-00331 ⓒ 2024 MESCIUS inc. All rights reserved.