HTML, CSS, JavaScript

[JavaScript] 테이블 토글 버튼(트리 구조 테이블)

망고고래 2025. 3. 7. 21:49

1. 개요

- 테이블: api 통신을 통해 받아서 오는 데이터로 작성

- 토글 버튼은 각 행에 설치하되, 컬럼 안에 들어있지 않고 absolute를 사용해 떠있게 함

- 토글 버튼을 클릭한 경우의 동작

  각 행의 데이터 중 sub_table_YN의 값이 Y: 해당 행 아래에 서브 테이블을 보여줌

  sub_table_YN의 값이 N: 서브 테이블 데이터를 추가하는 모달 오픈

  이 동작은 라디오 버튼이 체크된 후에 이루어져야 함

 

선택(라디오 버튼) 컬럼1 컬럼2 컬럼3 컬럼4
 +   ⊙        
 +   ⊙        

 

2. 코드

//현재 활성화되어있는 토글 버튼을 관리하기 위한 변수
let activeToggleButton = null;

function updateTable(data){
    //테이블과 티바디 초기화
    const table = document.getElementById('table');
    const tbody = table.querySelector('tbody') || table.createTBody();
    tbody.innerHTML = '';
    
    data.forEach((tableData, index) => {
        //행 생성 및 데이터셋 초기화
        const row = tbody.insertRow();
        row.dataset.data1 = tableData.data1 || '';
        //...
        row.dataset.hasSubData = tableData.sub_data_YN === 'Y' ? 'true' : 'false';
        
        
        //첫 번째 td 생성, 라디오 버튼과 토글 버튼 추가
        const firstTd = document.createElement('td');
        firstTd.style.position = 'relative';
        
        //라디오 버튼 추가
        const radio = document.createElement('input');
        radio.type = 'radio';
        radio.name = 'tableRadio';
        firstTd.appendChild(radio);
        
        //토글 버튼 추가
        //토글 스타일 설정
        const toggleButton = document.createElement('button');
        toggleButton.className = 'sub-data-toggle'; //스타일 적용을 위한 클래스
        if(tableData.sub_data_YN === 'Y'){
            toggleButton.classList.add('has-sub-data');
        }
        toggleButton.textContent = '+';
        //클릭 이벤트 설정
        toggleButton.onclick = function(e){
            e.preventDefault();
            e.stopPropagation();
            
            radio.checked = true;
            //서브 데이터를 라디오가 선택된 행을 기준으로 가져오기 때문에
            //라디오 체크가 필요
            
            //라디오가 클릭된 뒤에 서브 데이터를 불러오도록 지연
            setTimeout(() => {
                //기존의 활성화된 토글 버튼 초기화
                if(activeToggleButton && activeToggleButton !== this){
                    activeToggleButton.className.remove('active');
                    activeToggleButton.textContent = '+';
                    
                    //기존 하위 테이블 제거
                    const previousRow = activeToggleButton.closest('tr');
                    if(previousRow){
                        toggleSubContractVisibility(previousRow);
                    }
                    activeToggleButton = null;
                }
                
                //기존에는 dataset의 YN으로 체크했으나, 클래스 has-sub-data로 체크
                if(this.classList.contains('has-sub-data')){
                    //현재 버튼이 active 상태인 경우
                    if(this.classList.contains('active')){
                        console.log('active 상태에서 클릭');
                        toggleSubContractVisibility(row); //하위 계약 테이블 제거
                        this.classList.remove('active');  //active 상태 제거
                        this.textContent = '+';  //버튼 텍스트 변경
                        activeToggleButton = null;  //활성 버튼 참조 제거
                        return; //함수 종료
                    }
                    
                    console.log('비활성 상태에서 클릭');
                    this.classList.add('active');
                    this.textContent = '-';
                    activeToggleButton = this;
                    updateSubContractTable(row);
                }else{
                    openSubDataModal('add'); //데이터 신규 작성 모달
                }//has-sub-data 클래스 유무 if문
            }, 100);
        };//토글 버튼 클릭 이벤트 설정
        firstTd.appendChild(toggleButton);
        row.appendChild(firstTd);
        
        //나머지 내용 HTML로 추가
        row.insertAdjacentHTML('beforeend', `
            <td class="text-center">${index + 1}</td>
            ...
        `);
        
        //다른 행 클릭시 활성화된 토글 버튼 비활성화
        row.addEventListner('click', function(e){
            //토글 버튼 클릭은 제외
            if(e.target.classList.contains('sub-contract-toggle')){
                return;
            }
            
            radio.checked = true;
            
            if(activeToggleButton){
                activeToggleButton.classList.remove('active');
                activeToggleButton.textContent = '+';
                const previousRow = activeToggleButton.closest('tr');
                if(previousRow){
                    toggleSubContractVisibility(previousRow);
                }
                activeToggleButton = null;
            }
        });
    });
}

 

 

내부에서 호출하는 다른 함수들

function toggleSubContractVisibility(parentRow){
    const nextRow = parentRow.nextElementSibling;
    if(nextRow && nextRow.classList.contains('sub-contract-row')){
        nextRow.remove();
    }
}

 

3. 리뷰

새로 알게 된 함수

insertAdjacentHTML()

 

더 공부할 것

1) html 요소 자바스크립트로 조작하기

- classList.add()

- classList.contains()

- appendChild()

- createElement()

- element.name = ' '

- element.type = ' '

- creatTBody()

등등