과제 내용
- 현재 focus 된 셀의 위쪽 헤더 왼쪽 헤더가 함께 하늘색으로 하이라이트 되도록 해줌
- 작성된 모든 데이터들을 Export SpreadSheet 버튼을 눌러서 Excel 파일로 생성
- 생성된 Excel 파일을 구글 Spreadsheet 에서 import하면 같은 데이터가 나오도록 함

진행
HTML로 기본 틀 생성 + CSS로 스타일링 → 이 때, Export 버튼, cell: 까지만 생성하고 스프레드시트는 JS로 생성해야 함
JS로 스프레드시트 생성
initSpreadSheet() 함수로 기본데이터 생성하기

const ROWS = 10;
const COLS = 10;
const spreadsheet = [];
initSpreadsheet()
function initSpreadsheet() {
for (let i = 0; i < ROWS; i++) {
let spreadsheetRow = []
for (let j = 0; j < COLS; j++) {
spreadsheetRow.push(i + "-" + j)
}
spreadsheet.push(spreadsheetRow)
}
console.log(spreadsheet);
}
- 클래스
Cell 만들기: 그러나, 위의 데이터는 문자열 데이터이다. 우리는 각 cell의 정보와 상태를 효과적으로 관리하기 위해 문자열이 아닌 객체 데이터를 생성해야 한다.
class Cell {
constructor(isHeader, disabled, data, row, column, active = false){
this.isHeader = isHeader;
this.disabled = disabled;
this.data = data;
this.row = row;
this.column = column;
this.active = active;
}
}
- 클래스 개념 정리
- 자바스크립트의 클래스?
Cell
- 자바스크립트의 객체?
const cell1 = new Cell(true, false, "Header Data", 1, 1);
즉, 자바스크립트에서는 클래스에서 객체를 설계하고, new 키워드와 클래스를 사용해서 생성되는 것이 객체이다.
class Cell {
constructor(isHeader, disabled, data, row, column, active = false) {
this.isHeader = isHeader;
this.disabled = disabled;
this.data = data;
this.row = row;
this.column = column;
this.active = active;
this.twiceRow = row * 2;
this.constantValue = 100;
}
toggleActive() {
this.active = !this.active;
}
}
const cell1 = new Cell(true, false, "Header Data", 1, 1, true);
console.log(cell1.active);
cell1.toggleActive();
console.log(cell1.active);
- 텍스트를 객체로 변경해서 출력하고자 하는 결과 화면:

- 이를 위한 코드: 수정된
initSpreadsheet 함수
function initSpreadsheet() {
for (let i = 0; i < ROWS; i++) {
let spreadsheetRow = []
for (let j = 0; j < COLS; j++) {
const cell = new Cell(false, false, i + "-" + j, i, j, i, j, false);
spreadsheetRow.push(cell);
}
spreadsheet.push(spreadsheetRow)
}
drawSheet();
console.log(spreadsheet);
}
- 화면에 보일 스프레드시트 부분 JS로 생성하기:
createCellEl 함수로 input 태그를 가진 cell들을 생성해야 함
- 이 때, cell의 id가
cell_03이면 첫번째 줄 세번째 열을 뜻함. 즉, 행-열 순으로 표기되어있음!
function createCellEl(cell) {
const cellEl = document.createElement('input');
cellEl.className = 'cell';
cellEl.id = 'cell_' + cell.row + cell.column;
cellEl.value = cell.data;
cellEl.disabled = cell.disabled;
return cellEl;
}
function drawSheet() {
for (let i = 0; i < spreadsheet.length; i++) {
for (let j = 0; j < spreadsheet[i].length; j++) {
const cell = spreadsheet[i][j];
spreadSheetContainer.append(createCellEl(cell));
}
}
}

- 생성된 input 요소 cell들을 10개씩 묶어서 하나의 row div 태그로 감싸주기 위해
drawSheet 함수 수정
지금은 그냥 단순히 cell 100개가 한줄로 생성된 상황 (화면의 크기 때문에 여러줄에 거쳐서 보이지만 사실상 줄은 없음)

function drawSheet() {
for (let i = 0; i < spreadsheet.length; i++) {
const rowContainerEl = document.createElement("div");
rowContainerEl.className = "cell-row";
for (let j = 0; j < spreadsheet[i].length; j++) {
const cell = spreadsheet[i][j];
rowContainerEl.append(createCellEl(cell));
}
spreadSheetContainer.append(rowContainerEl);
}
}
- css 설정해주기

