HTML 코드는 jspreadsheet과 jsuites 라이브러리를 불러와서 간단한 엑셀 스타일의 웹 스프레드시트를 생성하는 예제입니다.
아래 예시는 초기 데이터/컬럼 타입 지정 + 자동 저장(로컬스토리지) + 수동 저장·불러오기·초기화 버튼까지 모두 갖춘 통합 HTML입니다. 그대로 붙여넣어 실행하시면 됩니다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<title>jspreadsheet LocalStorage Demo</title>
<!-- libs -->
<script src="https://jspreadsheet.com/v11/jspreadsheet.js"></script>
<script src="https://jsuites.net/v5/jsuites.js"></script>
<link rel="stylesheet" href="https://jsuites.net/v5/jsuites.css" />
<link rel="stylesheet" href="https://jspreadsheet.com/v11/jspreadsheet.css" />
<!-- icons (툴바 아이콘용) -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" />
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 24px; }
.actions { display:flex; gap:8px; margin-bottom:12px; flex-wrap: wrap; }
.actions button {
padding: 8px 12px; border: 1px solid #ddd; border-radius: 8px; background: #fff; cursor: pointer;
}
.actions button:hover { background:#f6f6f6; }
#status { font-size:12px; color:#666; margin-left: 6px; }
</style>
</head>
<body>
<h1>JSpreadsheet + LocalStorage 예제</h1>
<div class="actions">
<button id="saveBtn">수동 저장</button>
<button id="loadBtn">불러오기</button>
<button id="clearBtn">로컬스토리지 초기화</button>
<span id="status"></span>
</div>
<div id="spreadsheet"></div>
<script>
// ====== 라이선스 키 (테스트 키: 하루만 유효) ======
jspreadsheet.setLicense('OWMzYWFkOTc3ZGQ2ODg0NWIwYTkyNTE3MmQ5NmNiZGY4ZGEzNjkwZTVlMTA4NjZmNTg5MzQzMDMxNmVjZWM4Mzc0ZGNjMjI5Y2NiMWMxMzIwNjdkNjJiNGJiZWY4MGJhOTJjNWY0OTNkN2QzOTNiZmVlODg1OGVhNDAwNTMyOTQsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpVNE1EY3lNekl6TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFuMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElsMHNJbVJsYlc4aU9uUnlkV1Y5');
// ====== 설정 ======
const STORAGE_KEY = 'jspreadsheet-demo-v1';
const statusEl = document.getElementById('status');
const spreadsheetEl = document.getElementById('spreadsheet');
// 초기 데이터(스토리지에 없을 때만 사용)
const defaultSheet = {
minDimensions: [6, 10],
data: [
['이름', '나이', '점수', '등록일', '분류', '비고'],
['철수', 20, 85, '2025-09-15', 'A', '우수'],
['영희', 22, 90, '2025-09-16', 'S', '장학생'],
['민수', 21, 77, '2025-09-12', 'B', '보통'],
],
columns: [
{ type: 'text', title: '이름', width: 120 },
{ type: 'numeric', title: '나이', width: 80, mask:'#,##0' },
{ type: 'numeric', title: '점수', width: 80, mask:'#,##0' },
{ type: 'calendar', title: '등록일', width: 120, options: { format:'YYYY-MM-DD' } },
{ type: 'dropdown', title: '분류', width: 90, source: ['S','A','B','C'] },
{ type: 'text', title: '비고', width: 160 },
],
};
// ====== 유틸 ======
const setStatus = (msg) => {
statusEl.textContent = msg;
if (!msg) return;
// 2초 후 상태 문구 자동 지움
setTimeout(() => { if (statusEl.textContent === msg) statusEl.textContent = ''; }, 2000);
};
// 디바운스 저장 (변경 잦을 때 과도 저장 방지)
let saveTimer = null;
const debouncedSave = (fn, delay = 500) => {
clearTimeout(saveTimer);
saveTimer = setTimeout(fn, delay);
};
// ====== 초기 로드: 로컬스토리지 → 시트 ======
function loadFromStorageOrDefault() {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
try {
const parsed = JSON.parse(saved);
return parsed; // worksheets 배열 형태 기대
} catch(e) {
console.warn('저장된 데이터를 파싱할 수 없어 기본값으로 초기화합니다.', e);
}
}
return [defaultSheet];
}
// ====== 스프레드시트 생성 ======
let jss = jspreadsheet(spreadsheetEl, {
tabs: true,
toolbar: true,
worksheets: loadFromStorageOrDefault(),
// 변경 시 자동 저장
onchange: function() {
debouncedSave(() => saveToStorage(true), 600);
},
onpaste: function() {
debouncedSave(() => saveToStorage(true), 600);
},
// 시트 추가/삭제도 저장
oninsertrow: () => debouncedSave(() => saveToStorage(true), 600),
oninsertcolumn: () => debouncedSave(() => saveToStorage(true), 600),
ondeleterow: () => debouncedSave(() => saveToStorage(true), 600),
ondeletecolumn: () => debouncedSave(() => saveToStorage(true), 600),
oncreateworksheet: () => debouncedSave(() => saveToStorage(true), 600),
ondeleteworksheet: () => debouncedSave(() => saveToStorage(true), 600),
});
// ====== 저장 로직 ======
function collectWorksheetsAsJson() {
// v11에서는 인스턴스가 배열처럼 동작 (워크시트 접근)
// 각 워크시트의 구조를 getJson()으로 수집
const arr = [];
const count = jss.worksheets ? jss.worksheets.length : (jss.length || 1);
for (let i = 0; i < count; i++) {
const ws = jss.worksheets ? jss.worksheets[i] : jss[i] || jss;
// getJson()은 데이터/열 정의/너비 등 구성을 포함
// 환경에 따라 getJson() 대신 { data: ws.getData(), columns: ws.options.columns } 형태로 수집할 수도 있습니다.
if (ws && typeof ws.getJson === 'function') {
arr.push(ws.getJson());
} else {
// 폴백
arr.push({
data: ws.getData ? ws.getData() : [],
columns: ws.options?.columns || [],
minDimensions: ws.options?.minDimensions || [6,10]
});
}
}
return arr;
}
function saveToStorage(showStatus = false) {
try {
const json = collectWorksheetsAsJson();
localStorage.setItem(STORAGE_KEY, JSON.stringify(json));
if (showStatus) setStatus('자동 저장 완료');
} catch(e) {
console.error('저장 실패', e);
if (showStatus) setStatus('저장 실패');
}
}
// ====== 불러오기 로직 ======
function reloadFromStorage() {
const saved = localStorage.getItem(STORAGE_KEY);
if (!saved) {
setStatus('저장된 데이터가 없습니다.');
return;
}
try {
const worksheets = JSON.parse(saved);
// 전체 리셋 후 다시 생성
spreadsheetEl.innerHTML = '';
jss = jspreadsheet(spreadsheetEl, {
tabs: true,
toolbar: true,
worksheets,
onchange: () => debouncedSave(() => saveToStorage(true), 600),
});
setStatus('불러오기 완료');
} catch(e) {
console.error('불러오기 실패', e);
setStatus('불러오기 실패');
}
}
// ====== 초기화 로직 ======
function clearStorageAndReset() {
localStorage.removeItem(STORAGE_KEY);
spreadsheetEl.innerHTML = '';
jss = jspreadsheet(spreadsheetEl, {
tabs: true,
toolbar: true,
worksheets: [defaultSheet],
onchange: () => debouncedSave(() => saveToStorage(true), 600),
});
setStatus('로컬스토리지 초기화 및 기본 데이터로 복원');
}
// ====== 버튼 바인딩 ======
document.getElementById('saveBtn').addEventListener('click', () => {
saveToStorage(false);
setStatus('수동 저장 완료');
});
document.getElementById('loadBtn').addEventListener('click', () => reloadFromStorage());
document.getElementById('clearBtn').addEventListener('click', () => clearStorageAndReset());
// 페이지 진입 시 한 번 저장(구성 고정화)
saveToStorage(false);
</script>
</body>
</html>
어떻게 동작하나요?
- 셀을 편집하면 디바운스(0.6초) 후 자동 저장됩니다.
- 상단 버튼으로 수동 저장/불러오기/초기화가 가능합니다.
- 저장 포맷은 로컬스토리지의 jspreadsheet-demo-v1 키에 워크시트 배열(JSON) 형태로 보관됩니다.