# 동적행 생성 시스템 (Dynamic Row Generation System) ## 개요 이 문서는 웹 애플리케이션에서 동적으로 테이블 행을 생성, 삭제, 복사할 수 있는 시스템의 구현 방법을 설명합니다. ## 주요 기능 - **행 추가**: 기존 행 아래에 새로운 행 삽입 - **행 삭제**: 선택된 행 제거 - **행 복사**: 기존 행의 내용을 복사하여 새로운 행 생성 - **동적 ID 관리**: 자동으로 증가하는 행 인덱스 관리 ## HTML 구조 ### 기본 테이블 구조 ```html
관리 할일 완료 완료일 경과
``` ### 버튼 그룹 구조 ```html
``` ### 개별 행 구조 ```html
onchange="updateCompletionDate(this)">
``` ## JavaScript 함수들 ### 1. 행 추가 함수 (addRowAfter) ```javascript function addRowAfter(rowIndex) { const tbody = document.getElementById('taskTableBody'); const rows = tbody.querySelectorAll('.task-row'); const newRowIndex = getNextRowIndex(); // 새로운 행 HTML 생성 const newRow = createTaskRow(newRowIndex); // 지정된 행 다음에 삽입 if (rowIndex < rows.length - 1) { rows[rowIndex].insertAdjacentHTML('afterend', newRow); } else { tbody.insertAdjacentHTML('beforeend', newRow); } // 행 인덱스 재정렬 reorderRowIndexes(); // 통계 업데이트 updateTaskStatistics(); } function createTaskRow(rowIndex) { return `
- `; } ``` ### 2. 행 삭제 함수 (deleteRow) ```javascript function deleteRow(rowIndex) { const tbody = document.getElementById('taskTableBody'); const rows = tbody.querySelectorAll('.task-row'); if (rows.length <= 1) { alert('최소 하나의 행은 유지해야 합니다.'); return; } // 행 삭제 확인 if (!confirm('이 행을 삭제하시겠습니까?')) { return; } // 해당 행 삭제 const targetRow = tbody.querySelector(`tr[data-row="${rowIndex}"]`); if (targetRow) { targetRow.remove(); } // 행 인덱스 재정렬 reorderRowIndexes(); // 통계 업데이트 updateTaskStatistics(); } ``` ### 3. 행 복사 함수 (copyRow) ```javascript function copyRow(rowIndex) { const tbody = document.getElementById('taskTableBody'); const sourceRow = tbody.querySelector(`tr[data-row="${rowIndex}"]`); const newRowIndex = getNextRowIndex(); if (!sourceRow) return; // 소스 행의 데이터 수집 const taskContent = sourceRow.querySelector('input[name*="[task_content]"]').value; const isCompleted = sourceRow.querySelector('input[name*="[is_completed]"]').checked; const completionDate = sourceRow.querySelector('input[name*="[completion_date]"]').value; // 새로운 행 생성 (복사된 데이터 포함) const newRow = createTaskRowWithData(newRowIndex, taskContent, isCompleted, completionDate); // 소스 행 다음에 삽입 sourceRow.insertAdjacentHTML('afterend', newRow); // 행 인덱스 재정렬 reorderRowIndexes(); // 통계 업데이트 updateTaskStatistics(); } function createTaskRowWithData(rowIndex, taskContent, isCompleted, completionDate) { const checkedAttr = isCompleted ? 'checked' : ''; const readonlyAttr = isCompleted ? '' : 'readonly'; return `
- `; } ``` ### 4. 유틸리티 함수들 ```javascript // 다음 행 인덱스 가져오기 function getNextRowIndex() { const tbody = document.getElementById('taskTableBody'); const rows = tbody.querySelectorAll('.task-row'); return rows.length; } // 행 인덱스 재정렬 function reorderRowIndexes() { const tbody = document.getElementById('taskTableBody'); const rows = tbody.querySelectorAll('.task-row'); rows.forEach((row, index) => { // data-row 속성 업데이트 row.setAttribute('data-row', index); // 모든 input의 name 속성 업데이트 const inputs = row.querySelectorAll('input'); inputs.forEach(input => { const name = input.getAttribute('name'); if (name && name.includes('[')) { const newName = name.replace(/\[\d+\]/, `[${index}]`); input.setAttribute('name', newName); } }); // 버튼의 onclick 속성 업데이트 const buttons = row.querySelectorAll('button'); buttons.forEach(button => { const onclick = button.getAttribute('onclick'); if (onclick) { const newOnclick = onclick.replace(/\(\d+\)/g, `(${index})`); button.setAttribute('onclick', newOnclick); } }); }); } // 완료일 업데이트 함수 function updateCompletionDate(checkbox) { const row = checkbox.closest('tr'); const completionDateInput = row.querySelector('input[name*="[completion_date]"]'); if (checkbox.checked) { completionDateInput.value = new Date().toISOString().split('T')[0]; completionDateInput.removeAttribute('readonly'); } else { completionDateInput.value = ''; completionDateInput.setAttribute('readonly', 'readonly'); } // 통계 업데이트 updateTaskStatistics(); } // 통계 업데이트 함수 function updateTaskStatistics() { const tbody = document.getElementById('taskTableBody'); const rows = tbody.querySelectorAll('.task-row'); let totalTasks = 0; let completedTasks = 0; rows.forEach(row => { const taskContent = row.querySelector('input[name*="[task_content]"]').value.trim(); const isCompleted = row.querySelector('input[name*="[is_completed]"]').checked; if (taskContent !== '') { totalTasks++; if (isCompleted) { completedTasks++; } } }); const pendingTasks = totalTasks - completedTasks; const completionRate = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0; // 통계 표시 업데이트 document.getElementById('totalTasks').textContent = totalTasks; document.getElementById('completedTasks').textContent = completedTasks; document.getElementById('pendingTasks').textContent = pendingTasks; document.getElementById('completionRate').textContent = completionRate + '%'; } ``` ## CSS 스타일 ### 버튼 그룹 스타일 ```css .btn-group-sm .btn { border-radius: 0.2rem; font-size: 0.875rem; line-height: 1.5; padding: 0.25rem 0.5rem; } .btn-group .btn:not(:last-child):not(.dropdown-toggle) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .btn-group .btn:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } /* 작은 버튼 스타일 */ .btn-sm { padding: 0.25rem 0.5rem; font-size: 0.875rem; border-radius: 0.2rem; } /* 아이콘 버튼 스타일 */ .btn i { font-size: 12px; } ``` ### 테이블 스타일 ```css .table-sm td, .table-sm th { padding: 0.3rem; } .task-row { transition: background-color 0.2s ease; } .task-row:hover { background-color: rgba(0, 123, 255, 0.05); } .form-control-sm { height: calc(1.5em + 0.5rem + 2px); padding: 0.25rem 0.5rem; font-size: 0.875rem; line-height: 1.5; border-radius: 0.2rem; } ``` ## 사용 방법 ### 1. HTML에 테이블 구조 추가 ```html ``` ### 2. JavaScript 함수들 추가 ```html ``` ### 3. CSS 스타일 추가 ```html ``` ### 4. Bootstrap 및 아이콘 라이브러리 포함 ```html ``` ## 주의사항 1. **최소 행 유지**: 최소 하나의 행은 항상 유지되어야 합니다. 2. **인덱스 관리**: 행이 삭제되거나 추가될 때 인덱스가 자동으로 재정렬됩니다. 3. **데이터 무결성**: 폼 제출 시 모든 행의 데이터가 올바르게 전송되도록 name 속성이 관리됩니다. 4. **브라우저 호환성**: ES6 문법을 사용하므로 최신 브라우저에서 동작합니다. ## 확장 가능한 기능 1. **드래그 앤 드롭**: 행 순서 변경 2. **일괄 작업**: 여러 행 선택 및 일괄 처리 3. **검색 및 필터링**: 특정 조건에 맞는 행만 표시 4. **데이터 유효성 검사**: 입력 데이터 검증 5. **자동 저장**: 변경사항 자동 저장 이 시스템을 사용하면 동적으로 테이블 행을 관리할 수 있는 완전한 기능을 갖춘 인터페이스를 구축할 수 있습니다.