.cell-row {
display: flex;
}
.cell {
width: 80px;
border: 1px solid lightgray;
height: 40px;
outline: none;
box-sizing: border-box;
}
- 표의 제일 왼쪽 열에 "", 1, 2, 3, 4, ... 등을 넣어서 엑셀과 비슷하게 만들어주기:
initSpreadsheet 함수 수정
- disabled, isHeader 속성도 설정해주기
const alphabets = [
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
]
function initSpreadsheet() {
for (let i = 0; i < ROWS; i++) {
let spreadsheetRow = [];
for (let j = 0; j < COLS; j++) {
let cellData = '';
let isHeader = false;
let disabled = false;
if (j === 0) {
cellData = i;
isHeader = true;
disabled = true;
}
if (i === 0) {
cellData = alphabets[j - 1];
isHeader = true;
disabled = true;
}
if (!cellData) {
cellData = "";
}
const rowName = i;
const columnName = alphabets[j - 1];
const cell = new Cell(isHeader, disabled, cellData, i, j, rowName, columnName, false);
spreadsheetRow.push(cell);
}
spreadsheet.push(spreadsheetRow);
}
drawSheet();
}
- header들만 따로 스타일링 해주기 위해
class="cell header"로 header를 추가해서 변경해주기 위해 createCellEl 함수 수정
function createCellEl(cell) {
const cellEl = document.createElement('input');
cellEl.className = 'cell';
cellEl.id = 'cell_' + cell.row + cell.column;
cellEl.value = cell.data;
cellEl.disabled = cell.disabled;
if (cell.isHeader) {
cellEl.classList.add("header");
}
return cellEl;
}
- css에서 스타일링 수정
.cell.header {
text-align: center;
background: #ddd;
}
Cell 클래스에 rowName, colName 속성을 추가하기
class Cell {
constructor(isHeader, disabled, data, row, column, rowName, columnName, active = false) {
this.isHeader = isHeader;
this.disabled = disabled;
this.data = data;
this.row = row;
this.column = column;
this.rowName = rowName;
this.columnName = columnName;
this.active = active;
}
}
- cell을 클릭했을 때 위치 값을 반환하는 함수를
createCellEl에 추가
function createCellEl(cell) {
const cellEl = document.createElement('input');
cellEl.className = 'cell';
cellEl.id = 'cell_' + cell.row + cell.column;
cellEl.value = cell.data;
cellEl.disabled = cell.disabled;
if (cell.isHeader) {
cellEl.classList.add("header");
}
cellEl.onclick = () => handleCellClick(cell);
cellEl.onchange = (e) => handleOnChange(e.target.value, cell);
return cellEl;
}
handleCellClick: cell이 클릭되었을 때 해당 cell이 속해있는 header 2개의 데이터를 가져오도록 해야 함 (highlight css 구현하기 위해서)
function handleCellClick(cell) {
const columnHeader = spreadsheet[0][cell.column];
const rowHeader = spreadsheet[cell.row][0];
const columnHeaderEl = getElFromRowCol(columnHeader.row, columnHeader.column);
const rowHeaderEl = getElFromRowCol(rowHeader.row, rowHeader.column);
columnHeaderEl.classList.add('active');
rowHeaderEl.classList.add('active');
document.querySelector("#cell-status").innerHTML = cell.columnName + cell.rowName;
}
function getElFromRowCol(row, col) {
return document.querySelector("#cell_" + row + col);
}
function handleOnChange(data, cell) {
cell.data = data;
}
.cell.header.active {
background: lightblue;
color: white;
}
- 이전에 highlight 된 부분을 지워주기 위해서
.active 클래스명을 지워주기
function handleCellClick(cell) {
clearHeaderActiveStates();
const columnHeader = spreadsheet[0][cell.column];
const rowHeader = spreadsheet[cell.row][0];
const columnHeaderEl = getElFromRowCol(columnHeader.row, columnHeader.column);
const rowHeaderEl = getElFromRowCol(rowHeader.row, rowHeader.column);
columnHeaderEl.classList.add('active');
rowHeaderEl.classList.add('active');
document.querySelector("#cell-status").innerHTML = cell.columnName + cell.rowName;
}
function clearHeaderActiveStates() {
const headers = document.querySelectorAll('.header');
headers.forEach((header) => {
header.classList.remove('active');
})
}
엑셀파일 다운받기
- 엑셀파일로 변환하기 위해서는 데이터를 아래와 같이
,로 구분된 형태로 변환해야 함

exportBtn.onclick = function (e) {
let csv = "";
for (let i = 0; i < spreadsheet.length; i++) {
if (i === 0) continue;
csv +=
spreadsheet[i]
.filter(item => !item.isHeader)
.map(item => item.data)
.join(',') + "\r\n";
}
const csvObj = new Blob([csv]);
console.log('csvObj', csvObj);
const csvUrl = URL.createObjectURL(csvObj);
console.log('csvUrl', csvUrl);
const a = document.createElement("a");
a.href = csvUrl;
a.download = 'spreadsheet name.csv';
a.click();
}
querySelector vs getElementById
querySelector: 제공한 선택자 또는 선택자 뭉치와 일치하는 문서 내 첫 번째 Element를 반환하고, 일치하는 요소가 없으면 null을 반환
getElementById: 주어진 문자열과 일치하는 id 속성을 가진 요소를 찾고, 이를 나타내는 Element 객체를 반환
- ID는 문서 내에서 유일해야 하기 때문에 특정 요소를 빠르게 찾을 때 유용함
document.querySelector(".myclass");
document.querySelector("#export-btn");
document.getElementById("myid");