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) 형태로 보관됩니다.

 
 
 
 
 
 
 
 
 
 

+ Recent posts